def _encode_bytes_helper(source): #FIXME: do something much more efficient here. # can't quite just use base64 and then translate chars, # since this scheme is little-endian. end = len(source) tail = end % 3 end -= tail idx = 0 while idx < end: v1 = bord(source[idx]) v2 = bord(source[idx+1]) v3 = bord(source[idx+2]) yield encode_int24(v1 + (v2<<8) + (v3<<16)) idx += 3 if tail: v1 = bord(source[idx]) if tail == 1: #NOTE: 4 msb of int are always 0 yield encode_int12(v1) else: #NOTE: 2 msb of int are always 0 v2 = bord(source[idx+1]) yield encode_int18(v1 + (v2<<8))
def genhash(cls, secret, config): if config is not None and not cls.identify(config): raise ValueError("not a mysql-3.2.3 hash") #FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") MASK_32 = 0xffffffff MASK_31 = 0x7fffffff nr1 = 0x50305735 nr2 = 0x12345671 add = 7 for c in secret: if c in b(' \t'): continue tmp = bord(c) nr1 ^= ((((nr1 & 63) + add) * tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2 + ((nr2 << 8) ^ nr1)) & MASK_32 add = (add + tmp) & MASK_32 hash = u"%08x%08x" % (nr1 & MASK_31, nr2 & MASK_31) return to_hash_str(hash)
def genhash(cls, secret, config): if config is not None and not cls.identify(config): raise ValueError("not a mysql-3.2.3 hash") #FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") MASK_32 = 0xffffffff MASK_31 = 0x7fffffff nr1 = 0x50305735 nr2 = 0x12345671 add = 7 for c in secret: if c in b(' \t'): continue tmp = bord(c) nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32 add = (add+tmp) & MASK_32 hash = u"%08x%08x" % (nr1 & MASK_31, nr2 & MASK_31) return to_hash_str(hash)
def _crypt_secret_to_key(secret): "crypt helper which converts lower 7 bits of first 8 chars of secret -> 56-bit des key, padded to 64 bits" return sum( (bord(c) & 0x7f) << (57-8*i) for i, c in enumerate(secret[:8]) )
def raw_sun_md5_crypt(secret, rounds, salt): "given secret & salt, return encoded sun-md5-crypt checksum" global MAGIC_HAMLET assert isinstance(secret, bytes) assert isinstance(salt, bytes) # validate rounds if rounds <= 0: rounds = 0 real_rounds = 4096 + rounds # NOTE: spec seems to imply max 'rounds' is 2**32-1 # generate initial digest to start off round 0. # NOTE: algorithm 'salt' includes full config string w/ trailing "$" result = md5(secret + salt).digest() assert len(result) == 16 # NOTE: many things have been inlined to speed up the loop as much as possible, # so that this only barely resembles the algorithm as described in the docs. # * all accesses to a given bit have been inlined using the formula # rbitval(bit) = (rval((bit>>3) & 15) >> (bit & 7)) & 1 # * the calculation of coinflip value R has been inlined # * the conditional division of coinflip value V has been inlined as a shift right of 0 or 1. # * the i, i+3, etc iterations are precalculated in lists. # * the round-based conditional division of x & y is now performed # by choosing an appropriate precalculated list, so only the 7 used bits # are actually calculated X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS # NOTE: % appears to be *slightly* slower than &, so we prefer & if possible round = 0 while round < real_rounds: # convert last result byte string to list of byte-ints for easy access rval = [bord(c) for c in result].__getitem__ # build up X bit by bit x = 0 xrounds = X_ROUNDS_1 if (rval((round >> 3) & 15) >> (round & 7)) & 1 else X_ROUNDS_0 for i, ia, ib in xrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b >> (a & 7)) & 1) x |= ((rval((v >> 3) & 15) >> (v & 7)) & 1) << i # build up Y bit by bit y = 0 yrounds = Y_ROUNDS_1 if (rval(((round + 64) >> 3) & 15) >> (round & 7)) & 1 else Y_ROUNDS_0 for i, ia, ib in yrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b >> (a & 7)) & 1) y |= ((rval((v >> 3) & 15) >> (v & 7)) & 1) << i # extract x'th and y'th bit, xoring them together to yeild "coin flip" coin = ((rval(x >> 3) >> (x & 7)) ^ (rval(y >> 3) >> (y & 7))) & 1 # construct hash for this round h = md5(result) if coin: h.update(MAGIC_HAMLET) h.update(unicode(round).encode("ascii")) result = h.digest() round += 1 # encode output return h64.encode_transposed_bytes(result, _chk_offsets)
def raw_sun_md5_crypt(secret, rounds, salt): "given secret & salt, return encoded sun-md5-crypt checksum" global MAGIC_HAMLET assert isinstance(secret, bytes) assert isinstance(salt, bytes) #validate rounds if rounds <= 0: rounds = 0 real_rounds = 4096 + rounds #NOTE: spec seems to imply max 'rounds' is 2**32-1 #generate initial digest to start off round 0. #NOTE: algorithm 'salt' includes full config string w/ trailing "$" result = md5(secret + salt).digest() assert len(result) == 16 #NOTE: many things have been inlined to speed up the loop as much as possible, # so that this only barely resembles the algorithm as described in the docs. # * all accesses to a given bit have been inlined using the formula # rbitval(bit) = (rval((bit>>3) & 15) >> (bit & 7)) & 1 # * the calculation of coinflip value R has been inlined # * the conditional division of coinflip value V has been inlined as a shift right of 0 or 1. # * the i, i+3, etc iterations are precalculated in lists. # * the round-based conditional division of x & y is now performed # by choosing an appropriate precalculated list, so only the 7 used bits # are actually calculated X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS #NOTE: % appears to be *slightly* slower than &, so we prefer & if possible round = 0 while round < real_rounds: #convert last result byte string to list of byte-ints for easy access rval = [ bord(c) for c in result ].__getitem__ #build up X bit by bit x = 0 xrounds = X_ROUNDS_1 if (rval((round>>3) & 15)>>(round & 7)) & 1 else X_ROUNDS_0 for i, ia, ib in xrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1) x |= ((rval((v>>3)&15)>>(v&7))&1) << i #build up Y bit by bit y = 0 yrounds = Y_ROUNDS_1 if (rval(((round+64)>>3) & 15)>>(round & 7)) & 1 else Y_ROUNDS_0 for i, ia, ib in yrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1) y |= ((rval((v>>3)&15)>>(v&7))&1) << i #extract x'th and y'th bit, xoring them together to yeild "coin flip" coin = ((rval(x>>3) >> (x&7)) ^ (rval(y>>3) >> (y&7))) & 1 #construct hash for this round h = md5(result) if coin: h.update(MAGIC_HAMLET) h.update(unicode(round).encode("ascii")) result = h.digest() round += 1 #encode output return h64.encode_transposed_bytes(result, _chk_offsets)
def raw_sha_crypt(secret, salt, rounds, hash): """perform raw sha crypt :arg secret: password to encode (if unicode, encoded to utf-8) :arg salt: salt string to use (required) :arg rounds: int rounds :arg hash: hash constructor function for 256/512 variant :returns: Returns tuple of ``(unencoded checksum, normalized salt, normalized rounds)``. """ #validate secret if not isinstance(secret, bytes): raise TypeError("secret must be encoded as bytes") #validate rounds if rounds < 1000: rounds = 1000 if rounds > 999999999: #pragma: no cover rounds = 999999999 #validate salt if not isinstance(salt, bytes): raise TypeError("salt must be encoded as bytes") if any(c in salt for c in INVALID_SALT_VALUES): raise ValueError("invalid chars in salt") if len(salt) > 16: salt = salt[:16] #init helpers def extend(source, size_ref): "helper which repeats <source> digest string until it's the same length as <size_ref> string" assert len(source) == chunk_size size = len(size_ref) return source * int(size/chunk_size) + source[:size % chunk_size] #calc digest B b = hash(secret) chunk_size = b.digest_size #grab this once hash is created b.update(salt) a = b.copy() #make a copy to save a little time later b.update(secret) b_result = b.digest() b_extend = extend(b_result, secret) #begin digest A #a = hash(secret) <- performed above #a.update(salt) <- performed above a.update(b_extend) #for each bit in slen, add B or SECRET value = len(secret) while value > 0: if value % 2: a.update(b_result) else: a.update(secret) value >>= 1 #finish A a_result = a.digest() #calc DP - hash of password, extended to size of password dp = hash(secret * len(secret)) dp_result = extend(dp.digest(), secret) #calc DS - hash of salt, extended to size of salt ds = hash(salt * (16+bord(a_result[0]))) ds_result = extend(ds.digest(), salt) #aka 'S' # #calc digest C #NOTE: this has been contorted a little to allow pre-computing #some of the hashes. the original algorithm was that #each round generates digest composed of: # if round%2>0 => dp else lr # if round%3>0 => ds # if round%7>0 => dp # if round%2>0 => lr else dp #where lr is digest of the last round's hash (initially = a_result) # #pre-calculate some digests to speed up odd rounds dp_hash = hash(dp_result).copy dp_ds_hash = hash(dp_result + ds_result).copy dp_dp_hash = hash(dp_result * 2).copy dp_ds_dp_hash = hash(dp_result + ds_result + dp_result).copy #pre-calculate some strings to speed up even rounds ds_dp_result = ds_result + dp_result dp_dp_result = dp_result * 2 ds_dp_dp_result = ds_result + dp_dp_result #run through rounds last_result = a_result i = 0 while i < rounds: if i % 2: if i % 3: if i % 7: c = dp_ds_dp_hash() else: c = dp_ds_hash() elif i % 7: c = dp_dp_hash() else: c = dp_hash() c.update(last_result) else: c = hash(last_result) if i % 3: if i % 7: c.update(ds_dp_dp_result) else: c.update(ds_dp_result) elif i % 7: c.update(dp_dp_result) else: c.update(dp_result) last_result = c.digest() i += 1 #return unencoded result, along w/ normalized config values return last_result, salt, rounds
def iter_bits(source): for c in source: v = bord(c) for i in xrange(7,-1,-1): yield (v>>i) & 1
def raw_sha_crypt(secret, salt, rounds, hash): """perform raw sha crypt :arg secret: password to encode (if unicode, encoded to utf-8) :arg salt: salt string to use (required) :arg rounds: int rounds :arg hash: hash constructor function for 256/512 variant :returns: Returns tuple of ``(unencoded checksum, normalized salt, normalized rounds)``. """ #validate secret if not isinstance(secret, bytes): raise TypeError("secret must be encoded as bytes") #validate rounds if rounds < 1000: rounds = 1000 if rounds > 999999999: #pragma: no cover rounds = 999999999 #validate salt if not isinstance(salt, bytes): raise TypeError("salt must be encoded as bytes") if any(c in salt for c in INVALID_SALT_VALUES): raise ValueError("invalid chars in salt") if len(salt) > 16: salt = salt[:16] #init helpers def extend(source, size_ref): "helper which repeats <source> digest string until it's the same length as <size_ref> string" assert len(source) == chunk_size size = len(size_ref) return source * int(size / chunk_size) + source[:size % chunk_size] #calc digest B b = hash(secret) chunk_size = b.digest_size #grab this once hash is created b.update(salt) a = b.copy() #make a copy to save a little time later b.update(secret) b_result = b.digest() b_extend = extend(b_result, secret) #begin digest A #a = hash(secret) <- performed above #a.update(salt) <- performed above a.update(b_extend) #for each bit in slen, add B or SECRET value = len(secret) while value > 0: if value % 2: a.update(b_result) else: a.update(secret) value >>= 1 #finish A a_result = a.digest() #calc DP - hash of password, extended to size of password dp = hash(secret * len(secret)) dp_result = extend(dp.digest(), secret) #calc DS - hash of salt, extended to size of salt ds = hash(salt * (16 + bord(a_result[0]))) ds_result = extend(ds.digest(), salt) #aka 'S' # #calc digest C #NOTE: this has been contorted a little to allow pre-computing #some of the hashes. the original algorithm was that #each round generates digest composed of: # if round%2>0 => dp else lr # if round%3>0 => ds # if round%7>0 => dp # if round%2>0 => lr else dp #where lr is digest of the last round's hash (initially = a_result) # #pre-calculate some digests to speed up odd rounds dp_hash = hash(dp_result).copy dp_ds_hash = hash(dp_result + ds_result).copy dp_dp_hash = hash(dp_result * 2).copy dp_ds_dp_hash = hash(dp_result + ds_result + dp_result).copy #pre-calculate some strings to speed up even rounds ds_dp_result = ds_result + dp_result dp_dp_result = dp_result * 2 ds_dp_dp_result = ds_result + dp_dp_result #run through rounds last_result = a_result i = 0 while i < rounds: if i % 2: if i % 3: if i % 7: c = dp_ds_dp_hash() else: c = dp_ds_hash() elif i % 7: c = dp_dp_hash() else: c = dp_hash() c.update(last_result) else: c = hash(last_result) if i % 3: if i % 7: c.update(ds_dp_dp_result) else: c.update(ds_dp_result) elif i % 7: c.update(dp_dp_result) else: c.update(dp_result) last_result = c.digest() i += 1 #return unencoded result, along w/ normalized config values return last_result, salt, rounds