def update_settings(**kwds): """helper to update django settings from kwds""" for k,v in iteritems(kwds): if v is UNSET: if hasattr(settings, k): delattr(settings, k) else: setattr(settings, k, v)
def update_settings(**kwds): """helper to update django settings from kwds""" for k, v in iteritems(kwds): if v is UNSET: if hasattr(settings, k): delattr(settings, k) else: setattr(settings, k, v)
def check_all(self, strict=False): """run sanity check on all keys, issue warning if out of sync""" same = self._is_same_value for path, (orig, expected) in iteritems(self._state): if same(self._get_path(path), expected): continue msg = "another library has patched resource: %r" % path if strict: raise RuntimeError(msg) else: warn(msg, PasslibRuntimeWarning)
def safe_summary(self, encoded): from django.contrib.auth.hashers import mask_hash, _, SortedDict handler = self.passlib_handler items = [ # since this is user-facing, we're reporting passlib's name, # without the distracting PASSLIB_HASHER_PREFIX prepended. (_('algorithm'), handler.name), ] if hasattr(handler, "parsehash"): kwds = handler.parsehash(encoded, sanitize=mask_hash) for key, value in iteritems(kwds): key = self._translate_kwds.get(key, key) items.append((_(key), value)) return SortedDict(items)
def _norm_checksum(self, checksum): if checksum is None: return None for alg, digest in iteritems(checksum): if alg != norm_hash_name(alg, "iana"): raise ValueError("malformed algorithm name in scram hash: %r" % (alg,)) if len(alg) > 9: raise ValueError("SCRAM limits algorithm names to " "9 characters: %r" % (alg,)) if not isinstance(digest, bytes): raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests") # TODO: verify digest size (if digest is known) if "sha-1" not in checksum: # NOTE: required because of SCRAM spec. raise ValueError("sha-1 must be in algorithm list of scram hash") return checksum
def _norm_checksum(self, checksum, relaxed=False): if not isinstance(checksum, dict): raise uh.exc.ExpectedTypeError(checksum, "dict", "checksum") for alg, digest in iteritems(checksum): if alg != norm_hash_name(alg, 'iana'): raise ValueError("malformed algorithm name in scram hash: %r" % (alg,)) if len(alg) > 9: raise ValueError("SCRAM limits algorithm names to " "9 characters: %r" % (alg,)) if not isinstance(digest, bytes): raise uh.exc.ExpectedTypeError(digest, "raw bytes", "digests") # TODO: verify digest size (if digest is known) if 'sha-1' not in checksum: # NOTE: required because of SCRAM spec. raise ValueError("sha-1 must be in algorithm list of scram hash") return checksum
def verify(cls, secret, hash, full=False): uh.validate_secret(secret) self = cls.from_string(hash) chkmap = self.checksum if not chkmap: raise ValueError("expected %s hash, got %s config string instead" % (cls.name, cls.name)) # NOTE: to make the verify method efficient, we just calculate hash # of shortest digest by default. apps can pass in "full=True" to # check entire hash for consistency. if full: correct = failed = False for alg, digest in iteritems(chkmap): other = self._calc_checksum(secret, alg) # NOTE: could do this length check in norm_algs(), # but don't need to be that strict, and want to be able # to parse hashes containing algs not supported by platform. # it's fine if we fail here though. if len(digest) != len(other): raise ValueError( "mis-sized %s digest in scram hash: %r != %r" % (alg, len(digest), len(other))) if consteq(other, digest): correct = True else: failed = True if correct and failed: raise ValueError("scram hash verified inconsistently, " "may be corrupted") else: return correct else: # XXX: should this just always use sha1 hash? would be faster. # otherwise only verify against one hash, pick one w/ best security. for alg in self._verify_algs: if alg in chkmap: other = self._calc_checksum(secret, alg) return consteq(other, chkmap[alg]) # there should always be sha-1 at the very least, # or something went wrong inside _norm_algs() raise AssertionError("sha-1 digest not found!")
def verify(cls, secret, hash, full=False): uh.validate_secret(secret) self = cls.from_string(hash) chkmap = self.checksum if not chkmap: raise ValueError("expected %s hash, got %s config string instead" % (cls.name, cls.name)) # NOTE: to make the verify method efficient, we just calculate hash # of shortest digest by default. apps can pass in "full=True" to # check entire hash for consistency. if full: correct = failed = False for alg, digest in iteritems(chkmap): other = self._calc_checksum(secret, alg) # NOTE: could do this length check in norm_algs(), # but don't need to be that strict, and want to be able # to parse hashes containing algs not supported by platform. # it's fine if we fail here though. if len(digest) != len(other): raise ValueError("mis-sized %s digest in scram hash: %r != %r" % (alg, len(digest), len(other))) if consteq(other, digest): correct = True else: failed = True if correct and failed: raise ValueError("scram hash verified inconsistently, " "may be corrupted") else: return correct else: # XXX: should this just always use sha1 hash? would be faster. # otherwise only verify against one hash, pick one w/ best security. for alg in self._verify_algs: if alg in chkmap: other = self._calc_checksum(secret, alg) return consteq(other, chkmap[alg]) # there should always be sha-1 at the very least, # or something went wrong inside _norm_algs() raise AssertionError("sha-1 digest not found!")
def _iter_lines(self): "iterator yielding lines of database" return (self._render_record(key,value) for key,value in iteritems(self._records))
class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the FSHP password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords: :param salt: Optional raw salt string. If not specified, one will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any non-negative value. :param rounds: Optional number of rounds to use. Defaults to 480000, must be between 1 and 4294967295, inclusive. :param variant: Optionally specifies variant of FSHP to use. * ``0`` - uses SHA-1 digest (deprecated). * ``1`` - uses SHA-2/256 digest (default). * ``2`` - uses SHA-2/384 digest. * ``3`` - uses SHA-2/512 digest. :type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``rounds`` that are too small or too large, and ``salt`` strings that are too long. .. versionadded:: 1.6 """ #=================================================================== # class attrs #=================================================================== #--GenericHandler-- name = "fshp" setting_kwds = ("salt", "salt_size", "rounds", "variant") checksum_chars = uh.PADDED_BASE64_CHARS ident = u("{FSHP") # checksum_size is property() that depends on variant #--HasRawSalt-- default_salt_size = 16 # current passlib default, FSHP uses 8 max_salt_size = None #--HasRounds-- # FIXME: should probably use different default rounds # based on the variant. setting for default variant (sha256) for now. default_rounds = 480000 # current passlib default, FSHP uses 4096 min_rounds = 1 # set by FSHP max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP rounds_cost = "linear" #--variants-- default_variant = 1 _variant_info = { # variant: (hash name, digest size) 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)] ) #=================================================================== # configuration #=================================================================== @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 #=================================================================== # instance attrs #=================================================================== variant = None #=================================================================== # init #=================================================================== def __init__(self, variant=None, **kwds): # NOTE: variant must be set first, since it controls checksum size, etc. self.use_defaults = kwds.get("use_defaults") # load this early if variant is not None: variant = self._norm_variant(variant) elif self.use_defaults: variant = self.default_variant assert self._norm_variant(variant) == variant, "invalid default variant: %r" % (variant,) else: raise TypeError("no variant specified") self.variant = variant super(fshp, self).__init__(**kwds) @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] #=================================================================== # formatting #=================================================================== _hash_regex = re.compile(u(r""" ^ \{FSHP (\d+)\| # variant (\d+)\| # salt size (\d+)\} # rounds ([a-zA-Z0-9+/]+={0,3}) # digest $"""), 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) #=================================================================== # backend #=================================================================== def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") # NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed. # this has only a minimal impact on security, # but it is worth noting this deviation. return pbkdf1( digest=self.checksum_alg, secret=self.salt, salt=secret, rounds=self.rounds, keylen=self.checksum_size, )
def _iter_lines(self): """iterator yielding lines of database""" return (self._render_record(key, value) for key, value in iteritems(self._records))