diff -r e1118ff088be Lib/base64.py --- a/Lib/base64.py Fri Jan 18 19:59:13 2013 +0200 +++ b/Lib/base64.py Fri Jan 18 19:45:46 2013 +0100 @@ -150,14 +150,33 @@ 8: b'I', 17: b'R', 26: b'2', } +# "Extended Hex" alphabet specified in RFC 4648 Section 7 +_b32hexalphabet = { + 0: b'0', 9: b'9', 18: b'I', 27: b'R', + 1: b'1', 10: b'A', 19: b'J', 28: b'S', + 2: b'2', 11: b'B', 20: b'K', 29: b'T', + 3: b'3', 12: b'C', 21: b'L', 30: b'U', + 4: b'4', 13: b'D', 22: b'M', 31: b'V', + 5: b'5', 14: b'E', 23: b'N', + 6: b'6', 15: b'F', 24: b'O', + 7: b'7', 16: b'G', 25: b'P', + 8: b'8', 17: b'H', 26: b'Q', + } + + _b32tab = [v[0] for k, v in sorted(_b32alphabet.items())] _b32rev = dict([(v[0], k) for k, v in _b32alphabet.items()]) +_b32hextab = [v[0] for k, v in sorted(_b32hexalphabet.items())] +_b32hexrev = dict([(v[0], k) for k, v in _b32hexalphabet.items()]) -def b32encode(s): + +def b32encode(s, base32hex=False): """Encode a byte string using Base32. - s is the byte string to encode. The encoded byte string is returned. + s is the string to encode. Optional base32hex indicates whether to use + the modified "Extended Hex" alphabet as specified in RFC 4648 Section 7. + The encoded string is returned. """ if not isinstance(s, bytes_types): raise TypeError("expected bytes, not %s" % s.__class__.__name__) @@ -166,6 +185,7 @@ if leftover: s = s + bytes(5 - leftover) # Don't use += ! quanta += 1 + b32tab = _b32hextab if base32hex else _b32tab encoded = bytes() for i in range(quanta): # c1 and c2 are 16 bits wide, c3 is 8 bits wide. The intent of this @@ -176,14 +196,14 @@ c1, c2, c3 = struct.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) + 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: @@ -197,7 +217,7 @@ return encoded -def b32decode(s, casefold=False, map01=None): +def b32decode(s, casefold=False, map01=None, base32hex=False): """Decode a Base32 encoded byte string. s is the byte string to decode. Optional casefold is a flag @@ -212,6 +232,9 @@ the letter O). For security purposes the default is None, so that 0 and 1 are not allowed in the input. + Optional base32hex indicates whether to use the modified "Extended Hex" + alphabet as specified in RFC 4648 Section 7. + The decoded byte string is returned. binascii.Error is raised if the input is incorrectly padded or if there are non-alphabet characters present in the input. @@ -229,6 +252,7 @@ s = s.translate(bytes.maketrans(b'01', b'O' + map01)) if casefold: s = s.upper() + b32rev = _b32hexrev if base32hex else _b32rev # 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. @@ -243,10 +267,10 @@ acc = 0 shift = 35 for c in s: - val = _b32rev.get(c) + val = b32rev.get(c) if val is None: raise TypeError('Non-base32 digit found') - acc += _b32rev[c] << shift + acc += b32rev[c] << shift shift -= 5 if shift < 0: parts.append(binascii.unhexlify(bytes('%010x' % acc, "ascii"))) diff -r e1118ff088be Lib/test/test_base64.py --- a/Lib/test/test_base64.py Fri Jan 18 19:59:13 2013 +0200 +++ b/Lib/test/test_base64.py Fri Jan 18 19:45:46 2013 +0100 @@ -181,6 +181,16 @@ eq(base64.b32encode(b'abcde'), b'MFRGGZDF') self.assertRaises(TypeError, base64.b32encode, "") + def test_b32encode_hex(self): + eq = self.assertEqual + eq(base64.b32encode(b'', base32hex=True), b'') + eq(base64.b32encode(b'f', base32hex=True), b'CO======') + eq(base64.b32encode(b'fo', base32hex=True), b'CPNG====') + eq(base64.b32encode(b'foo', base32hex=True), b'CPNMU===') + eq(base64.b32encode(b'foob', base32hex=True), b'CPNMUOG=') + eq(base64.b32encode(b'fooba', base32hex=True), b'CPNMUOJ1') + eq(base64.b32encode(b'foobar', base32hex=True), b'CPNMUOJ1E8======') + def test_b32decode(self): eq = self.assertEqual tests = {b'': b'', @@ -234,6 +244,16 @@ eq(base64.b32decode(data, map01=map01_str), res) eq(base64.b32decode(data_str, map01=map01_str), res) + def test_b32decode_hex(self): + eq = self.assertEqual + eq(base64.b32decode(b'', base32hex=True), b'') + eq(base64.b32decode(b'CO======', base32hex=True), b'f') + eq(base64.b32decode(b'CPNG====', base32hex=True), b'fo') + eq(base64.b32decode(b'CPNMU===', base32hex=True), b'foo') + eq(base64.b32decode(b'CPNMUOG=', base32hex=True), b'foob') + eq(base64.b32decode(b'CPNMUOJ1', base32hex=True), b'fooba') + eq(base64.b32decode(b'CPNMUOJ1E8======', base32hex=True), b'foobar') + def test_b32decode_error(self): for data in [b'abc', b'ABCDEF==']: with self.assertRaises(binascii.Error):