codekingpro/portable-devtools
114k
1"""passlib.handlers.fshp2"""3 4#=============================================================================5# imports6#=============================================================================7# core8from base64 import b64encode, b64decode9import re10import logging; log = logging.getLogger(__name__)11# site12# pkg13from passlib.utils import to_unicode14import passlib.utils.handlers as uh15from passlib.utils.compat import bascii_to_str, iteritems, u,\16 unicode17from passlib.crypto.digest import pbkdf118# local19__all__ = [20 'fshp',21]22#=============================================================================23# sha1-crypt24#=============================================================================25class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):26 """This class implements the FSHP password hash, and follows the :ref:`password-hash-api`.27 28 It supports a variable-length salt, and a variable number of rounds.29 30 The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:31 32 :param salt:33 Optional raw salt string.34 If not specified, one will be autogenerated (this is recommended).35 36 :param salt_size:37 Optional number of bytes to use when autogenerating new salts.38 Defaults to 16 bytes, but can be any non-negative value.39 40 :param rounds:41 Optional number of rounds to use.42 Defaults to 480000, must be between 1 and 4294967295, inclusive.43 44 :param variant:45 Optionally specifies variant of FSHP to use.46 47 * ``0`` - uses SHA-1 digest (deprecated).48 * ``1`` - uses SHA-2/256 digest (default).49 * ``2`` - uses SHA-2/384 digest.50 * ``3`` - uses SHA-2/512 digest.51 52 :type relaxed: bool53 :param relaxed:54 By default, providing an invalid value for one of the other55 keywords will result in a :exc:`ValueError`. If ``relaxed=True``,56 and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`57 will be issued instead. Correctable errors include ``rounds``58 that are too small or too large, and ``salt`` strings that are too long.59 60 .. versionadded:: 1.661 """62 63 #===================================================================64 # class attrs65 #===================================================================66 #--GenericHandler--67 name = "fshp"68 setting_kwds = ("salt", "salt_size", "rounds", "variant")69 checksum_chars = uh.PADDED_BASE64_CHARS70 ident = u("{FSHP")71 # checksum_size is property() that depends on variant72 73 #--HasRawSalt--74 default_salt_size = 16 # current passlib default, FSHP uses 875 max_salt_size = None76 77 #--HasRounds--78 # FIXME: should probably use different default rounds79 # based on the variant. setting for default variant (sha256) for now.80 default_rounds = 480000 # current passlib default, FSHP uses 409681 min_rounds = 1 # set by FSHP82 max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP83 rounds_cost = "linear"84 85 #--variants--86 default_variant = 187 _variant_info = {88 # variant: (hash name, digest size)89 0: ("sha1", 20),90 1: ("sha256", 32),91 2: ("sha384", 48),92 3: ("sha512", 64),93 }94 _variant_aliases = dict(95 [(unicode(k),k) for k in _variant_info] +96 [(v[0],k) for k,v in iteritems(_variant_info)]97 )98 99 #===================================================================100 # configuration101 #===================================================================102 @classmethod103 def using(cls, variant=None, **kwds):104 subcls = super(fshp, cls).using(**kwds)105 if variant is not None:106 subcls.default_variant = cls._norm_variant(variant)107 return subcls108 109 #===================================================================110 # instance attrs111 #===================================================================112 variant = None113 114 #===================================================================115 # init116 #===================================================================117 def __init__(self, variant=None, **kwds):118 # NOTE: variant must be set first, since it controls checksum size, etc.119 self.use_defaults = kwds.get("use_defaults") # load this early120 if variant is not None:121 variant = self._norm_variant(variant)122 elif self.use_defaults:123 variant = self.default_variant124 assert self._norm_variant(variant) == variant, "invalid default variant: %r" % (variant,)125 else:126 raise TypeError("no variant specified")127 self.variant = variant128 super(fshp, self).__init__(**kwds)129 130 @classmethod131 def _norm_variant(cls, variant):132 if isinstance(variant, bytes):133 variant = variant.decode("ascii")134 if isinstance(variant, unicode):135 try:136 variant = cls._variant_aliases[variant]137 except KeyError:138 raise ValueError("invalid fshp variant")139 if not isinstance(variant, int):140 raise TypeError("fshp variant must be int or known alias")141 if variant not in cls._variant_info:142 raise ValueError("invalid fshp variant")143 return variant144 145 @property146 def checksum_alg(self):147 return self._variant_info[self.variant][0]148 149 @property150 def checksum_size(self):151 return self._variant_info[self.variant][1]152 153 #===================================================================154 # formatting155 #===================================================================156 157 _hash_regex = re.compile(u(r"""158 ^159 \{FSHP160 (\d+)\| # variant161 (\d+)\| # salt size162 (\d+)\} # rounds163 ([a-zA-Z0-9+/]+={0,3}) # digest164 $"""), re.X)165 166 @classmethod167 def from_string(cls, hash):168 hash = to_unicode(hash, "ascii", "hash")169 m = cls._hash_regex.match(hash)170 if not m:171 raise uh.exc.InvalidHashError(cls)172 variant, salt_size, rounds, data = m.group(1,2,3,4)173 variant = int(variant)174 salt_size = int(salt_size)175 rounds = int(rounds)176 try:177 data = b64decode(data.encode("ascii"))178 except TypeError:179 raise uh.exc.MalformedHashError(cls)180 salt = data[:salt_size]181 chk = data[salt_size:]182 return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)183 184 def to_string(self):185 chk = self.checksum186 salt = self.salt187 data = bascii_to_str(b64encode(salt+chk))188 return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)189 190 #===================================================================191 # backend192 #===================================================================193 194 def _calc_checksum(self, secret):195 if isinstance(secret, unicode):196 secret = secret.encode("utf-8")197 # NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed.198 # this has only a minimal impact on security,199 # but it is worth noting this deviation.200 return pbkdf1(201 digest=self.checksum_alg,202 secret=self.salt,203 salt=secret,204 rounds=self.rounds,205 keylen=self.checksum_size,206 )207 208 #===================================================================209 # eoc210 #===================================================================211 212#=============================================================================213# eof214#=============================================================================215 