|
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +"""Vic Cipher Codec - vic content encoding. |
| 3 | +
|
| 4 | +This codec: |
| 5 | +- en/decodes strings from str to str |
| 6 | +- en/decodes strings from bytes to bytes |
| 7 | +- decodes file content to str (read) |
| 8 | +- encodes file content from str to bytes (write) |
| 9 | +""" |
| 10 | +from itertools import cycle |
| 11 | +from string import ascii_uppercase as UC |
| 12 | + |
| 13 | +from ..__common__ import * |
| 14 | + |
| 15 | + |
| 16 | +__examples__ = { |
| 17 | + 'dec(vic-test-33)': None, |
| 18 | + 'dec(vic-*-12-1234T': {'BBXMBDMBCBBHEMC': "THISISATEST"}, |
| 19 | + 'dec(vic-^-26-0248T)': {'VVXYWYY.XYJ': "VICTOR2"}, # '2' because of superfluous extra letter from substitution |
| 20 | + 'enc(vic-test-12-ABC)': None, |
| 21 | + 'enc(vic-*-12-1234T': {'This is a Test': "BBXMBDMBCBBHEMC"}, |
| 22 | + 'enc(vic-^-26-0248)': {'VICTOR': "88547440546"}, |
| 23 | + 'enc(vic-^-26-0248T)': {'VICTOR': "VVXYWYY.XYJ"}, |
| 24 | + 'enc-dec(vic-^-26)': ["TEST", "LONGTESTSTRING", "VICTOR"], |
| 25 | + 'enc-dec(vic-^-85-72564)': ["TEST", "LONGTESTSTRING", "VICTOR"], |
| 26 | + 'enc-dec(vic-^-85-54321T)': ["TEST", "LONGTESTSTRING", "VICTOR"], |
| 27 | +} |
| 28 | +__guess__ = ["vic"] |
| 29 | + |
| 30 | + |
| 31 | +def __build_alphabet(key, alphabet=UC, reverse=False): |
| 32 | + """Return the 28-letter alphabet derived from the input key.""" |
| 33 | + seen, result = set(), "" |
| 34 | + for c in (key or "").upper(): |
| 35 | + if c.isalpha() and c not in seen: |
| 36 | + result += c |
| 37 | + seen.add(c) |
| 38 | + for c in (alphabet[::-1] if reverse else alphabet): |
| 39 | + if c not in seen: |
| 40 | + result += c |
| 41 | + return "./" + result if reverse else result + "./" |
| 42 | + |
| 43 | + |
| 44 | +def __build_checkerboard(alphabet, blank1=1, blank2=2): |
| 45 | + """Build encode/decode lookup tables for the straddling checkerboard. |
| 46 | +
|
| 47 | + Layout with digit1=2 and digit2=6: |
| 48 | + 0 1 [2] 3 4 5 [6] 7 8 9 |
| 49 | + 0: * * * * * * * * |
| 50 | + 2: * * * * * * * * * * |
| 51 | + 6: * * * * * * * * * * |
| 52 | + """ |
| 53 | + if blank1 == blank2: |
| 54 | + raise LookupError(f"Bad parameter for encoding 'vic': blank1 and blank2 cannot be identical") |
| 55 | + enc, dec, i = {}, {}, 0 |
| 56 | + # top row |
| 57 | + for col in range(10): |
| 58 | + if col not in (blank1, blank2): |
| 59 | + enc[alphabet[i]] = str(col) |
| 60 | + i += 1 |
| 61 | + # second row ; header digit is 'blank1' |
| 62 | + for col in range(10): |
| 63 | + enc[alphabet[i]] = str(blank1) + str(col) |
| 64 | + i += 1 |
| 65 | + # third row ; header digit is 'blank2' |
| 66 | + for col in range(10): |
| 67 | + enc[alphabet[i]] = str(blank2) + str(col) |
| 68 | + i += 1 |
| 69 | + return enc, {v: k for k, v in enc.items()} |
| 70 | + |
| 71 | + |
| 72 | +def __set_params(key, blanks, numeric_key): |
| 73 | + return (UC if key == "*" else UC[::-1] if key == "^" else key).upper(), \ |
| 74 | + tuple(map(int, str(blanks) or "12")), \ |
| 75 | + str(numeric_key or "").rstrip("T"), \ |
| 76 | + key == "^", \ |
| 77 | + str(numeric_key or " ")[-1] == "T" |
| 78 | + |
| 79 | + |
| 80 | +def vic_encode(key=None, blanks=None, numeric_key=None): |
| 81 | + key, blanks, numeric_key, rev, txt = __set_params(key, blanks, numeric_key) |
| 82 | + enc_map, dec_map = __build_checkerboard(__build_alphabet(key, reverse=rev), blanks[0], blanks[1]) |
| 83 | + def _encode(text, errors="strict"): |
| 84 | + _h = handle_error("vic", errors) |
| 85 | + digits, nk_i, nk_l = [], 0, len(numeric_key or "") |
| 86 | + for pos, c in enumerate(ensure_str(text).upper().replace(" ", "")): |
| 87 | + # 1) encode with the straddling checkerboard |
| 88 | + c = enc_map[c] if c in enc_map else _h(c, pos, "".join(digits)) |
| 89 | + # 2) if numeric_key is defined, over-encrypt digits |
| 90 | + if numeric_key and c.isdigit(): |
| 91 | + for ci in c: |
| 92 | + digits.append(str((int(ci) + int(numeric_key[nk_i % nk_l])) % 10)) |
| 93 | + nk_i += 1 |
| 94 | + else: |
| 95 | + digits.append(c) |
| 96 | + r = "".join(d for d in digits if d) |
| 97 | + # 3) if text mode, convert digits to text |
| 98 | + if txt: |
| 99 | + i, r0, l, r = 0, r, len(r), "" |
| 100 | + while i < len(r0): |
| 101 | + if int(r0[i]) in blanks: |
| 102 | + r += dec_map[r0[i] + ("0" if i == l - 1 else r0[i+1])] |
| 103 | + i += 1 |
| 104 | + else: |
| 105 | + r += dec_map[r0[i]] |
| 106 | + i += 1 |
| 107 | + return r, len(r) |
| 108 | + return _encode |
| 109 | + |
| 110 | + |
| 111 | +def vic_decode(key=None, blanks=None, numeric_key=None): |
| 112 | + key, blanks, numeric_key, rev, txt = __set_params(key, blanks, numeric_key) |
| 113 | + enc_map, dec_map = __build_checkerboard(__build_alphabet(key, reverse=rev), blanks[0], blanks[1]) |
| 114 | + def _decode(text, errors="strict"): |
| 115 | + _h = handle_error("vic", errors, decode=True) |
| 116 | + # 1) if text mode, convert text to digits |
| 117 | + text = "".join(enc_map[c] for c in ensure_str(text)) if txt else ensure_str(text) |
| 118 | + # 2) if numeric_key is defined, over-decrypt |
| 119 | + digits, nk_i, nk_l = [], 0, len(numeric_key or "") |
| 120 | + for pos, c in enumerate(text): |
| 121 | + if numeric_key and c.isdigit(): |
| 122 | + for ci in c: |
| 123 | + digits.append(str((int(c) - int(numeric_key[nk_i % nk_l])) % 10)) |
| 124 | + nk_i += 1 |
| 125 | + else: |
| 126 | + digits.append(c if c.isdigit() else _h(c, pos, "".join(digits))) |
| 127 | + # 3) decode with the straddling checkerboard |
| 128 | + i, r, r0 = 0, "", "".join(d for d in digits if d) |
| 129 | + while i < (l := len(r0)): |
| 130 | + if int(r0[i]) in blanks: |
| 131 | + r += r0[i] if i == l - 1 else dec_map[r0[i] + r0[i+1]] |
| 132 | + i += 1 |
| 133 | + else: |
| 134 | + r += dec_map[r0[i]] if r0[i] in dec_map else _h(r0[i], i, r) |
| 135 | + i += 1 |
| 136 | + return r, len(r) |
| 137 | + return _decode |
| 138 | + |
| 139 | + |
| 140 | +add("vic", vic_encode, vic_decode, r"vic(?:[-_]([\^*./a-zA-Z]+))?(?:[-_]([0-9]{2}))?(?:[-_]([0-9]+T?))?$", |
| 141 | + printables_rate=1., penalty=.1) |
| 142 | + |
0 commit comments