def raw_sun_md5_crypt(secret, rounds, salt): global MAGIC_HAMLET if rounds <= 0: rounds = 0 real_rounds = 4096 + rounds result = md5(secret + salt).digest() X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS round = 0 while round < real_rounds: rval = [ byte_elem_value(c) for c in result ].__getitem__ 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 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 coin = (rval(x >> 3) >> (x & 7) ^ rval(y >> 3) >> (y & 7)) & 1 h = md5(result) if coin: h.update(MAGIC_HAMLET) h.update(unicode(round).encode('ascii')) result = h.digest() round += 1 return h64.encode_transposed_bytes(result, _chk_offsets)
def _sanitize(value, char=u('*')): if value is None: return if isinstance(value, bytes): from otp.ai.passlib.utils.binary import ab64_encode value = ab64_encode(value).decode('ascii') else: if not isinstance(value, unicode): value = unicode(value) size = len(value) clip = min(4, size // 8) return value[:clip] + char * (size - clip)
def render_mc3(ident, rounds, salt, checksum, sep=u('$'), rounds_base=10): if rounds is None: rounds = u('') else: if rounds_base == 16: rounds = u('%x') % rounds else: rounds = unicode(rounds) if checksum: parts = [ident, rounds, sep, salt, sep, checksum] else: parts = [ident, rounds, sep, salt] return uascii_to_str(join_unicode(parts))
def from_string(cls, hash): hash = to_unicode(hash, 'ascii', 'hash') if hash.startswith(u('$md5$')): rounds = 0 salt_idx = 5 else: if hash.startswith(u('$md5,rounds=')): idx = hash.find(u('$'), 12) if idx == -1: raise uh.exc.MalformedHashError(cls, 'unexpected end of rounds') rstr = hash[12:idx] try: rounds = int(rstr) except ValueError: raise uh.exc.MalformedHashError(cls, 'bad rounds') if rstr != unicode(rounds): raise uh.exc.ZeroPaddedRoundsError(cls) if rounds == 0: raise uh.exc.MalformedHashError(cls, 'explicit zero rounds') salt_idx = idx + 1 else: raise uh.exc.InvalidHashError(cls) chk_idx = hash.rfind(u('$'), salt_idx) if chk_idx == -1: salt = hash[salt_idx:] chk = None bare_salt = True else: if chk_idx == len(hash) - 1: if chk_idx > salt_idx and hash[(-2)] == u('$'): raise uh.exc.MalformedHashError(cls, "too many '$' separators") salt = hash[salt_idx:-1] chk = None bare_salt = False else: if chk_idx > 0 and hash[(chk_idx - 1)] == u('$'): salt = hash[salt_idx:chk_idx - 1] chk = hash[chk_idx + 1:] bare_salt = False else: salt = hash[salt_idx:chk_idx] chk = hash[chk_idx + 1:] bare_salt = True return cls(rounds=rounds, salt=salt, checksum=chk, bare_salt=bare_salt)
class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): name = 'fshp' setting_kwds = ('salt', 'salt_size', 'rounds', 'variant') checksum_chars = uh.PADDED_BASE64_CHARS ident = u('{FSHP') default_salt_size = 16 max_salt_size = None default_rounds = 480000 min_rounds = 1 max_rounds = 4294967295L rounds_cost = 'linear' default_variant = 1 _variant_info = {0: ('sha1', 20), 1: ('sha256', 32), 2: ('sha384', 48), 3: ('sha512', 64)} _variant_aliases = dict([ (unicode(k), k) for k in _variant_info ] + [ (v[0], k) for k, v in iteritems(_variant_info) ]) @classmethod def using(cls, variant=None, **kwds): subcls = super(fshp, cls).using(**kwds) if variant is not None: subcls.default_variant = cls._norm_variant(variant) return subcls variant = None def __init__(self, variant=None, **kwds): self.use_defaults = kwds.get('use_defaults') if variant is not None: variant = self._norm_variant(variant) else: if self.use_defaults: variant = self.default_variant else: raise TypeError('no variant specified') self.variant = variant super(fshp, self).__init__(**kwds) return @classmethod def _norm_variant(cls, variant): if isinstance(variant, bytes): variant = variant.decode('ascii') if isinstance(variant, unicode): try: variant = cls._variant_aliases[variant] except KeyError: raise ValueError('invalid fshp variant') if not isinstance(variant, int): raise TypeError('fshp variant must be int or known alias') if variant not in cls._variant_info: raise ValueError('invalid fshp variant') return variant @property def checksum_alg(self): return self._variant_info[self.variant][0] @property def checksum_size(self): return self._variant_info[self.variant][1] _hash_regex = re.compile(u('\n ^\n \\{FSHP\n (\\d+)\\| # variant\n (\\d+)\\| # salt size\n (\\d+)\\} # rounds\n ([a-zA-Z0-9+/]+={0,3}) # digest\n $'), re.X) @classmethod def from_string(cls, hash): hash = to_unicode(hash, 'ascii', 'hash') m = cls._hash_regex.match(hash) if not m: raise uh.exc.InvalidHashError(cls) variant, salt_size, rounds, data = m.group(1, 2, 3, 4) variant = int(variant) salt_size = int(salt_size) rounds = int(rounds) try: data = b64decode(data.encode('ascii')) except TypeError: raise uh.exc.MalformedHashError(cls) salt = data[:salt_size] chk = data[salt_size:] return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant) def to_string(self): chk = self.checksum salt = self.salt data = bascii_to_str(b64encode(salt + chk)) return '{FSHP%d|%d|%d}%s' % (self.variant, len(salt), self.rounds, data) def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode('utf-8') return pbkdf1(digest=self.checksum_alg, secret=self.salt, salt=secret, rounds=self.rounds, keylen=self.checksum_size)