File: modhex.py

package info (click to toggle)
python-yubiotp 1.0.0.post1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 196 kB
  • sloc: python: 671; makefile: 130
file content (116 lines) | stat: -rw-r--r-- 2,851 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
Implementation of `modhex encoding <http://www.yubico.com/modhex-calculator>`_,
which uses keyboard-independent characters.

::

    hex digit:    0123456789abcdef
    modhex digit: cbdefghijklnrtuv
"""

from binascii import hexlify, unhexlify
from functools import partial
import struct


__all__ = ['modhex', 'unmodhex', 'is_modhex', 'hex_to_modhex', 'modhex_to_hex']


def modhex(data):
    """
    Encode a string of bytes as modhex.

    >>> modhex(b'abcdefghijklmnop') == b'hbhdhehfhghhhihjhkhlhnhrhthuhvic'
    True
    """
    return hex_to_modhex(hexlify(data))


def unmodhex(encoded):
    """
    Decode a modhex string to its binary form.

    >>> unmodhex(b'hbhdhehfhghhhihjhkhlhnhrhthuhvic') == b'abcdefghijklmnop'
    True
    """
    return unhexlify(modhex_to_hex(encoded))


def is_modhex(encoded):
    """
    Returns ``True`` iff the given string is valid modhex.

    >>> is_modhex(b'cbdefghijklnrtuv')
    True
    >>> is_modhex(b'cbdefghijklnrtuvv')
    False
    >>> is_modhex(b'cbdefghijklnrtuvyy')
    False
    """
    if any(c not in modhex_chars for c in encoded):
        return False
    elif len(encoded) % 2 != 0:
        return False
    else:
        return True


def hex_to_modhex(hex_str):
    """
    Convert a string of hex digits to a string of modhex digits.

    >>> hex_to_modhex(b'69b6481c8baba2b60e8f22179b58cd56') == b'hknhfjbrjnlnldnhcujvddbikngjrtgh'
    True
    >>> hex_to_modhex(b'6j')
    Traceback (most recent call last):
        ...
    ValueError: Illegal hex character in input
    """
    try:
        return b''.join(int2byte(hex_to_modhex_char(b))
                        for b in iter(hex_str.lower()))
    except ValueError:
        raise ValueError('Illegal hex character in input')


def modhex_to_hex(modhex_str):
    """
    Convert a string of modhex digits to a string of hex digits.

    >>> modhex_to_hex(b'hknhfjbrjnlnldnhcujvddbikngjrtgh') == b'69b6481c8baba2b60e8f22179b58cd56'
    True
    >>> modhex_to_hex(b'hbhdxx')
    Traceback (most recent call last):
        ...
    ValueError: Illegal modhex character in input
    """
    try:
        return b''.join(int2byte(modhex_to_hex_char(b))
                        for b in iter(modhex_str.lower()))
    except ValueError:
        raise ValueError('Illegal modhex character in input')


#
# Internals
#

def int2byte(i):
    return struct.Struct(">B").pack(i)


def lookup(alist, key):
    try:
        return next(v for (k, v) in alist if k == key)
    except StopIteration:
        raise ValueError()


hex_chars = b'0123456789abcdef'
modhex_chars = b'cbdefghijklnrtuv'

hex_to_modhex_map = list(zip(iter(hex_chars), iter(modhex_chars)))
modhex_to_hex_map = list(zip(iter(modhex_chars), iter(hex_chars)))

hex_to_modhex_char = partial(lookup, hex_to_modhex_map)
modhex_to_hex_char = partial(lookup, modhex_to_hex_map)