codekingpro/portable-devtools
115k
1"""2passlib.utils.binary - binary data encoding/decoding/manipulation3"""4#=============================================================================5# imports6#=============================================================================7# core8from __future__ import absolute_import, division, print_function9from base64 import (10 b64encode,11 b64decode,12 b32decode as _b32decode,13 b32encode as _b32encode,14)15from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError16import logging17log = logging.getLogger(__name__)18# site19# pkg20from passlib import exc21from passlib.utils.compat import (22 PY3, bascii_to_str,23 irange, imap, iter_byte_chars, join_byte_values, join_byte_elems,24 nextgetter, suppress_cause,25 u, unicode, unicode_or_bytes_types,26)27from passlib.utils.decor import memoized_property28# from passlib.utils import BASE64_CHARS, HASH64_CHARS29# local30__all__ = [31 # constants32 "BASE64_CHARS", "PADDED_BASE64_CHARS",33 "AB64_CHARS",34 "HASH64_CHARS",35 "BCRYPT_CHARS",36 "HEX_CHARS", "LOWER_HEX_CHARS", "UPPER_HEX_CHARS",37 38 "ALL_BYTE_VALUES",39 40 # misc41 "compile_byte_translation",42 43 # base6444 'ab64_encode', 'ab64_decode',45 'b64s_encode', 'b64s_decode',46 47 # base3248 "b32encode", "b32decode",49 50 # custom encodings51 'Base64Engine',52 'LazyBase64Engine',53 'h64',54 'h64big',55 'bcrypt64',56]57 58#=============================================================================59# constant strings60#=============================================================================61 62#-------------------------------------------------------------63# common salt_chars & checksum_chars values64#-------------------------------------------------------------65 66#: standard base64 charmap67BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")68 69#: alt base64 charmap -- "." instead of "+"70AB64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./")71 72#: charmap used by HASH64 encoding.73HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")74 75#: charmap used by BCrypt76BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")77 78#: std base64 chars + padding char79PADDED_BASE64_CHARS = BASE64_CHARS + u("=")80 81#: all hex chars82HEX_CHARS = u("0123456789abcdefABCDEF")83 84#: upper case hex chars85UPPER_HEX_CHARS = u("0123456789ABCDEF")86 87#: lower case hex chars88LOWER_HEX_CHARS = u("0123456789abcdef")89 90#-------------------------------------------------------------91# byte strings92#-------------------------------------------------------------93 94#: special byte string containing all possible byte values95#: NOTE: for efficiency, this is treated as singleton by some of the code96ALL_BYTE_VALUES = join_byte_values(irange(256))97 98#: some string constants we reuse99B_EMPTY = b''100B_NULL = b'\x00'101B_EQUAL = b'='102 103#=============================================================================104# byte translation105#=============================================================================106 107#: base list used to compile byte translations108_TRANSLATE_SOURCE = list(iter_byte_chars(ALL_BYTE_VALUES))109 110def compile_byte_translation(mapping, source=None):111 """112 return a 256-byte string for translating bytes using specified mapping.113 bytes not specified by mapping will be left alone.114 115 :param mapping:116 dict mapping input byte (str or int) -> output byte (str or int).117 118 :param source:119 optional existing byte translation string to use as base.120 (must be 255-length byte string). defaults to identity mapping.121 122 :returns:123 255-length byte string for passing to bytes().translate.124 """125 if source is None:126 target = _TRANSLATE_SOURCE[:]127 else:128 assert isinstance(source, bytes) and len(source) == 255129 target = list(iter_byte_chars(source))130 for k, v in mapping.items():131 if isinstance(k, unicode_or_bytes_types):132 k = ord(k)133 assert isinstance(k, int) and 0 <= k < 256134 if isinstance(v, unicode):135 v = v.encode("ascii")136 assert isinstance(v, bytes) and len(v) == 1137 target[k] = v138 return B_EMPTY.join(target)139 140#=============================================================================141# unpadding / stripped base64 encoding142#=============================================================================143def b64s_encode(data):144 """145 encode using shortened base64 format which omits padding & whitespace.146 uses default ``+/`` altchars.147 """148 return b2a_base64(data).rstrip(_BASE64_STRIP)149 150def b64s_decode(data):151 """152 decode from shortened base64 format which omits padding & whitespace.153 uses default ``+/`` altchars.154 """155 if isinstance(data, unicode):156 # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()157 try:158 data = data.encode("ascii")159 except UnicodeEncodeError:160 raise suppress_cause(ValueError("string argument should contain only ASCII characters"))161 off = len(data) & 3162 if off == 0:163 pass164 elif off == 2:165 data += _BASE64_PAD2166 elif off == 3:167 data += _BASE64_PAD1168 else: # off == 1169 raise ValueError("invalid base64 input")170 try:171 return a2b_base64(data)172 except _BinAsciiError as err:173 raise suppress_cause(TypeError(err))174 175#=============================================================================176# adapted-base64 encoding177#=============================================================================178_BASE64_STRIP = b"=\n"179_BASE64_PAD1 = b"="180_BASE64_PAD2 = b"=="181 182# XXX: Passlib 1.8/1.9 -- deprecate everything that's using ab64_encode(),183# have it start outputing b64s_encode() instead? can use a64_decode() to retain backwards compat.184 185def ab64_encode(data):186 """187 encode using shortened base64 format which omits padding & whitespace.188 uses custom ``./`` altchars.189 190 it is primarily used by Passlib's custom pbkdf2 hashes.191 """192 return b64s_encode(data).replace(b"+", b".")193 194def ab64_decode(data):195 """196 decode from shortened base64 format which omits padding & whitespace.197 uses custom ``./`` altchars, but supports decoding normal ``+/`` altchars as well.198 199 it is primarily used by Passlib's custom pbkdf2 hashes.200 """201 if isinstance(data, unicode):202 # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()203 try:204 data = data.encode("ascii")205 except UnicodeEncodeError:206 raise suppress_cause(ValueError("string argument should contain only ASCII characters"))207 return b64s_decode(data.replace(b".", b"+"))208 209#=============================================================================210# base32 codec211#=============================================================================212 213def b32encode(source):214 """215 wrapper around :func:`base64.b32encode` which strips padding,216 and returns a native string.217 """218 # NOTE: using upper case by default here, since 'I & L' are less219 # visually ambiguous than 'i & l'220 return bascii_to_str(_b32encode(source).rstrip(B_EQUAL))221 222#: byte translation map to replace common mistyped base32 chars.223#: XXX: could correct '1' -> 'I', but could be a mistyped lower-case 'l', so leaving it alone.224_b32_translate = compile_byte_translation({"8": "B", "0": "O"})225 226#: helper to add padding227_b32_decode_pad = B_EQUAL * 8228 229def b32decode(source):230 """231 wrapper around :func:`base64.b32decode`232 which handles common mistyped chars.233 padding optional, ignored if present.234 """235 # encode & correct for typos236 if isinstance(source, unicode):237 source = source.encode("ascii")238 source = source.translate(_b32_translate)239 240 # pad things so final string is multiple of 8241 remainder = len(source) & 0x7242 if remainder:243 source += _b32_decode_pad[:-remainder]244 245 # XXX: py27 stdlib's version of this has some inefficiencies,246 # could look into using optimized version.247 return _b32decode(source, True)248 249#=============================================================================250# base64-variant encoding251#=============================================================================252 253class Base64Engine(object):254 """Provides routines for encoding/decoding base64 data using255 arbitrary character mappings, selectable endianness, etc.256 257 :arg charmap:258 A string of 64 unique characters,259 which will be used to encode successive 6-bit chunks of data.260 A character's position within the string should correspond261 to its 6-bit value.262 263 :param big:264 Whether the encoding should be big-endian (default False).265 266 .. note::267 This class does not currently handle base64's padding characters268 in any way what so ever.269 270 Raw Bytes <-> Encoded Bytes271 ===========================272 The following methods convert between raw bytes,273 and strings encoded using the engine's specific base64 variant:274 275 .. automethod:: encode_bytes276 .. automethod:: decode_bytes277 .. automethod:: encode_transposed_bytes278 .. automethod:: decode_transposed_bytes279 280 ..281 .. automethod:: check_repair_unused282 .. automethod:: repair_unused283 284 Integers <-> Encoded Bytes285 ==========================286 The following methods allow encoding and decoding287 unsigned integers to and from the engine's specific base64 variant.288 Endianess is determined by the engine's ``big`` constructor keyword.289 290 .. automethod:: encode_int6291 .. automethod:: decode_int6292 293 .. automethod:: encode_int12294 .. automethod:: decode_int12295 296 .. automethod:: encode_int24297 .. automethod:: decode_int24298 299 .. automethod:: encode_int64300 .. automethod:: decode_int64301 302 Informational Attributes303 ========================304 .. attribute:: charmap305 306 unicode string containing list of characters used in encoding;307 position in string matches 6bit value of character.308 309 .. attribute:: bytemap310 311 bytes version of :attr:`charmap`312 313 .. attribute:: big314 315 boolean flag indicating this using big-endian encoding.316 """317 318 #===================================================================319 # instance attrs320 #===================================================================321 # public config322 bytemap = None # charmap as bytes323 big = None # little or big endian324 325 # filled in by init based on charmap.326 # (byte elem: single byte under py2, 8bit int under py3)327 _encode64 = None # maps 6bit value -> byte elem328 _decode64 = None # maps byte elem -> 6bit value329 330 # helpers filled in by init based on endianness331 _encode_bytes = None # throws IndexError if bad value (shouldn't happen)332 _decode_bytes = None # throws KeyError if bad char.333 334 #===================================================================335 # init336 #===================================================================337 def __init__(self, charmap, big=False):338 # validate charmap, generate encode64/decode64 helper functions.339 if isinstance(charmap, unicode):340 charmap = charmap.encode("latin-1")341 elif not isinstance(charmap, bytes):342 raise exc.ExpectedStringError(charmap, "charmap")343 if len(charmap) != 64:344 raise ValueError("charmap must be 64 characters in length")345 if len(set(charmap)) != 64:346 raise ValueError("charmap must not contain duplicate characters")347 self.bytemap = charmap348 self._encode64 = charmap.__getitem__349 lookup = dict((value, idx) for idx, value in enumerate(charmap))350 self._decode64 = lookup.__getitem__351 352 # validate big, set appropriate helper functions.353 self.big = big354 if big:355 self._encode_bytes = self._encode_bytes_big356 self._decode_bytes = self._decode_bytes_big357 else:358 self._encode_bytes = self._encode_bytes_little359 self._decode_bytes = self._decode_bytes_little360 361 # TODO: support padding character362 ##if padding is not None:363 ## if isinstance(padding, unicode):364 ## padding = padding.encode("latin-1")365 ## elif not isinstance(padding, bytes):366 ## raise TypeError("padding char must be unicode or bytes")367 ## if len(padding) != 1:368 ## raise ValueError("padding must be single character")369 ##self.padding = padding370 371 @property372 def charmap(self):373 """charmap as unicode"""374 return self.bytemap.decode("latin-1")375 376 #===================================================================377 # encoding byte strings378 #===================================================================379 def encode_bytes(self, source):380 """encode bytes to base64 string.381 382 :arg source: byte string to encode.383 :returns: byte string containing encoded data.384 """385 if not isinstance(source, bytes):386 raise TypeError("source must be bytes, not %s" % (type(source),))387 chunks, tail = divmod(len(source), 3)388 if PY3:389 next_value = nextgetter(iter(source))390 else:391 next_value = nextgetter(ord(elem) for elem in source)392 gen = self._encode_bytes(next_value, chunks, tail)393 out = join_byte_elems(imap(self._encode64, gen))394 ##if tail:395 ## padding = self.padding396 ## if padding:397 ## out += padding * (3-tail)398 return out399 400 def _encode_bytes_little(self, next_value, chunks, tail):401 """helper used by encode_bytes() to handle little-endian encoding"""402 #403 # output bit layout:404 #405 # first byte: v1 543210406 #407 # second byte: v1 ....76408 # +v2 3210..409 #410 # third byte: v2 ..7654411 # +v3 10....412 #413 # fourth byte: v3 765432414 #415 idx = 0416 while idx < chunks:417 v1 = next_value()418 v2 = next_value()419 v3 = next_value()420 yield v1 & 0x3f421 yield ((v2 & 0x0f)<<2)|(v1>>6)422 yield ((v3 & 0x03)<<4)|(v2>>4)423 yield v3>>2424 idx += 1425 if tail:426 v1 = next_value()427 if tail == 1:428 # note: 4 msb of last byte are padding429 yield v1 & 0x3f430 yield v1>>6431 else:432 assert tail == 2433 # note: 2 msb of last byte are padding434 v2 = next_value()435 yield v1 & 0x3f436 yield ((v2 & 0x0f)<<2)|(v1>>6)437 yield v2>>4438 439 def _encode_bytes_big(self, next_value, chunks, tail):440 """helper used by encode_bytes() to handle big-endian encoding"""441 #442 # output bit layout:443 #444 # first byte: v1 765432445 #446 # second byte: v1 10....447 # +v2 ..7654448 #449 # third byte: v2 3210..450 # +v3 ....76451 #452 # fourth byte: v3 543210453 #454 idx = 0455 while idx < chunks:456 v1 = next_value()457 v2 = next_value()458 v3 = next_value()459 yield v1>>2460 yield ((v1&0x03)<<4)|(v2>>4)461 yield ((v2&0x0f)<<2)|(v3>>6)462 yield v3 & 0x3f463 idx += 1464 if tail:465 v1 = next_value()466 if tail == 1:467 # note: 4 lsb of last byte are padding468 yield v1>>2469 yield (v1&0x03)<<4470 else:471 assert tail == 2472 # note: 2 lsb of last byte are padding473 v2 = next_value()474 yield v1>>2475 yield ((v1&0x03)<<4)|(v2>>4)476 yield ((v2&0x0f)<<2)477 478 #===================================================================479 # decoding byte strings480 #===================================================================481 482 def decode_bytes(self, source):483 """decode bytes from base64 string.484 485 :arg source: byte string to decode.486 :returns: byte string containing decoded data.487 """488 if not isinstance(source, bytes):489 raise TypeError("source must be bytes, not %s" % (type(source),))490 ##padding = self.padding491 ##if padding:492 ## # TODO: add padding size check?493 ## source = source.rstrip(padding)494 chunks, tail = divmod(len(source), 4)495 if tail == 1:496 # only 6 bits left, can't encode a whole byte!497 raise ValueError("input string length cannot be == 1 mod 4")498 next_value = nextgetter(imap(self._decode64, source))499 try:500 return join_byte_values(self._decode_bytes(next_value, chunks, tail))501 except KeyError as err:502 raise ValueError("invalid character: %r" % (err.args[0],))503 504 def _decode_bytes_little(self, next_value, chunks, tail):505 """helper used by decode_bytes() to handle little-endian encoding"""506 #507 # input bit layout:508 #509 # first byte: v1 ..543210510 # +v2 10......511 #512 # second byte: v2 ....5432513 # +v3 3210....514 #515 # third byte: v3 ......54516 # +v4 543210..517 #518 idx = 0519 while idx < chunks:520 v1 = next_value()521 v2 = next_value()522 v3 = next_value()523 v4 = next_value()524 yield v1 | ((v2 & 0x3) << 6)525 yield (v2>>2) | ((v3 & 0xF) << 4)526 yield (v3>>4) | (v4<<2)527 idx += 1528 if tail:529 # tail is 2 or 3530 v1 = next_value()531 v2 = next_value()532 yield v1 | ((v2 & 0x3) << 6)533 # NOTE: if tail == 2, 4 msb of v2 are ignored (should be 0)534 if tail == 3:535 # NOTE: 2 msb of v3 are ignored (should be 0)536 v3 = next_value()537 yield (v2>>2) | ((v3 & 0xF) << 4)538 539 def _decode_bytes_big(self, next_value, chunks, tail):540 """helper used by decode_bytes() to handle big-endian encoding"""541 #542 # input bit layout:543 #544 # first byte: v1 543210..545 # +v2 ......54546 #547 # second byte: v2 3210....548 # +v3 ....5432549 #550 # third byte: v3 10......551 # +v4 ..543210552 #553 idx = 0554 while idx < chunks:555 v1 = next_value()556 v2 = next_value()557 v3 = next_value()558 v4 = next_value()559 yield (v1<<2) | (v2>>4)560 yield ((v2&0xF)<<4) | (v3>>2)561 yield ((v3&0x3)<<6) | v4562 idx += 1563 if tail:564 # tail is 2 or 3565 v1 = next_value()566 v2 = next_value()567 yield (v1<<2) | (v2>>4)568 # NOTE: if tail == 2, 4 lsb of v2 are ignored (should be 0)569 if tail == 3:570 # NOTE: 2 lsb of v3 are ignored (should be 0)571 v3 = next_value()572 yield ((v2&0xF)<<4) | (v3>>2)573 574 #===================================================================575 # encode/decode helpers576 #===================================================================577 578 # padmap2/3 - dict mapping last char of string ->579 # equivalent char with no padding bits set.580 581 def __make_padset(self, bits):582 """helper to generate set of valid last chars & bytes"""583 pset = set(c for i,c in enumerate(self.bytemap) if not i & bits)584 pset.update(c for i,c in enumerate(self.charmap) if not i & bits)585 return frozenset(pset)586 587 @memoized_property588 def _padinfo2(self):589 """mask to clear padding bits, and valid last bytes (for strings 2 % 4)"""590 # 4 bits of last char unused (lsb for big, msb for little)591 bits = 15 if self.big else (15<<2)592 return ~bits, self.__make_padset(bits)593 594 @memoized_property595 def _padinfo3(self):596 """mask to clear padding bits, and valid last bytes (for strings 3 % 4)"""597 # 2 bits of last char unused (lsb for big, msb for little)598 bits = 3 if self.big else (3<<4)599 return ~bits, self.__make_padset(bits)600 601 def check_repair_unused(self, source):602 """helper to detect & clear invalid unused bits in last character.603 604 :arg source:605 encoded data (as ascii bytes or unicode).606 607 :returns:608 `(True, result)` if the string was repaired,609 `(False, source)` if the string was ok as-is.610 """611 # figure out how many padding bits there are in last char.612 tail = len(source) & 3613 if tail == 2:614 mask, padset = self._padinfo2615 elif tail == 3:616 mask, padset = self._padinfo3617 elif not tail:618 return False, source619 else:620 raise ValueError("source length must != 1 mod 4")621 622 # check if last char is ok (padset contains bytes & unicode versions)623 last = source[-1]624 if last in padset:625 return False, source626 627 # we have dirty bits - repair the string by decoding last char,628 # clearing the padding bits via <mask>, and encoding new char.629 if isinstance(source, unicode):630 cm = self.charmap631 last = cm[cm.index(last) & mask]632 assert last in padset, "failed to generate valid padding char"633 else:634 # NOTE: this assumes ascii-compat encoding, and that635 # all chars used by encoding are 7-bit ascii.636 last = self._encode64(self._decode64(last) & mask)637 assert last in padset, "failed to generate valid padding char"638 if PY3:639 last = bytes([last])640 return True, source[:-1] + last641 642 def repair_unused(self, source):643 return self.check_repair_unused(source)[1]644 645 ##def transcode(self, source, other):646 ## return ''.join(647 ## other.charmap[self.charmap.index(char)]648 ## for char in source649 ## )650 651 ##def random_encoded_bytes(self, size, random=None, unicode=False):652 ## "return random encoded string of given size"653 ## data = getrandstr(random or rng,654 ## self.charmap if unicode else self.bytemap, size)655 ## return self.repair_unused(data)656 657 #===================================================================658 # transposed encoding/decoding659 #===================================================================660 def encode_transposed_bytes(self, source, offsets):661 """encode byte string, first transposing source using offset list"""662 if not isinstance(source, bytes):663 raise TypeError("source must be bytes, not %s" % (type(source),))664 tmp = join_byte_elems(source[off] for off in offsets)665 return self.encode_bytes(tmp)666 667 def decode_transposed_bytes(self, source, offsets):668 """decode byte string, then reverse transposition described by offset list"""669 # NOTE: if transposition does not use all bytes of source,670 # the original can't be recovered... and join_byte_elems() will throw671 # an error because 1+ values in <buf> will be None.672 tmp = self.decode_bytes(source)673 buf = [None] * len(offsets)674 for off, char in zip(offsets, tmp):675 buf[off] = char676 return join_byte_elems(buf)677 678 #===================================================================679 # integer decoding helpers - mainly used by des_crypt family680 #===================================================================681 def _decode_int(self, source, bits):682 """decode base64 string -> integer683 684 :arg source: base64 string to decode.685 :arg bits: number of bits in resulting integer.686 687 :raises ValueError:688 * if the string contains invalid base64 characters.689 * if the string is not long enough - it must be at least690 ``int(ceil(bits/6))`` in length.691 692 :returns:693 a integer in the range ``0 <= n < 2**bits``694 """695 if not isinstance(source, bytes):696 raise TypeError("source must be bytes, not %s" % (type(source),))697 big = self.big698 pad = -bits % 6699 chars = (bits+pad)/6700 if len(source) != chars:701 raise ValueError("source must be %d chars" % (chars,))702 decode = self._decode64703 out = 0704 try:705 for c in source if big else reversed(source):706 out = (out<<6) + decode(c)707 except KeyError:708 raise ValueError("invalid character in string: %r" % (c,))709 if pad:710 # strip padding bits711 if big:712 out >>= pad713 else:714 out &= (1<<bits)-1715 return out716 717 #---------------------------------------------------------------718 # optimized versions for common integer sizes719 #---------------------------------------------------------------720 721 def decode_int6(self, source):722 """decode single character -> 6 bit integer"""723 if not isinstance(source, bytes):724 raise TypeError("source must be bytes, not %s" % (type(source),))725 if len(source) != 1:726 raise ValueError("source must be exactly 1 byte")727 if PY3:728 # convert to 8bit int before doing lookup729 source = source[0]730 try:731 return self._decode64(source)732 except KeyError:733 raise ValueError("invalid character")734 735 def decode_int12(self, source):736 """decodes 2 char string -> 12-bit integer"""737 if not isinstance(source, bytes):738 raise TypeError("source must be bytes, not %s" % (type(source),))739 if len(source) != 2:740 raise ValueError("source must be exactly 2 bytes")741 decode = self._decode64742 try:743 if self.big:744 return decode(source[1]) + (decode(source[0])<<6)745 else:746 return decode(source[0]) + (decode(source[1])<<6)747 except KeyError:748 raise ValueError("invalid character")749 750 def decode_int24(self, source):751 """decodes 4 char string -> 24-bit integer"""752 if not isinstance(source, bytes):753 raise TypeError("source must be bytes, not %s" % (type(source),))754 if len(source) != 4:755 raise ValueError("source must be exactly 4 bytes")756 decode = self._decode64757 try:758 if self.big:759 return decode(source[3]) + (decode(source[2])<<6)+ \760 (decode(source[1])<<12) + (decode(source[0])<<18)761 else:762 return decode(source[0]) + (decode(source[1])<<6)+ \763 (decode(source[2])<<12) + (decode(source[3])<<18)764 except KeyError:765 raise ValueError("invalid character")766 767 def decode_int30(self, source):768 """decode 5 char string -> 30 bit integer"""769 return self._decode_int(source, 30)770 771 def decode_int64(self, source):772 """decode 11 char base64 string -> 64-bit integer773 774 this format is used primarily by des-crypt & variants to encode775 the DES output value used as a checksum.776 """777 return self._decode_int(source, 64)778 779 #===================================================================780 # integer encoding helpers - mainly used by des_crypt family781 #===================================================================782 def _encode_int(self, value, bits):783 """encode integer into base64 format784 785 :arg value: non-negative integer to encode786 :arg bits: number of bits to encode787 788 :returns:789 a string of length ``int(ceil(bits/6.0))``.790 """791 assert value >= 0, "caller did not sanitize input"792 pad = -bits % 6793 bits += pad794 if self.big:795 itr = irange(bits-6, -6, -6)796 # shift to add lsb padding.797 value <<= pad798 else:799 itr = irange(0, bits, 6)800 # padding is msb, so no change needed.801 return join_byte_elems(imap(self._encode64,802 ((value>>off) & 0x3f for off in itr)))803 804 #---------------------------------------------------------------805 # optimized versions for common integer sizes806 #---------------------------------------------------------------807 808 def encode_int6(self, value):809 """encodes 6-bit integer -> single hash64 character"""810 if value < 0 or value > 63:811 raise ValueError("value out of range")812 if PY3:813 return self.bytemap[value:value+1]814 else:815 return self._encode64(value)816 817 def encode_int12(self, value):818 """encodes 12-bit integer -> 2 char string"""819 if value < 0 or value > 0xFFF:820 raise ValueError("value out of range")821 raw = [value & 0x3f, (value>>6) & 0x3f]822 if self.big:823 raw = reversed(raw)824 return join_byte_elems(imap(self._encode64, raw))825 826 def encode_int24(self, value):827 """encodes 24-bit integer -> 4 char string"""828 if value < 0 or value > 0xFFFFFF:829 raise ValueError("value out of range")830 raw = [value & 0x3f, (value>>6) & 0x3f,831 (value>>12) & 0x3f, (value>>18) & 0x3f]832 if self.big:833 raw = reversed(raw)834 return join_byte_elems(imap(self._encode64, raw))835 836 def encode_int30(self, value):837 """decode 5 char string -> 30 bit integer"""838 if value < 0 or value > 0x3fffffff:839 raise ValueError("value out of range")840 return self._encode_int(value, 30)841 842 def encode_int64(self, value):843 """encode 64-bit integer -> 11 char hash64 string844 845 this format is used primarily by des-crypt & variants to encode846 the DES output value used as a checksum.847 """848 if value < 0 or value > 0xffffffffffffffff:849 raise ValueError("value out of range")850 return self._encode_int(value, 64)851 852 #===================================================================853 # eof854 #===================================================================855 856class LazyBase64Engine(Base64Engine):857 """Base64Engine which delays initialization until it's accessed"""858 _lazy_opts = None859 860 def __init__(self, *args, **kwds):861 self._lazy_opts = (args, kwds)862 863 def _lazy_init(self):864 args, kwds = self._lazy_opts865 super(LazyBase64Engine, self).__init__(*args, **kwds)866 del self._lazy_opts867 self.__class__ = Base64Engine868 869 def __getattribute__(self, attr):870 if not attr.startswith("_"):871 self._lazy_init()872 return object.__getattribute__(self, attr)873 874#-------------------------------------------------------------875# common variants876#-------------------------------------------------------------877 878h64 = LazyBase64Engine(HASH64_CHARS)879h64big = LazyBase64Engine(HASH64_CHARS, big=True)880bcrypt64 = LazyBase64Engine(BCRYPT_CHARS, big=True)881 882#=============================================================================883# eof884#=============================================================================885 