You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/trezor/crypto/base32.py

100 lines
3.3 KiB

# Base32 implementation taken from the micropython-lib's base64 module
# https://github.com/micropython/micropython-lib/blob/master/base64/base64.py
#
from ubinascii import unhexlify
from ustruct import unpack
_b32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
_b32tab = [ord(c) for c in _b32alphabet]
_b32rev = {ord(v): k for k, v in enumerate(_b32alphabet)}
def encode(s: bytes) -> str:
quanta, leftover = divmod(len(s), 5)
# Pad the last quantum with zero bits if necessary
if leftover:
s = s + bytes(5 - leftover) # Don't use += !
quanta += 1
encoded = bytearray()
for i in range(quanta):
# c1 and c2 are 16 bits wide, c3 is 8 bits wide. The intent of this
# code is to process the 40 bits in units of 5 bits. So we take the 1
# leftover bit of c1 and tack it onto c2. Then we take the 2 leftover
# bits of c2 and tack them onto c3. The shifts and masks are intended
# to give us values of exactly 5 bits in width.
c1, c2, c3 = unpack("!HHB", s[i * 5 : (i + 1) * 5])
c2 += (c1 & 1) << 16 # 17 bits wide
c3 += (c2 & 3) << 8 # 10 bits wide
encoded += bytes(
[
_b32tab[c1 >> 11], # bits 1 - 5
_b32tab[(c1 >> 6) & 0x1F], # bits 6 - 10
_b32tab[(c1 >> 1) & 0x1F], # bits 11 - 15
_b32tab[c2 >> 12], # bits 16 - 20 (1 - 5)
_b32tab[(c2 >> 7) & 0x1F], # bits 21 - 25 (6 - 10)
_b32tab[(c2 >> 2) & 0x1F], # bits 26 - 30 (11 - 15)
_b32tab[c3 >> 5], # bits 31 - 35 (1 - 5)
_b32tab[c3 & 0x1F], # bits 36 - 40 (1 - 5)
]
)
# Adjust for any leftover partial quanta
if leftover == 1:
encoded = encoded[:-6] + b"======"
elif leftover == 2:
encoded = encoded[:-4] + b"===="
elif leftover == 3:
encoded = encoded[:-3] + b"==="
elif leftover == 4:
encoded = encoded[:-1] + b"="
return bytes(encoded).decode()
def decode(s: str) -> bytes:
data = s.encode()
_, leftover = divmod(len(data), 8)
if leftover:
raise ValueError("Incorrect padding")
# Strip off pad characters from the right. We need to count the pad
# characters because this will tell us how many null bytes to remove from
# the end of the decoded string.
padchars = data.find(b"=")
if padchars > 0:
padchars = len(data) - padchars
data = data[:-padchars]
else:
padchars = 0
# Now decode the full quanta
parts = []
acc = 0
shift = 35
for c in data:
val = _b32rev.get(c)
if val is None:
raise ValueError("Non-base32 digit found")
acc += _b32rev[c] << shift
shift -= 5
if shift < 0:
parts.append(unhexlify((f"{acc:010x}").encode()))
acc = 0
shift = 35
# Process the last, partial quanta
last = unhexlify(bytes(f"{acc:010x}", "ascii"))
if padchars == 0:
last = b"" # No characters
elif padchars == 1:
last = last[:-1]
elif padchars == 3:
last = last[:-2]
elif padchars == 4:
last = last[:-3]
elif padchars == 6:
last = last[:-4]
else:
raise ValueError("Incorrect padding")
parts.append(last)
return b"".join(parts)