|
| 1 | +# -*- coding: UTF-8 -*- |
| 2 | +"""ModHex Codec - modified hexadecimal content encoding. |
| 3 | +
|
| 4 | +ModHex (modified hexadecimal) is the hexadecimal encoding used by YubiKeys for their one-time passwords: the digits 0 to |
| 5 | + f are replaced by the letters "cbdefghijklnrtuv", which are at the same positions on most keyboard layouts, as a |
| 6 | + YubiKey types its output as a USB keyboard. Encoding gives the digits without delimiter (as typed by a YubiKey), while |
| 7 | + decoding ignores the case, whitespaces and the delimiters "%", ",", ";" and ":" (as output by CyberChef). |
| 8 | +
|
| 9 | +This codec: |
| 10 | +- en/decodes strings from str to str |
| 11 | +- en/decodes strings from bytes to bytes |
| 12 | +- decodes file content to str (read) |
| 13 | +- encodes file content from str to bytes (write) |
| 14 | +
|
| 15 | +Reference: https://developers.yubico.com/yubico-c/Manuals/modhex.1.html |
| 16 | +""" |
| 17 | +from ..__common__ import * |
| 18 | + |
| 19 | + |
| 20 | +__examples__ = { |
| 21 | + 'enc(modhex|mod-hex|mod_hex|base16-modhex|yubikey-modhex)': {'\n\x14\x1e': "clbfbu", 'test': "ifhgieif", '': ""}, |
| 22 | + 'dec(modhex)': {'clbfbu': "\n\x14\x1e", 'cl bf bu': "\n\x14\x1e", 'CL:BF:BU': "\n\x14\x1e", 'cl\r\nbf': "\n\x14", |
| 23 | + 'cla': None, 'clb': None, 'clxx': None}, |
| 24 | + 'enc-dec(modhex)': ["test", "This is a test", "@random{512,1024,2048}"], |
| 25 | +} |
| 26 | +__guess__ = ["modhex"] |
| 27 | + |
| 28 | + |
| 29 | +MODHEX, HEX = "cbdefghijklnrtuv", "0123456789abcdef" |
| 30 | +_ENC, _DEC = str.maketrans(HEX, MODHEX), {c: h for c, h in zip(MODHEX, HEX)} |
| 31 | +DELIMITERS = " \t\r\n%,;:" |
| 32 | + |
| 33 | + |
| 34 | +def modhex_encode(text, errors="strict"): |
| 35 | + t = b(text) |
| 36 | + return t.hex().translate(_ENC), len(t) |
| 37 | + |
| 38 | + |
| 39 | +def modhex_decode(text, errors="strict"): |
| 40 | + _h, digits = handle_error("modhex", errors, decode=True), [] |
| 41 | + for i, c in enumerate(ensure_str(text).lower()): |
| 42 | + if c in _DEC: |
| 43 | + digits.append(_DEC[c]) |
| 44 | + elif c not in DELIMITERS: |
| 45 | + _h(c, i) |
| 46 | + # an odd number of digits leaves an incomplete byte, which is an error (or dropped if errors are not handled |
| 47 | + # strictly) |
| 48 | + if len(digits) % 2: |
| 49 | + _h(text[-1:], len(text) - 1) |
| 50 | + digits = digits[:-1] |
| 51 | + return bytes.fromhex("".join(digits)), len(text) |
| 52 | + |
| 53 | + |
| 54 | +add("modhex", modhex_encode, modhex_decode, r"^(?:mod[-_]?hex|base[-_]?16[-_]modhex|yubikey[-_]modhex)$", |
| 55 | + entropy=4., expansion_factor=2., len_charset=16, printables_rate=1.) |
0 commit comments