1"""passlib.handlers.sha2_crypt - SHA256-Crypt / SHA512-Crypt"""
2#=============================================================================
3# imports
4#=============================================================================
5# core
6import hashlib
7import logging; log = logging.getLogger(__name__)
8# site
9# pkg
10from passlib.utils import safe_crypt, test_crypt, \
11                          repeat_string, to_unicode
12from passlib.utils.binary import h64
13from passlib.utils.compat import byte_elem_value, u, \
14                                 uascii_to_str, unicode
15import passlib.utils.handlers as uh
16# local
17__all__ = [
18    "sha512_crypt",
19    "sha256_crypt",
20]
21
22#=============================================================================
23# pure-python backend, used by both sha256_crypt & sha512_crypt
24# when crypt.crypt() backend is not available.
25#=============================================================================
26_BNULL = b'\x00'
27
28# pre-calculated offsets used to speed up C digest stage (see notes below).
29# sequence generated using the following:
30    ##perms_order = "p,pp,ps,psp,sp,spp".split(",")
31    ##def offset(i):
32    ##    key = (("p" if i % 2 else "") + ("s" if i % 3 else "") +
33    ##        ("p" if i % 7 else "") + ("" if i % 2 else "p"))
34    ##    return perms_order.index(key)
35    ##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)]
36_c_digest_offsets = (
37    (0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3),
38    (4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1),
39    (4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
40    )
41
42# map used to transpose bytes when encoding final sha256_crypt digest
43_256_transpose_map = (
44    20, 10,  0, 11,  1, 21,  2, 22, 12, 23, 13,  3, 14,  4, 24,  5,
45    25, 15, 26, 16,  6, 17,  7, 27,  8, 28, 18, 29, 19,  9, 30, 31,
46)
47
48# map used to transpose bytes when encoding final sha512_crypt digest
49_512_transpose_map = (
50    42, 21,  0,  1, 43, 22, 23,  2, 44, 45, 24,  3,  4, 46, 25, 26,
51     5, 47, 48, 27,  6,  7, 49, 28, 29,  8, 50, 51, 30,  9, 10, 52,
52    31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15,
53    16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63,
54)
55
56def _raw_sha2_crypt(pwd, salt, rounds, use_512=False):
57    """perform raw sha256-crypt / sha512-crypt
58
59    this function provides a pure-python implementation of the internals
60    for the SHA256-Crypt and SHA512-Crypt algorithms; it doesn't
61    handle any of the parsing/validation of the hash strings themselves.
62
63    :arg pwd: password chars/bytes to hash
64    :arg salt: salt chars to use
65    :arg rounds: linear rounds cost
66    :arg use_512: use sha512-crypt instead of sha256-crypt mode
67
68    :returns:
69        encoded checksum chars
70    """
71    #===================================================================
72    # init & validate inputs
73    #===================================================================
74
75    # NOTE: the setup portion of this algorithm scales ~linearly in time
76    #       with the size of the password, making it vulnerable to a DOS from
77    #       unreasonably large inputs. the following code has some optimizations
78    #       which would make things even worse, using O(pwd_len**2) memory
79    #       when calculating digest P.
80    #
81    #       to mitigate these two issues: 1) this code switches to a
82    #       O(pwd_len)-memory algorithm for passwords that are much larger
83    #       than average, and 2) Passlib enforces a library-wide max limit on
84    #       the size of passwords it will allow, to prevent this algorithm and
85    #       others from being DOSed in this way (see passlib.exc.PasswordSizeError
86    #       for details).
87
88    # validate secret
89    if isinstance(pwd, unicode):
90        # XXX: not sure what official unicode policy is, using this as default
91        pwd = pwd.encode("utf-8")
92    assert isinstance(pwd, bytes)
93    if _BNULL in pwd:
94        raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt)
95    pwd_len = len(pwd)
96
97    # validate rounds
98    assert 1000 <= rounds <= 999999999, "invalid rounds"
99        # NOTE: spec says out-of-range rounds should be clipped, instead of
100        # causing an error. this function assumes that's been taken care of
101        # by the handler class.
102
103    # validate salt
104    assert isinstance(salt, unicode), "salt not unicode"
105    salt = salt.encode("ascii")
106    salt_len = len(salt)
107    assert salt_len < 17, "salt too large"
108        # NOTE: spec says salts larger than 16 bytes should be truncated,
109        # instead of causing an error. this function assumes that's been
110        # taken care of by the handler class.
111
112    # load sha256/512 specific constants
113    if use_512:
114        hash_const = hashlib.sha512
115        transpose_map = _512_transpose_map
116    else:
117        hash_const = hashlib.sha256
118        transpose_map = _256_transpose_map
119
120    #===================================================================
121    # digest B - used as subinput to digest A
122    #===================================================================
123    db = hash_const(pwd + salt + pwd).digest()
124
125    #===================================================================
126    # digest A - used to initialize first round of digest C
127    #===================================================================
128    # start out with pwd + salt
129    a_ctx = hash_const(pwd + salt)
130    a_ctx_update = a_ctx.update
131
132    # add pwd_len bytes of b, repeating b as many times as needed.
133    a_ctx_update(repeat_string(db, pwd_len))
134
135    # for each bit in pwd_len: add b if it's 1, or pwd if it's 0
136    i = pwd_len
137    while i:
138        a_ctx_update(db if i & 1 else pwd)
139        i >>= 1
140
141    # finish A
142    da = a_ctx.digest()
143
144    #===================================================================
145    # digest P from password - used instead of password itself
146    #                          when calculating digest C.
147    #===================================================================
148    if pwd_len < 96:
149        # this method is faster under python, but uses O(pwd_len**2) memory;
150        # so we don't use it for larger passwords to avoid a potential DOS.
151        dp = repeat_string(hash_const(pwd * pwd_len).digest(), pwd_len)
152    else:
153        # this method is slower under python, but uses a fixed amount of memory.
154        tmp_ctx = hash_const(pwd)
155        tmp_ctx_update = tmp_ctx.update
156        i = pwd_len-1
157        while i:
158            tmp_ctx_update(pwd)
159            i -= 1
160        dp = repeat_string(tmp_ctx.digest(), pwd_len)
161    assert len(dp) == pwd_len
162
163    #===================================================================
164    # digest S  - used instead of salt itself when calculating digest C
165    #===================================================================
166    ds = hash_const(salt * (16 + byte_elem_value(da[0]))).digest()[:salt_len]
167    assert len(ds) == salt_len, "salt_len somehow > hash_len!"
168
169    #===================================================================
170    # digest C - for a variable number of rounds, combine A, S, and P
171    #            digests in various ways; in order to burn CPU time.
172    #===================================================================
173
174    # NOTE: the original SHA256/512-Crypt specification performs the C digest
175    # calculation using the following loop:
176    #
177    ##dc = da
178    ##i = 0
179    ##while i < rounds:
180    ##    tmp_ctx = hash_const(dp if i & 1 else dc)
181    ##    if i % 3:
182    ##        tmp_ctx.update(ds)
183    ##    if i % 7:
184    ##        tmp_ctx.update(dp)
185    ##    tmp_ctx.update(dc if i & 1 else dp)
186    ##    dc = tmp_ctx.digest()
187    ##    i += 1
188    #
189    # The code Passlib uses (below) implements an equivalent algorithm,
190    # it's just been heavily optimized to pre-calculate a large number
191    # of things beforehand. It works off of a couple of observations
192    # about the original algorithm:
193    #
194    # 1. each round is a combination of 'dc', 'ds', and 'dp'; determined
195    #    by the whether 'i' a multiple of 2,3, and/or 7.
196    # 2. since lcm(2,3,7)==42, the series of combinations will repeat
197    #    every 42 rounds.
198    # 3. even rounds 0-40 consist of 'hash(dc + round-specific-constant)';
199    #    while odd rounds 1-41 consist of hash(round-specific-constant + dc)
200    #
201    # Using these observations, the following code...
202    # * calculates the round-specific combination of ds & dp for each round 0-41
203    # * runs through as many 42-round blocks as possible
204    # * runs through as many pairs of rounds as possible for remaining rounds
205    # * performs once last round if the total rounds should be odd.
206    #
207    # this cuts out a lot of the control overhead incurred when running the
208    # original loop 40,000+ times in python, resulting in ~20% increase in
209    # speed under CPython (though still 2x slower than glibc crypt)
210
211    # prepare the 6 combinations of ds & dp which are needed
212    # (order of 'perms' must match how _c_digest_offsets was generated)
213    dp_dp = dp+dp
214    dp_ds = dp+ds
215    perms = [dp, dp_dp, dp_ds, dp_ds+dp, ds+dp, ds+dp_dp]
216
217    # build up list of even-round & odd-round constants,
218    # and store in 21-element list as (even,odd) pairs.
219    data = [ (perms[even], perms[odd]) for even, odd in _c_digest_offsets]
220
221    # perform as many full 42-round blocks as possible
222    dc = da
223    blocks, tail = divmod(rounds, 42)
224    while blocks:
225        for even, odd in data:
226            dc = hash_const(odd + hash_const(dc + even).digest()).digest()
227        blocks -= 1
228
229    # perform any leftover rounds
230    if tail:
231        # perform any pairs of rounds
232        pairs = tail>>1
233        for even, odd in data[:pairs]:
234            dc = hash_const(odd + hash_const(dc + even).digest()).digest()
235
236        # if rounds was odd, do one last round (since we started at 0,
237        # last round will be an even-numbered round)
238        if tail & 1:
239            dc = hash_const(dc + data[pairs][0]).digest()
240
241    #===================================================================
242    # encode digest using appropriate transpose map
243    #===================================================================
244    return h64.encode_transposed_bytes(dc, transpose_map).decode("ascii")
245
246#=============================================================================
247# handlers
248#=============================================================================
249_UROUNDS = u("rounds=")
250_UDOLLAR = u("$")
251_UZERO = u("0")
252
253class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
254                   uh.GenericHandler):
255    """class containing common code shared by sha256_crypt & sha512_crypt"""
256    #===================================================================
257    # class attrs
258    #===================================================================
259    # name - set by subclass
260    setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size")
261    # ident - set by subclass
262    checksum_chars = uh.HASH64_CHARS
263    # checksum_size - set by subclass
264
265    max_salt_size = 16
266    salt_chars = uh.HASH64_CHARS
267
268    min_rounds = 1000 # bounds set by spec
269    max_rounds = 999999999 # bounds set by spec
270    rounds_cost = "linear"
271
272    _cdb_use_512 = False # flag for _calc_digest_builtin()
273    _rounds_prefix = None # ident + _UROUNDS
274
275    #===================================================================
276    # methods
277    #===================================================================
278    implicit_rounds = False
279
280    def __init__(self, implicit_rounds=None, **kwds):
281        super(_SHA2_Common, self).__init__(**kwds)
282        # if user calls hash() w/ 5000 rounds, default to compact form.
283        if implicit_rounds is None:
284            implicit_rounds = (self.use_defaults and self.rounds == 5000)
285        self.implicit_rounds = implicit_rounds
286
287    def _parse_salt(self, salt):
288        # required per SHA2-crypt spec -- truncate config salts rather than throwing error
289        return self._norm_salt(salt, relaxed=self.checksum is None)
290
291    def _parse_rounds(self, rounds):
292        # required per SHA2-crypt spec -- clip config rounds rather than throwing error
293        return self._norm_rounds(rounds, relaxed=self.checksum is None)
294
295    @classmethod
296    def from_string(cls, hash):
297        # basic format this parses -
298        # $5$[rounds=<rounds>$]<salt>[$<checksum>]
299
300        # TODO: this *could* use uh.parse_mc3(), except that the rounds
301        # portion has a slightly different grammar.
302
303        # convert to unicode, check for ident prefix, split on dollar signs.
304        hash = to_unicode(hash, "ascii", "hash")
305        ident = cls.ident
306        if not hash.startswith(ident):
307            raise uh.exc.InvalidHashError(cls)
308        assert len(ident) == 3
309        parts = hash[3:].split(_UDOLLAR)
310
311        # extract rounds value
312        if parts[0].startswith(_UROUNDS):
313            assert len(_UROUNDS) == 7
314            rounds = parts.pop(0)[7:]
315            if rounds.startswith(_UZERO) and rounds != _UZERO:
316                raise uh.exc.ZeroPaddedRoundsError(cls)
317            rounds = int(rounds)
318            implicit_rounds = False
319        else:
320            rounds = 5000
321            implicit_rounds = True
322
323        # rest should be salt and checksum
324        if len(parts) == 2:
325            salt, chk = parts
326        elif len(parts) == 1:
327            salt = parts[0]
328            chk = None
329        else:
330            raise uh.exc.MalformedHashError(cls)
331
332        # return new object
333        return cls(
334            rounds=rounds,
335            salt=salt,
336            checksum=chk or None,
337            implicit_rounds=implicit_rounds,
338            )
339
340    def to_string(self):
341        if self.rounds == 5000 and self.implicit_rounds:
342            hash = u("%s%s$%s") % (self.ident, self.salt,
343                                   self.checksum or u(''))
344        else:
345            hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds,
346                                             self.salt, self.checksum or u(''))
347        return uascii_to_str(hash)
348
349    #===================================================================
350    # backends
351    #===================================================================
352    backends = ("os_crypt", "builtin")
353
354    #---------------------------------------------------------------
355    # os_crypt backend
356    #---------------------------------------------------------------
357
358    #: test hash for OS detection -- provided by subclass
359    _test_hash = None
360
361    @classmethod
362    def _load_backend_os_crypt(cls):
363        if test_crypt(*cls._test_hash):
364            cls._set_calc_checksum_backend(cls._calc_checksum_os_crypt)
365            return True
366        else:
367            return False
368
369    def _calc_checksum_os_crypt(self, secret):
370        hash = safe_crypt(secret, self.to_string())
371        if hash:
372            # NOTE: avoiding full parsing routine via from_string().checksum,
373            # and just extracting the bit we need.
374            cs = self.checksum_size
375            assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR
376            return hash[-cs:]
377        else:
378            # py3's crypt.crypt() can't handle non-utf8 bytes.
379            # fallback to builtin alg, which is always available.
380            return self._calc_checksum_builtin(secret)
381
382    #---------------------------------------------------------------
383    # builtin backend
384    #---------------------------------------------------------------
385    @classmethod
386    def _load_backend_builtin(cls):
387        cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
388        return True
389
390    def _calc_checksum_builtin(self, secret):
391        return _raw_sha2_crypt(secret, self.salt, self.rounds,
392                               self._cdb_use_512)
393
394    #===================================================================
395    # eoc
396    #===================================================================
397
398class sha256_crypt(_SHA2_Common):
399    """This class implements the SHA256-Crypt password hash, and follows the :ref:`password-hash-api`.
400
401    It supports a variable-length salt, and a variable number of rounds.
402
403    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
404
405    :type salt: str
406    :param salt:
407        Optional salt string.
408        If not specified, one will be autogenerated (this is recommended).
409        If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
410
411    :type rounds: int
412    :param rounds:
413        Optional number of rounds to use.
414        Defaults to 535000, must be between 1000 and 999999999, inclusive.
415
416    :type implicit_rounds: bool
417    :param implicit_rounds:
418        this is an internal option which generally doesn't need to be touched.
419
420        this flag determines whether the hash should omit the rounds parameter
421        when encoding it to a string; this is only permitted by the spec for rounds=5000,
422        and the flag is ignored otherwise. the spec requires the two different
423        encodings be preserved as they are, instead of normalizing them.
424
425    :type relaxed: bool
426    :param relaxed:
427        By default, providing an invalid value for one of the other
428        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
429        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
430        will be issued instead. Correctable errors include ``rounds``
431        that are too small or too large, and ``salt`` strings that are too long.
432
433        .. versionadded:: 1.6
434    """
435    #===================================================================
436    # class attrs
437    #===================================================================
438    name = "sha256_crypt"
439    ident = u("$5$")
440    checksum_size = 43
441    # NOTE: using 25/75 weighting of builtin & os_crypt backends
442    default_rounds = 535000
443
444    #===================================================================
445    # backends
446    #===================================================================
447    _test_hash = ("test", "$5$rounds=1000$test$QmQADEXMG8POI5W"
448                          "Dsaeho0P36yK3Tcrgboabng6bkb/")
449
450    #===================================================================
451    # eoc
452    #===================================================================
453
454#=============================================================================
455# sha 512 crypt
456#=============================================================================
457class sha512_crypt(_SHA2_Common):
458    """This class implements the SHA512-Crypt password hash, and follows the :ref:`password-hash-api`.
459
460    It supports a variable-length salt, and a variable number of rounds.
461
462    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
463
464    :type salt: str
465    :param salt:
466        Optional salt string.
467        If not specified, one will be autogenerated (this is recommended).
468        If specified, it must be 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
469
470    :type rounds: int
471    :param rounds:
472        Optional number of rounds to use.
473        Defaults to 656000, must be between 1000 and 999999999, inclusive.
474
475    :type implicit_rounds: bool
476    :param implicit_rounds:
477        this is an internal option which generally doesn't need to be touched.
478
479        this flag determines whether the hash should omit the rounds parameter
480        when encoding it to a string; this is only permitted by the spec for rounds=5000,
481        and the flag is ignored otherwise. the spec requires the two different
482        encodings be preserved as they are, instead of normalizing them.
483
484    :type relaxed: bool
485    :param relaxed:
486        By default, providing an invalid value for one of the other
487        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
488        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
489        will be issued instead. Correctable errors include ``rounds``
490        that are too small or too large, and ``salt`` strings that are too long.
491
492        .. versionadded:: 1.6
493    """
494
495    #===================================================================
496    # class attrs
497    #===================================================================
498    name = "sha512_crypt"
499    ident = u("$6$")
500    checksum_size = 86
501    _cdb_use_512 = True
502    # NOTE: using 25/75 weighting of builtin & os_crypt backends
503    default_rounds = 656000
504
505    #===================================================================
506    # backend
507    #===================================================================
508    _test_hash = ("test", "$6$rounds=1000$test$2M/Lx6Mtobqj"
509                          "Ljobw0Wmo4Q5OFx5nVLJvmgseatA6oMn"
510                          "yWeBdRDx4DU.1H3eGmse6pgsOgDisWBG"
511                          "I5c7TZauS0")
512
513    #===================================================================
514    # eoc
515    #===================================================================
516
517#=============================================================================
518# eof
519#=============================================================================
520