|
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +"""AER-256 Codec - AER-256 content encryption. |
| 3 | +
|
| 4 | +AER-256 is a toy cipher found on online JavaScript encryption tools. The text is split into blocks of half the key |
| 5 | + length (rounded up), each block being read as a big-endian number of its character codes. For each character of the key |
| 6 | + (at least 3), its code is added to, divides or is subtracted from this number in turn (in floating-point arithmetic), |
| 7 | + and the resulting numbers are written as JavaScript would (e.g. "123.456") and separated by ", ". Decryption applies |
| 8 | + the inverse operations in reverse order and rounds the result. As numbers are double-precision floats, blocks of more |
| 9 | + than 6 characters (i.e. keys of more than 12 characters) lose precision and generally cannot be fully decrypted. Only |
| 10 | + characters with a code below 256 can be encrypted, and null characters at the start of a block are lost. |
| 11 | +
|
| 12 | +This codec: |
| 13 | +- en/decodes strings from str to str |
| 14 | +- en/decodes strings from bytes to bytes |
| 15 | +- decodes file content to str (read) |
| 16 | +- encodes file content from str to bytes (write) |
| 17 | +
|
| 18 | +Reference: https://github.com/gungunfebrianza/Mastering-Cryptography/blob/master/AER-256/AER-256.js |
| 19 | +""" |
| 20 | +from math import floor, isfinite |
| 21 | + |
| 22 | +from ..__common__ import * |
| 23 | + |
| 24 | + |
| 25 | +__examples__ = { |
| 26 | + 'enc(aer256|aer256-ab)': None, |
| 27 | + 'enc(aer256-key|aer-256_key)': {'this is a test': "175.10891089108912, 147.33663366336634, -37.79207920792079, " |
| 28 | + "171.86138613861385, 126.23762376237624, 175.0792079207921, " |
| 29 | + "172.69306930693068"}, |
| 30 | + 'dec(aer256-key)': {'175.10891089108912, 147.33663366336634': "this", '175.1, x': None}, |
| 31 | + 'enc-dec(aer256-key|aer256-secret|aer256-K3y!|aer256-123456789012)': ["", "this is a test", "Hello, World! 123", |
| 32 | + "\xe9t\xe9 \xff"], |
| 33 | +} |
| 34 | + |
| 35 | + |
| 36 | +def _js_str(x): |
| 37 | + """ Convert a float to a string like JavaScript's Number.prototype.toString. """ |
| 38 | + x = float(x) |
| 39 | + if x != x: |
| 40 | + return "NaN" |
| 41 | + if x < 0: |
| 42 | + return "-" + _js_str(-x) |
| 43 | + if x == 0: |
| 44 | + return "0" |
| 45 | + if not isfinite(x): |
| 46 | + return "Infinity" |
| 47 | + # shortest digits that round-trip (as with repr) and decimal exponent n such that x = 0.digits * 10^n |
| 48 | + m, _, e = repr(x).partition("e") |
| 49 | + i, _, f = m.partition(".") |
| 50 | + d = (i + f.rstrip("0")) if f != "0" else i |
| 51 | + n = len(i) + int(e or 0) |
| 52 | + k = len(d.lstrip("0")) |
| 53 | + n -= len(d) - k |
| 54 | + d = d.lstrip("0") |
| 55 | + if k <= n <= 21: |
| 56 | + return d + "0" * (n - k) |
| 57 | + if 0 < n <= 21: |
| 58 | + return d[:n] + "." + d[n:] |
| 59 | + if -6 < n <= 0: |
| 60 | + return "0." + "0" * -n + d |
| 61 | + return d[0] + ("." + d[1:] if k > 1 else "") + "e" + "+-"[n < 1] + str(abs(n - 1)) |
| 62 | + |
| 63 | + |
| 64 | +def __check_key(key): |
| 65 | + key = str(key or "") |
| 66 | + if len(key) < 3: |
| 67 | + raise LookupError("Bad parameter for encoding 'aer256': key must be at least 3 characters long") |
| 68 | + return [ord(c) for c in key], (len(key) + 1) // 2 |
| 69 | + |
| 70 | + |
| 71 | +def aer256_encode(key=None): |
| 72 | + def encode(text, errors="strict"): |
| 73 | + k, n = __check_key(key) |
| 74 | + _h, t = handle_error("aer256", errors), ensure_str(text) |
| 75 | + t = "".join(c for c in "".join(c if ord(c) < 256 else _h(c, i) for i, c in enumerate(t)) if ord(c) < 256) |
| 76 | + r = [] |
| 77 | + for i in range(0, len(t), n): |
| 78 | + v = float(int("".join(f"{ord(c):02x}" for c in t[i:i+n]), 16)) |
| 79 | + for j, kc in enumerate(k): |
| 80 | + if j % 3 == 0: |
| 81 | + v += kc |
| 82 | + elif j % 3 == 1: |
| 83 | + v /= kc |
| 84 | + else: |
| 85 | + v -= kc |
| 86 | + r.append(_js_str(v)) |
| 87 | + return ", ".join(r), len(text) |
| 88 | + return encode |
| 89 | + |
| 90 | + |
| 91 | +def aer256_decode(key=None): |
| 92 | + def decode(text, errors="strict"): |
| 93 | + k, _ = __check_key(key) |
| 94 | + _h, t, r = handle_error("aer256", errors, decode=True, kind="number"), ensure_str(text), "" |
| 95 | + for i, s in enumerate(t.split(", ") if t else []): |
| 96 | + try: |
| 97 | + v = float(s) |
| 98 | + except ValueError: |
| 99 | + r += _h(s, i) |
| 100 | + continue |
| 101 | + for j in range(len(k) - 1, -1, -1): |
| 102 | + if j % 3 == 0: |
| 103 | + v -= k[j] |
| 104 | + elif j % 3 == 1: |
| 105 | + v *= k[j] |
| 106 | + else: |
| 107 | + v += k[j] |
| 108 | + # Math.round, then the hexadecimal digits of the number (nothing when it is not positive) |
| 109 | + v = floor(v) + (v - floor(v) >= .5) if isfinite(v) else 0 |
| 110 | + h = f"{v:x}" if v > 0 else "" |
| 111 | + h = "0" * (len(h) % 2) + h |
| 112 | + r += "".join(chr(int(h[j:j+2], 16)) for j in range(0, len(h), 2)) |
| 113 | + return r, len(text) |
| 114 | + return decode |
| 115 | + |
| 116 | + |
| 117 | +add("aer256", aer256_encode, aer256_decode, r"^aer[-_]?256(?:[-_](\S*))?$", guess=None) |
0 commit comments