1"""passlib.bcrypt -- implementation of OpenBSD's BCrypt algorithm.
2
3TODO:
4
5* support 2x and altered-2a hashes?
6  http://www.openwall.com/lists/oss-security/2011/06/27/9
7
8* deal with lack of PY3-compatibile c-ext implementation
9"""
10#=============================================================================
11# imports
12#=============================================================================
13from __future__ import with_statement, absolute_import
14# core
15from base64 import b64encode
16from hashlib import sha256
17import os
18import re
19import logging; log = logging.getLogger(__name__)
20from warnings import warn
21# site
22_bcrypt = None # dynamically imported by _load_backend_bcrypt()
23_pybcrypt = None # dynamically imported by _load_backend_pybcrypt()
24_bcryptor = None # dynamically imported by _load_backend_bcryptor()
25# pkg
26_builtin_bcrypt = None  # dynamically imported by _load_backend_builtin()
27from passlib.crypto.digest import compile_hmac
28from passlib.exc import PasslibHashWarning, PasslibSecurityWarning, PasslibSecurityError
29from passlib.utils import safe_crypt, repeat_string, to_bytes, parse_version, \
30                          rng, getrandstr, test_crypt, to_unicode, \
31                          utf8_truncate, utf8_repeat_string, crypt_accepts_bytes
32from passlib.utils.binary import bcrypt64
33from passlib.utils.compat import get_unbound_method_function
34from passlib.utils.compat import u, uascii_to_str, unicode, str_to_uascii, PY3, error_from
35import passlib.utils.handlers as uh
36
37# local
38__all__ = [
39    "bcrypt",
40]
41
42#=============================================================================
43# support funcs & constants
44#=============================================================================
45IDENT_2 = u("$2$")
46IDENT_2A = u("$2a$")
47IDENT_2X = u("$2x$")
48IDENT_2Y = u("$2y$")
49IDENT_2B = u("$2b$")
50_BNULL = b'\x00'
51
52# reference hash of "test", used in various self-checks
53TEST_HASH_2A = "$2a$04$5BJqKfqMQvV7nS.yUguNcueVirQqDBGaLXSqj.rs.pZPlNR0UX/HK"
54
55def _detect_pybcrypt():
56    """
57    internal helper which tries to distinguish pybcrypt vs bcrypt.
58
59    :returns:
60        True if cext-based py-bcrypt,
61        False if ffi-based bcrypt,
62        None if 'bcrypt' module not found.
63
64    .. versionchanged:: 1.6.3
65
66        Now assuming bcrypt installed, unless py-bcrypt explicitly detected.
67        Previous releases assumed py-bcrypt by default.
68
69        Making this change since py-bcrypt is (apparently) unmaintained and static,
70        whereas bcrypt is being actively maintained, and it's internal structure may shift.
71    """
72    # NOTE: this is also used by the unittests.
73
74    # check for module.
75    try:
76        import bcrypt
77    except ImportError:
78        # XXX: this is ignoring case where py-bcrypt's "bcrypt._bcrypt" C Ext fails to import;
79        #      would need to inspect actual ImportError message to catch that.
80        return None
81
82    # py-bcrypt has a "._bcrypt.__version__" attribute (confirmed for v0.1 - 0.4),
83    # which bcrypt lacks (confirmed for v1.0 - 2.0)
84    # "._bcrypt" alone isn't sufficient, since bcrypt 2.0 now has that attribute.
85    try:
86        from bcrypt._bcrypt import __version__
87    except ImportError:
88        return False
89    return True
90
91#=============================================================================
92# backend mixins
93#=============================================================================
94class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
95                    uh.HasRounds, uh.HasSalt, uh.GenericHandler):
96    """
97    Base class which implements brunt of BCrypt code.
98    This is then subclassed by the various backends,
99    to override w/ backend-specific methods.
100
101    When a backend is loaded, the bases of the 'bcrypt' class proper
102    are modified to prepend the correct backend-specific subclass.
103    """
104    #===================================================================
105    # class attrs
106    #===================================================================
107
108    #--------------------
109    # PasswordHash
110    #--------------------
111    name = "bcrypt"
112    setting_kwds = ("salt", "rounds", "ident", "truncate_error")
113
114    #--------------------
115    # GenericHandler
116    #--------------------
117    checksum_size = 31
118    checksum_chars = bcrypt64.charmap
119
120    #--------------------
121    # HasManyIdents
122    #--------------------
123    default_ident = IDENT_2B
124    ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y, IDENT_2B)
125    ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A,  u("2y"): IDENT_2Y,
126                     u("2b"): IDENT_2B}
127
128    #--------------------
129    # HasSalt
130    #--------------------
131    min_salt_size = max_salt_size = 22
132    salt_chars = bcrypt64.charmap
133
134    # NOTE: 22nd salt char must be in restricted set of ``final_salt_chars``, not full set above.
135    final_salt_chars = ".Oeu"  # bcrypt64._padinfo2[1]
136
137    #--------------------
138    # HasRounds
139    #--------------------
140    default_rounds = 12 # current passlib default
141    min_rounds = 4 # minimum from bcrypt specification
142    max_rounds = 31 # 32-bit integer limit (since real_rounds=1<<rounds)
143    rounds_cost = "log2"
144
145    #--------------------
146    # TruncateMixin
147    #--------------------
148    truncate_size = 72
149
150    #--------------------
151    # custom
152    #--------------------
153
154    # backend workaround detection flags
155    # NOTE: these are only set on the backend mixin classes
156    _workrounds_initialized = False
157    _has_2a_wraparound_bug = False
158    _lacks_20_support = False
159    _lacks_2y_support = False
160    _lacks_2b_support = False
161    _fallback_ident = IDENT_2A
162    _require_valid_utf8_bytes = False
163
164    #===================================================================
165    # formatting
166    #===================================================================
167
168    @classmethod
169    def from_string(cls, hash):
170        ident, tail = cls._parse_ident(hash)
171        if ident == IDENT_2X:
172            raise ValueError("crypt_blowfish's buggy '2x' hashes are not "
173                             "currently supported")
174        rounds_str, data = tail.split(u("$"))
175        rounds = int(rounds_str)
176        if rounds_str != u('%02d') % (rounds,):
177            raise uh.exc.MalformedHashError(cls, "malformed cost field")
178        salt, chk = data[:22], data[22:]
179        return cls(
180            rounds=rounds,
181            salt=salt,
182            checksum=chk or None,
183            ident=ident,
184        )
185
186    def to_string(self):
187        hash = u("%s%02d$%s%s") % (self.ident, self.rounds, self.salt, self.checksum)
188        return uascii_to_str(hash)
189
190    # NOTE: this should be kept separate from to_string()
191    #       so that bcrypt_sha256() can still use it, while overriding to_string()
192    def _get_config(self, ident):
193        """internal helper to prepare config string for backends"""
194        config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
195        return uascii_to_str(config)
196
197    #===================================================================
198    # migration
199    #===================================================================
200
201    @classmethod
202    def needs_update(cls, hash, **kwds):
203        # NOTE: can't convert this to use _calc_needs_update() helper,
204        #       since _norm_hash() will correct salt padding before we can read it here.
205        # check for incorrect padding bits (passlib issue 25)
206        if isinstance(hash, bytes):
207            hash = hash.decode("ascii")
208        if hash.startswith(IDENT_2A) and hash[28] not in cls.final_salt_chars:
209            return True
210
211        # TODO: try to detect incorrect 8bit/wraparound hashes using kwds.get("secret")
212
213        # hand off to base implementation, so HasRounds can check rounds value.
214        return super(_BcryptCommon, cls).needs_update(hash, **kwds)
215
216    #===================================================================
217    # specialized salt generation - fixes passlib issue 25
218    #===================================================================
219
220    @classmethod
221    def normhash(cls, hash):
222        """helper to normalize hash, correcting any bcrypt padding bits"""
223        if cls.identify(hash):
224            return cls.from_string(hash).to_string()
225        else:
226            return hash
227
228    @classmethod
229    def _generate_salt(cls):
230        # generate random salt as normal,
231        # but repair last char so the padding bits always decode to zero.
232        salt = super(_BcryptCommon, cls)._generate_salt()
233        return bcrypt64.repair_unused(salt)
234
235    @classmethod
236    def _norm_salt(cls, salt, **kwds):
237        salt = super(_BcryptCommon, cls)._norm_salt(salt, **kwds)
238        assert salt is not None, "HasSalt didn't generate new salt!"
239        changed, salt = bcrypt64.check_repair_unused(salt)
240        if changed:
241            # FIXME: if salt was provided by user, this message won't be
242            # correct. not sure if we want to throw error, or use different warning.
243            warn(
244                "encountered a bcrypt salt with incorrectly set padding bits; "
245                "you may want to use bcrypt.normhash() "
246                "to fix this; this will be an error under Passlib 2.0",
247                PasslibHashWarning)
248        return salt
249
250    def _norm_checksum(self, checksum, relaxed=False):
251        checksum = super(_BcryptCommon, self)._norm_checksum(checksum, relaxed=relaxed)
252        changed, checksum = bcrypt64.check_repair_unused(checksum)
253        if changed:
254            warn(
255                "encountered a bcrypt hash with incorrectly set padding bits; "
256                "you may want to use bcrypt.normhash() "
257                "to fix this; this will be an error under Passlib 2.0",
258                PasslibHashWarning)
259        return checksum
260
261    #===================================================================
262    # backend configuration
263    # NOTE: backends are defined in terms of mixin classes,
264    #       which are dynamically inserted into the bases of the 'bcrypt' class
265    #       via the machinery in 'SubclassBackendMixin'.
266    #       this lets us load in a backend-specific implementation
267    #       of _calc_checksum() and similar methods.
268    #===================================================================
269
270    # NOTE: backend config is located down in <bcrypt> class
271
272    # NOTE: set_backend() will execute the ._load_backend_mixin()
273    #       of the matching mixin class, which will handle backend detection
274
275    # appended to HasManyBackends' "no backends available" error message
276    _no_backend_suggestion = " -- recommend you install one (e.g. 'pip install bcrypt')"
277
278    @classmethod
279    def _finalize_backend_mixin(mixin_cls, backend, dryrun):
280        """
281        helper called by from backend mixin classes' _load_backend_mixin() --
282        invoked after backend imports have been loaded, and performs
283        feature detection & testing common to all backends.
284        """
285        #----------------------------------------------------------------
286        # setup helpers
287        #----------------------------------------------------------------
288        assert mixin_cls is bcrypt._backend_mixin_map[backend], \
289            "_configure_workarounds() invoked from wrong class"
290
291        if mixin_cls._workrounds_initialized:
292            return True
293
294        verify = mixin_cls.verify
295
296        err_types = (ValueError, uh.exc.MissingBackendError)
297        if _bcryptor:
298            err_types += (_bcryptor.engine.SaltError,)
299
300        def safe_verify(secret, hash):
301            """verify() wrapper which traps 'unknown identifier' errors"""
302            try:
303                return verify(secret, hash)
304            except err_types:
305                # backends without support for given ident will throw various
306                # errors about unrecognized version:
307                #   os_crypt -- internal code below throws
308                #       - PasswordValueError if there's encoding issue w/ password.
309                #       - InternalBackendError if crypt fails for unknown reason
310                #         (trapped below so we can debug it)
311                #   pybcrypt, bcrypt -- raises ValueError
312                #   bcryptor -- raises bcryptor.engine.SaltError
313                return NotImplemented
314            except uh.exc.InternalBackendError:
315                # _calc_checksum() code may also throw CryptBackendError
316                # if correct hash isn't returned (e.g. 2y hash converted to 2b,
317                # such as happens with bcrypt 3.0.0)
318                log.debug("trapped unexpected response from %r backend: verify(%r, %r):",
319                          backend, secret, hash, exc_info=True)
320                return NotImplemented
321
322        def assert_lacks_8bit_bug(ident):
323            """
324            helper to check for cryptblowfish 8bit bug (fixed in 2y/2b);
325            even though it's not known to be present in any of passlib's backends.
326            this is treated as FATAL, because it can easily result in seriously malformed hashes,
327            and we can't correct for it ourselves.
328
329            test cases from <http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/glibc/crypt_blowfish/wrapper.c.diff?r1=1.9;r2=1.10>
330            reference hash is the incorrectly generated $2x$ hash taken from above url
331            """
332            # NOTE: passlib 1.7.2 and earlier used the commented-out LATIN-1 test vector to detect
333            #       this bug; but python3's crypt.crypt() only supports unicode inputs (and
334            #       always encodes them as UTF8 before passing to crypt); so passlib 1.7.3
335            #       switched to the UTF8-compatible test vector below.  This one's bug_hash value
336            #       ("$2x$...rcAS") was drawn from the same openwall source (above); and the correct
337            #       hash ("$2a$...X6eu") was generated by passing the raw bytes to python2's
338            #       crypt.crypt() using OpenBSD 6.7 (hash confirmed as same for $2a$ & $2b$).
339
340            # LATIN-1 test vector
341            # secret = b"\xA3"
342            # bug_hash = ident.encode("ascii") + b"05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"
343            # correct_hash = ident.encode("ascii") + b"05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"
344
345            # UTF-8 test vector
346            secret = b"\xd1\x91"  # aka "\u0451"
347            bug_hash = ident.encode("ascii") + b"05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS"
348            correct_hash = ident.encode("ascii") + b"05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"
349
350            if verify(secret, bug_hash):
351                # NOTE: this only EVER be observed in (broken) 2a and (backward-compat) 2x hashes
352                #       generated by crypt_blowfish library. 2y/2b hashes should not have the bug
353                #       (but we check w/ them anyways).
354                raise PasslibSecurityError(
355                    "passlib.hash.bcrypt: Your installation of the %r backend is vulnerable to "
356                    "the crypt_blowfish 8-bit bug (CVE-2011-2483) under %r hashes, "
357                    "and should be upgraded or replaced with another backend" % (backend, ident))
358
359            # it doesn't have wraparound bug, but make sure it *does* verify against the correct
360            # hash, or we're in some weird third case!
361            if not verify(secret, correct_hash):
362                raise RuntimeError("%s backend failed to verify %s 8bit hash" % (backend, ident))
363
364        def detect_wrap_bug(ident):
365            """
366            check for bsd wraparound bug (fixed in 2b)
367            this is treated as a warning, because it's rare in the field,
368            and pybcrypt (as of 2015-7-21) is unpatched, but some people may be stuck with it.
369
370            test cases from <http://www.openwall.com/lists/oss-security/2012/01/02/4>
371
372            NOTE: reference hash is of password "0"*72
373
374            NOTE: if in future we need to deliberately create hashes which have this bug,
375                  can use something like 'hashpw(repeat_string(secret[:((1+secret) % 256) or 1]), 72)'
376            """
377            # check if it exhibits wraparound bug
378            secret = (b"0123456789"*26)[:255]
379            bug_hash = ident.encode("ascii") + b"04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"
380            if verify(secret, bug_hash):
381                return True
382
383            # if it doesn't have wraparound bug, make sure it *does* handle things
384            # correctly -- or we're in some weird third case.
385            correct_hash = ident.encode("ascii") + b"04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi"
386            if not verify(secret, correct_hash):
387                raise RuntimeError("%s backend failed to verify %s wraparound hash" % (backend, ident))
388
389            return False
390
391        def assert_lacks_wrap_bug(ident):
392            if not detect_wrap_bug(ident):
393                return
394            # should only see in 2a, later idents should NEVER exhibit this bug:
395            # * 2y implementations should have been free of it
396            # * 2b was what (supposedly) fixed it
397            raise RuntimeError("%s backend unexpectedly has wraparound bug for %s" % (backend, ident))
398
399        #----------------------------------------------------------------
400        # check for old 20 support
401        #----------------------------------------------------------------
402        test_hash_20 = b"$2$04$5BJqKfqMQvV7nS.yUguNcuRfMMOXK0xPWavM7pOzjEi5ze5T1k8/S"
403        result = safe_verify("test", test_hash_20)
404        if result is NotImplemented:
405            mixin_cls._lacks_20_support = True
406            log.debug("%r backend lacks $2$ support, enabling workaround", backend)
407        elif not result:
408            raise RuntimeError("%s incorrectly rejected $2$ hash" % backend)
409
410        #----------------------------------------------------------------
411        # check for 2a support
412        #----------------------------------------------------------------
413        result = safe_verify("test", TEST_HASH_2A)
414        if result is NotImplemented:
415            # 2a support is required, and should always be present
416            raise RuntimeError("%s lacks support for $2a$ hashes" % backend)
417        elif not result:
418            raise RuntimeError("%s incorrectly rejected $2a$ hash" % backend)
419        else:
420            assert_lacks_8bit_bug(IDENT_2A)
421            if detect_wrap_bug(IDENT_2A):
422                if backend == "os_crypt":
423                    # don't make this a warning for os crypt (e.g. openbsd);
424                    # they'll have proper 2b implementation which will be used for new hashes.
425                    # so even if we didn't have a workaround, this bug wouldn't be a concern.
426                    log.debug("%r backend has $2a$ bsd wraparound bug, enabling workaround", backend)
427                else:
428                    # installed library has the bug -- want to let users know,
429                    # so they can upgrade it to something better (e.g. bcrypt cffi library)
430                    warn("passlib.hash.bcrypt: Your installation of the %r backend is vulnerable to "
431                         "the bsd wraparound bug, "
432                         "and should be upgraded or replaced with another backend "
433                         "(enabling workaround for now)." % backend,
434                         uh.exc.PasslibSecurityWarning)
435                mixin_cls._has_2a_wraparound_bug = True
436
437        #----------------------------------------------------------------
438        # check for 2y support
439        #----------------------------------------------------------------
440        test_hash_2y = TEST_HASH_2A.replace("2a", "2y")
441        result = safe_verify("test", test_hash_2y)
442        if result is NotImplemented:
443            mixin_cls._lacks_2y_support = True
444            log.debug("%r backend lacks $2y$ support, enabling workaround", backend)
445        elif not result:
446            raise RuntimeError("%s incorrectly rejected $2y$ hash" % backend)
447        else:
448            # NOTE: Not using this as fallback candidate,
449            #       lacks wide enough support across implementations.
450            assert_lacks_8bit_bug(IDENT_2Y)
451            assert_lacks_wrap_bug(IDENT_2Y)
452
453        #----------------------------------------------------------------
454        # TODO: check for 2x support
455        #----------------------------------------------------------------
456
457        #----------------------------------------------------------------
458        # check for 2b support
459        #----------------------------------------------------------------
460        test_hash_2b = TEST_HASH_2A.replace("2a", "2b")
461        result = safe_verify("test", test_hash_2b)
462        if result is NotImplemented:
463            mixin_cls._lacks_2b_support = True
464            log.debug("%r backend lacks $2b$ support, enabling workaround", backend)
465        elif not result:
466            raise RuntimeError("%s incorrectly rejected $2b$ hash" % backend)
467        else:
468            mixin_cls._fallback_ident = IDENT_2B
469            assert_lacks_8bit_bug(IDENT_2B)
470            assert_lacks_wrap_bug(IDENT_2B)
471
472        # set flag so we don't have to run this again
473        mixin_cls._workrounds_initialized = True
474        return True
475
476    #===================================================================
477    # digest calculation
478    #===================================================================
479
480    # _calc_checksum() defined by backends
481
482    def _prepare_digest_args(self, secret):
483        """
484        common helper for backends to implement _calc_checksum().
485        takes in secret, returns (secret, ident) pair,
486        """
487        return self._norm_digest_args(secret, self.ident, new=self.use_defaults)
488
489    @classmethod
490    def _norm_digest_args(cls, secret, ident, new=False):
491        # make sure secret is unicode
492        require_valid_utf8_bytes = cls._require_valid_utf8_bytes
493        if isinstance(secret, unicode):
494            secret = secret.encode("utf-8")
495        elif require_valid_utf8_bytes:
496            # if backend requires utf8 bytes (os_crypt);
497            # make sure input actually is utf8, or don't bother enabling utf-8 specific helpers.
498            try:
499                secret.decode("utf-8")
500            except UnicodeDecodeError:
501                # XXX: could just throw PasswordValueError here, backend will just do that
502                #      when _calc_digest() is actually called.
503                require_valid_utf8_bytes = False
504
505        # check max secret size
506        uh.validate_secret(secret)
507
508        # check for truncation (during .hash() calls only)
509        if new:
510            cls._check_truncate_policy(secret)
511
512        # NOTE: especially important to forbid NULLs for bcrypt, since many
513        # backends (bcryptor, bcrypt) happily accept them, and then
514        # silently truncate the password at first NULL they encounter!
515        if _BNULL in secret:
516            raise uh.exc.NullPasswordError(cls)
517
518        # TODO: figure out way to skip these tests when not needed...
519
520        # protect from wraparound bug by truncating secret before handing it to the backend.
521        # bcrypt only uses first 72 bytes anyways.
522        # NOTE: not needed for 2y/2b, but might use 2a as fallback for them.
523        if cls._has_2a_wraparound_bug and len(secret) >= 255:
524            if require_valid_utf8_bytes:
525                # backend requires valid utf8 bytes, so truncate secret to nearest valid segment.
526                # want to do this in constant time to not give away info about secret.
527                # NOTE: this only works because bcrypt will ignore everything past
528                #       secret[71], so padding to include a full utf8 sequence
529                #       won't break anything about the final output.
530                secret = utf8_truncate(secret, 72)
531            else:
532                secret = secret[:72]
533
534        # special case handling for variants (ordered most common first)
535        if ident == IDENT_2A:
536            # nothing needs to be done.
537            pass
538
539        elif ident == IDENT_2B:
540            if cls._lacks_2b_support:
541                # handle $2b$ hash format even if backend is too old.
542                # have it generate a 2A/2Y digest, then return it as a 2B hash.
543                # 2a-only backend could potentially exhibit wraparound bug --
544                # but we work around that issue above.
545                ident = cls._fallback_ident
546
547        elif ident == IDENT_2Y:
548            if cls._lacks_2y_support:
549                # handle $2y$ hash format (not supported by BSDs, being phased out on others)
550                # have it generate a 2A/2B digest, then return it as a 2Y hash.
551                ident = cls._fallback_ident
552
553        elif ident == IDENT_2:
554            if cls._lacks_20_support:
555                # handle legacy $2$ format (not supported by most backends except BSD os_crypt)
556                # we can fake $2$ behavior using the 2A/2Y/2B algorithm
557                # by repeating the password until it's at least 72 chars in length.
558                if secret:
559                    if require_valid_utf8_bytes:
560                        # NOTE: this only works because bcrypt will ignore everything past
561                        #       secret[71], so padding to include a full utf8 sequence
562                        #       won't break anything about the final output.
563                        secret = utf8_repeat_string(secret, 72)
564                    else:
565                        secret = repeat_string(secret, 72)
566                ident = cls._fallback_ident
567
568        elif ident == IDENT_2X:
569
570            # NOTE: shouldn't get here.
571            # XXX: could check if backend does actually offer 'support'
572            raise RuntimeError("$2x$ hashes not currently supported by passlib")
573
574        else:
575            raise AssertionError("unexpected ident value: %r" % ident)
576
577        return secret, ident
578
579#-----------------------------------------------------------------------
580# stub backend
581#-----------------------------------------------------------------------
582class _NoBackend(_BcryptCommon):
583    """
584    mixin used before any backend has been loaded.
585    contains stubs that force loading of one of the available backends.
586    """
587    #===================================================================
588    # digest calculation
589    #===================================================================
590    def _calc_checksum(self, secret):
591        self._stub_requires_backend()
592        # NOTE: have to use super() here so that we don't recursively
593        #       call subclass's wrapped _calc_checksum, e.g. bcrypt_sha256._calc_checksum
594        return super(bcrypt, self)._calc_checksum(secret)
595
596    #===================================================================
597    # eoc
598    #===================================================================
599
600#-----------------------------------------------------------------------
601# bcrypt backend
602#-----------------------------------------------------------------------
603class _BcryptBackend(_BcryptCommon):
604    """
605    backend which uses 'bcrypt' package
606    """
607
608    @classmethod
609    def _load_backend_mixin(mixin_cls, name, dryrun):
610        # try to import bcrypt
611        global _bcrypt
612        if _detect_pybcrypt():
613            # pybcrypt was installed instead
614            return False
615        try:
616            import bcrypt as _bcrypt
617        except ImportError: # pragma: no cover
618            return False
619        try:
620            version = _bcrypt.__about__.__version__
621        except:
622            log.warning("(trapped) error reading bcrypt version", exc_info=True)
623            version = '<unknown>'
624
625        log.debug("detected 'bcrypt' backend, version %r", version)
626        return mixin_cls._finalize_backend_mixin(name, dryrun)
627
628    # # TODO: would like to implementing verify() directly,
629    # #       to skip need for parsing hash strings.
630    # #       below method has a few edge cases where it chokes though.
631    # @classmethod
632    # def verify(cls, secret, hash):
633    #     if isinstance(hash, unicode):
634    #         hash = hash.encode("ascii")
635    #     ident = hash[:hash.index(b"$", 1)+1].decode("ascii")
636    #     if ident not in cls.ident_values:
637    #         raise uh.exc.InvalidHashError(cls)
638    #     secret, eff_ident = cls._norm_digest_args(secret, ident)
639    #     if eff_ident != ident:
640    #         # lacks support for original ident, replace w/ new one.
641    #         hash = eff_ident.encode("ascii") + hash[len(ident):]
642    #     result = _bcrypt.hashpw(secret, hash)
643    #     assert result.startswith(eff_ident)
644    #     return consteq(result, hash)
645
646    def _calc_checksum(self, secret):
647        # bcrypt behavior:
648        #   secret must be bytes
649        #   config must be ascii bytes
650        #   returns ascii bytes
651        secret, ident = self._prepare_digest_args(secret)
652        config = self._get_config(ident)
653        if isinstance(config, unicode):
654            config = config.encode("ascii")
655        hash = _bcrypt.hashpw(secret, config)
656        assert isinstance(hash, bytes)
657        if not hash.startswith(config) or len(hash) != len(config)+31:
658            raise uh.exc.CryptBackendError(self, config, hash, source="`bcrypt` package")
659        return hash[-31:].decode("ascii")
660
661#-----------------------------------------------------------------------
662# bcryptor backend
663#-----------------------------------------------------------------------
664class _BcryptorBackend(_BcryptCommon):
665    """
666    backend which uses 'bcryptor' package
667    """
668
669    @classmethod
670    def _load_backend_mixin(mixin_cls, name, dryrun):
671        # try to import bcryptor
672        global _bcryptor
673        try:
674            import bcryptor as _bcryptor
675        except ImportError: # pragma: no cover
676            return False
677
678        # deprecated as of 1.7.2
679        if not dryrun:
680            warn("Support for `bcryptor` is deprecated, and will be removed in Passlib 1.8; "
681                 "Please use `pip install bcrypt` instead", DeprecationWarning)
682
683        return mixin_cls._finalize_backend_mixin(name, dryrun)
684
685    def _calc_checksum(self, secret):
686        # bcryptor behavior:
687        #   py2: unicode secret/hash encoded as ascii bytes before use,
688        #        bytes taken as-is; returns ascii bytes.
689        #   py3: not supported
690        secret, ident = self._prepare_digest_args(secret)
691        config = self._get_config(ident)
692        hash = _bcryptor.engine.Engine(False).hash_key(secret, config)
693        if not hash.startswith(config) or len(hash) != len(config) + 31:
694            raise uh.exc.CryptBackendError(self, config, hash, source="bcryptor library")
695        return str_to_uascii(hash[-31:])
696
697#-----------------------------------------------------------------------
698# pybcrypt backend
699#-----------------------------------------------------------------------
700class _PyBcryptBackend(_BcryptCommon):
701    """
702    backend which uses 'pybcrypt' package
703    """
704
705    #: classwide thread lock used for pybcrypt < 0.3
706    _calc_lock = None
707
708    @classmethod
709    def _load_backend_mixin(mixin_cls, name, dryrun):
710        # try to import pybcrypt
711        global _pybcrypt
712        if not _detect_pybcrypt():
713            # not installed, or bcrypt installed instead
714            return False
715        try:
716            import bcrypt as _pybcrypt
717        except ImportError: # pragma: no cover
718            # XXX: should we raise AssertionError here? (if get here, _detect_pybcrypt() is broken)
719            return False
720
721        # deprecated as of 1.7.2
722        if not dryrun:
723            warn("Support for `py-bcrypt` is deprecated, and will be removed in Passlib 1.8; "
724                 "Please use `pip install bcrypt` instead", DeprecationWarning)
725
726        # determine pybcrypt version
727        try:
728            version = _pybcrypt._bcrypt.__version__
729        except:
730            log.warning("(trapped) error reading pybcrypt version", exc_info=True)
731            version = "<unknown>"
732        log.debug("detected 'pybcrypt' backend, version %r", version)
733
734        # return calc function based on version
735        vinfo = parse_version(version) or (0, 0)
736        if vinfo < (0, 3):
737            warn("py-bcrypt %s has a major security vulnerability, "
738                 "you should upgrade to py-bcrypt 0.3 immediately."
739                 % version, uh.exc.PasslibSecurityWarning)
740            if mixin_cls._calc_lock is None:
741                import threading
742                mixin_cls._calc_lock = threading.Lock()
743            mixin_cls._calc_checksum = get_unbound_method_function(mixin_cls._calc_checksum_threadsafe)
744
745        return mixin_cls._finalize_backend_mixin(name, dryrun)
746
747    def _calc_checksum_threadsafe(self, secret):
748        # as workaround for pybcrypt < 0.3's concurrency issue,
749        # we wrap everything in a thread lock. as long as bcrypt is only
750        # used through passlib, this should be safe.
751        with self._calc_lock:
752            return self._calc_checksum_raw(secret)
753
754    def _calc_checksum_raw(self, secret):
755        # py-bcrypt behavior:
756        #   py2: unicode secret/hash encoded as ascii bytes before use,
757        #        bytes taken as-is; returns ascii bytes.
758        #   py3: unicode secret encoded as utf-8 bytes,
759        #        hash encoded as ascii bytes, returns ascii unicode.
760        secret, ident = self._prepare_digest_args(secret)
761        config = self._get_config(ident)
762        hash = _pybcrypt.hashpw(secret, config)
763        if not hash.startswith(config) or len(hash) != len(config) + 31:
764            raise uh.exc.CryptBackendError(self, config, hash, source="pybcrypt library")
765        return str_to_uascii(hash[-31:])
766
767    _calc_checksum = _calc_checksum_raw
768
769#-----------------------------------------------------------------------
770# os crypt backend
771#-----------------------------------------------------------------------
772class _OsCryptBackend(_BcryptCommon):
773    """
774    backend which uses :func:`crypt.crypt`
775    """
776
777    #: set flag to ensure _prepare_digest_args() doesn't create invalid utf8 string
778    #: when truncating bytes.
779    _require_valid_utf8_bytes = not crypt_accepts_bytes
780
781    @classmethod
782    def _load_backend_mixin(mixin_cls, name, dryrun):
783        if not test_crypt("test", TEST_HASH_2A):
784            return False
785        return mixin_cls._finalize_backend_mixin(name, dryrun)
786
787    def _calc_checksum(self, secret):
788        #
789        # run secret through crypt.crypt().
790        # if everything goes right, we'll get back a properly formed bcrypt hash.
791        #
792        secret, ident = self._prepare_digest_args(secret)
793        config = self._get_config(ident)
794        hash = safe_crypt(secret, config)
795        if hash is not None:
796            if not hash.startswith(config) or len(hash) != len(config) + 31:
797                raise uh.exc.CryptBackendError(self, config, hash)
798            return hash[-31:]
799
800        #
801        # Check if this failed due to non-UTF8 bytes
802        # In detail: under py3, crypt.crypt() requires unicode inputs, which are then encoded to
803        # utf8 before passing them to os crypt() call.  this is done according to the "s" format
804        # specifier for PyArg_ParseTuple (https://docs.python.org/3/c-api/arg.html).
805        # There appears no way to get around that to pass raw bytes; so we just throw error here
806        # to let user know they need to use another backend if they want raw bytes support.
807        #
808        # XXX: maybe just let safe_crypt() throw UnicodeDecodeError under passlib 2.0,
809        #      and then catch it above? maybe have safe_crypt ALWAYS throw error
810        #      instead of returning None? (would save re-detecting what went wrong)
811        # XXX: isn't secret ALWAYS bytes at this point?
812        #
813        if PY3 and isinstance(secret, bytes):
814            try:
815                secret.decode("utf-8")
816            except UnicodeDecodeError:
817                raise error_from(uh.exc.PasswordValueError(
818                    "python3 crypt.crypt() ony supports bytes passwords using UTF8; "
819                    "passlib recommends running `pip install bcrypt` for general bcrypt support.",
820                    ), None)
821
822        #
823        # else crypt() call failed for unknown reason.
824        #
825        # NOTE: getting here should be considered a bug in passlib --
826        #       if os_crypt backend detection said there's support,
827        #       and we've already checked all known reasons above;
828        #       want them to file bug so we can figure out what happened.
829        #       in the meantime, users can avoid this by installing bcrypt-cffi backend;
830        #       which won't have this (or utf8) edgecases.
831        #
832        # XXX: throw something more specific, like an "InternalBackendError"?
833        # NOTE: if do change this error, need to update test_81_crypt_fallback() expectations
834        #       about what will be thrown; as well as safe_verify() above.
835        #
836        debug_only_repr = uh.exc.debug_only_repr
837        raise uh.exc.InternalBackendError(
838            "crypt.crypt() failed for unknown reason; "
839            "passlib recommends running `pip install bcrypt` for general bcrypt support."
840            # for debugging UTs --
841            "(config=%s, secret=%s)" % (debug_only_repr(config), debug_only_repr(secret)),
842            )
843
844#-----------------------------------------------------------------------
845# builtin backend
846#-----------------------------------------------------------------------
847class _BuiltinBackend(_BcryptCommon):
848    """
849    backend which uses passlib's pure-python implementation
850    """
851    @classmethod
852    def _load_backend_mixin(mixin_cls, name, dryrun):
853        from passlib.utils import as_bool
854        if not as_bool(os.environ.get("PASSLIB_BUILTIN_BCRYPT")):
855            log.debug("bcrypt 'builtin' backend not enabled via $PASSLIB_BUILTIN_BCRYPT")
856            return False
857        global _builtin_bcrypt
858        from passlib.crypto._blowfish import raw_bcrypt as _builtin_bcrypt
859        return mixin_cls._finalize_backend_mixin(name, dryrun)
860
861    def _calc_checksum(self, secret):
862        secret, ident = self._prepare_digest_args(secret)
863        chk = _builtin_bcrypt(secret, ident[1:-1],
864                              self.salt.encode("ascii"), self.rounds)
865        return chk.decode("ascii")
866
867#=============================================================================
868# handler
869#=============================================================================
870class bcrypt(_NoBackend, _BcryptCommon):
871    """This class implements the BCrypt password hash, and follows the :ref:`password-hash-api`.
872
873    It supports a fixed-length salt, and a variable number of rounds.
874
875    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
876
877    :type salt: str
878    :param salt:
879        Optional salt string.
880        If not specified, one will be autogenerated (this is recommended).
881        If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
882
883    :type rounds: int
884    :param rounds:
885        Optional number of rounds to use.
886        Defaults to 12, must be between 4 and 31, inclusive.
887        This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`
888        -- increasing the rounds by +1 will double the amount of time taken.
889
890    :type ident: str
891    :param ident:
892        Specifies which version of the BCrypt algorithm will be used when creating a new hash.
893        Typically this option is not needed, as the default (``"2b"``) is usually the correct choice.
894        If specified, it must be one of the following:
895
896        * ``"2"`` - the first revision of BCrypt, which suffers from a minor security flaw and is generally not used anymore.
897        * ``"2a"`` - some implementations suffered from rare security flaws, replaced by 2b.
898        * ``"2y"`` - format specific to the *crypt_blowfish* BCrypt implementation,
899          identical to ``"2b"`` in all but name.
900        * ``"2b"`` - latest revision of the official BCrypt algorithm, current default.
901
902    :param bool truncate_error:
903        By default, BCrypt will silently truncate passwords larger than 72 bytes.
904        Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
905        to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
906
907        .. versionadded:: 1.7
908
909    :type relaxed: bool
910    :param relaxed:
911        By default, providing an invalid value for one of the other
912        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
913        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
914        will be issued instead. Correctable errors include ``rounds``
915        that are too small or too large, and ``salt`` strings that are too long.
916
917        .. versionadded:: 1.6
918
919    .. versionchanged:: 1.6
920        This class now supports ``"2y"`` hashes, and recognizes
921        (but does not support) the broken ``"2x"`` hashes.
922        (see the :ref:`crypt_blowfish bug <crypt-blowfish-bug>`
923        for details).
924
925    .. versionchanged:: 1.6
926        Added a pure-python backend.
927
928    .. versionchanged:: 1.6.3
929
930        Added support for ``"2b"`` variant.
931
932    .. versionchanged:: 1.7
933
934        Now defaults to ``"2b"`` variant.
935    """
936    #=============================================================================
937    # backend
938    #=============================================================================
939
940    # NOTE: the brunt of the bcrypt class is implemented in _BcryptCommon.
941    #       there are then subclass for each backend (e.g. _PyBcryptBackend),
942    #       these are dynamically prepended to this class's bases
943    #       in order to load the appropriate backend.
944
945    #: list of potential backends
946    backends = ("bcrypt", "pybcrypt", "bcryptor", "os_crypt", "builtin")
947
948    #: flag that this class's bases should be modified by SubclassBackendMixin
949    _backend_mixin_target = True
950
951    #: map of backend -> mixin class, used by _get_backend_loader()
952    _backend_mixin_map = {
953        None: _NoBackend,
954        "bcrypt": _BcryptBackend,
955        "pybcrypt": _PyBcryptBackend,
956        "bcryptor": _BcryptorBackend,
957        "os_crypt": _OsCryptBackend,
958        "builtin": _BuiltinBackend,
959    }
960
961    #=============================================================================
962    # eoc
963    #=============================================================================
964
965#=============================================================================
966# variants
967#=============================================================================
968_UDOLLAR = u("$")
969
970# XXX: it might be better to have all the bcrypt variants share a common base class,
971#      and have the (django_)bcrypt_sha256 wrappers just proxy bcrypt instead of subclassing it.
972class _wrapped_bcrypt(bcrypt):
973    """
974    abstracts out some bits bcrypt_sha256 & django_bcrypt_sha256 share.
975    - bypass backend-loading wrappers for hash() etc
976    - disable truncation support, sha256 wrappers don't need it.
977    """
978    setting_kwds = tuple(elem for elem in bcrypt.setting_kwds if elem not in ["truncate_error"])
979    truncate_size = None
980
981    # XXX: these will be needed if any bcrypt backends directly implement this...
982    # @classmethod
983    # def hash(cls, secret, **kwds):
984    #     # bypass bcrypt backend overriding this method
985    #     # XXX: would wrapping bcrypt make this easier than subclassing it?
986    #     return super(_BcryptCommon, cls).hash(secret, **kwds)
987    #
988    # @classmethod
989    # def verify(cls, secret, hash):
990    #     # bypass bcrypt backend overriding this method
991    #     return super(_BcryptCommon, cls).verify(secret, hash)
992    #
993    # @classmethod
994    # def genhash(cls, secret, hash):
995    #     # bypass bcrypt backend overriding this method
996    #     return super(_BcryptCommon, cls).genhash(secret, hash)
997
998    @classmethod
999    def _check_truncate_policy(cls, secret):
1000        # disable check performed by bcrypt(), since this doesn't truncate passwords.
1001        pass
1002
1003#=============================================================================
1004# bcrypt sha256 wrapper
1005#=============================================================================
1006
1007class bcrypt_sha256(_wrapped_bcrypt):
1008    """
1009    This class implements a composition of BCrypt + HMAC_SHA256,
1010    and follows the :ref:`password-hash-api`.
1011
1012    It supports a fixed-length salt, and a variable number of rounds.
1013
1014    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept
1015    all the same optional keywords as the base :class:`bcrypt` hash.
1016
1017    .. versionadded:: 1.6.2
1018
1019    .. versionchanged:: 1.7
1020
1021        Now defaults to ``"2b"`` bcrypt variant; though supports older hashes
1022        generated using the ``"2a"`` bcrypt variant.
1023
1024    .. versionchanged:: 1.7.3
1025
1026        For increased security, updated to use HMAC-SHA256 instead of plain SHA256.
1027        Now only supports the ``"2b"`` bcrypt variant.  Hash format updated to "v=2".
1028    """
1029    #===================================================================
1030    # class attrs
1031    #===================================================================
1032
1033    #--------------------
1034    # PasswordHash
1035    #--------------------
1036    name = "bcrypt_sha256"
1037
1038    #--------------------
1039    # GenericHandler
1040    #--------------------
1041    # this is locked at 2b for now (with 2a allowed only for legacy v1 format)
1042    ident_values = (IDENT_2A, IDENT_2B)
1043
1044    # clone bcrypt's ident aliases so they can be used here as well...
1045    ident_aliases = (lambda ident_values: dict(item for item in bcrypt.ident_aliases.items()
1046                                               if item[1] in ident_values))(ident_values)
1047    default_ident = IDENT_2B
1048
1049    #--------------------
1050    # class specific
1051    #--------------------
1052
1053    _supported_versions = set([1, 2])
1054
1055    #===================================================================
1056    # instance attrs
1057    #===================================================================
1058
1059    #: wrapper version.
1060    #: v1 -- used prior to passlib 1.7.3; performs ``bcrypt(sha256(secret), salt, cost)``
1061    #: v2 -- new in passlib 1.7.3; performs `bcrypt(sha256_hmac(salt, secret), salt, cost)``
1062    version = 2
1063
1064    #===================================================================
1065    # configuration
1066    #===================================================================
1067
1068    @classmethod
1069    def using(cls, version=None, **kwds):
1070        subcls = super(bcrypt_sha256, cls).using(**kwds)
1071        if version is not None:
1072            subcls.version = subcls._norm_version(version)
1073        ident = subcls.default_ident
1074        if subcls.version > 1 and ident != IDENT_2B:
1075            raise ValueError("bcrypt %r hashes not allowed for version %r" %
1076                             (ident, subcls.version))
1077        return subcls
1078
1079    #===================================================================
1080    # formatting
1081    #===================================================================
1082
1083    # sample hash:
1084    # $bcrypt-sha256$2a,6$/3OeRpbOf8/l6nPPRdZPp.$nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
1085    # $bcrypt-sha256$           -- prefix/identifier
1086    # 2a                        -- bcrypt variant
1087    # ,                         -- field separator
1088    # 6                         -- bcrypt work factor
1089    # $                         -- section separator
1090    # /3OeRpbOf8/l6nPPRdZPp.    -- salt
1091    # $                         -- section separator
1092    # nRiyYqPobEZGdNRBWihQhiFDh1ws1tu  -- digest
1093
1094    # XXX: we can't use .ident attr due to bcrypt code using it.
1095    #      working around that via prefix.
1096    prefix = u('$bcrypt-sha256$')
1097
1098    #: current version 2 hash format
1099    _v2_hash_re = re.compile(r"""(?x)
1100        ^
1101        [$]bcrypt-sha256[$]
1102        v=(?P<version>\d+),
1103        t=(?P<type>2b),
1104        r=(?P<rounds>\d{1,2})
1105        [$](?P<salt>[^$]{22})
1106        (?:[$](?P<digest>[^$]{31}))?
1107        $
1108        """)
1109
1110    #: old version 1 hash format
1111    _v1_hash_re = re.compile(r"""(?x)
1112        ^
1113        [$]bcrypt-sha256[$]
1114        (?P<type>2[ab]),
1115        (?P<rounds>\d{1,2})
1116        [$](?P<salt>[^$]{22})
1117        (?:[$](?P<digest>[^$]{31}))?
1118        $
1119        """)
1120
1121    @classmethod
1122    def identify(cls, hash):
1123        hash = uh.to_unicode_for_identify(hash)
1124        if not hash:
1125            return False
1126        return hash.startswith(cls.prefix)
1127
1128    @classmethod
1129    def from_string(cls, hash):
1130        hash = to_unicode(hash, "ascii", "hash")
1131        if not hash.startswith(cls.prefix):
1132            raise uh.exc.InvalidHashError(cls)
1133        m = cls._v2_hash_re.match(hash)
1134        if m:
1135            version = int(m.group("version"))
1136            if version < 2:
1137                raise uh.exc.MalformedHashError(cls)
1138        else:
1139            m = cls._v1_hash_re.match(hash)
1140            if m:
1141                version = 1
1142            else:
1143                raise uh.exc.MalformedHashError(cls)
1144        rounds = m.group("rounds")
1145        if rounds.startswith(uh._UZERO) and rounds != uh._UZERO:
1146            raise uh.exc.ZeroPaddedRoundsError(cls)
1147        return cls(
1148            version=version,
1149            ident=m.group("type"),
1150            rounds=int(rounds),
1151            salt=m.group("salt"),
1152            checksum=m.group("digest"),
1153        )
1154
1155    _v2_template = u("$bcrypt-sha256$v=2,t=%s,r=%d$%s$%s")
1156    _v1_template = u("$bcrypt-sha256$%s,%d$%s$%s")
1157
1158    def to_string(self):
1159        if self.version == 1:
1160            template = self._v1_template
1161        else:
1162            template = self._v2_template
1163        hash = template % (self.ident.strip(_UDOLLAR), self.rounds, self.salt, self.checksum)
1164        return uascii_to_str(hash)
1165
1166    #===================================================================
1167    # init
1168    #===================================================================
1169
1170    def __init__(self, version=None, **kwds):
1171        if version is not None:
1172            self.version = self._norm_version(version)
1173        super(bcrypt_sha256, self).__init__(**kwds)
1174
1175    #===================================================================
1176    # version
1177    #===================================================================
1178
1179    @classmethod
1180    def _norm_version(cls, version):
1181        if version not in cls._supported_versions:
1182            raise ValueError("%s: unknown or unsupported version: %r" % (cls.name, version))
1183        return version
1184
1185    #===================================================================
1186    # checksum
1187    #===================================================================
1188
1189    def _calc_checksum(self, secret):
1190        # NOTE: can't use digest directly, since bcrypt stops at first NULL.
1191        # NOTE: bcrypt doesn't fully mix entropy for bytes 55-72 of password
1192        #       (XXX: citation needed), so we don't want key to be > 55 bytes.
1193        #       thus, have to use base64 (44 bytes) rather than hex (64 bytes).
1194        # XXX: it's later come out that 55-72 may be ok, so later revision of bcrypt_sha256
1195        #      may switch to hex encoding, since it's simpler to implement elsewhere.
1196        if isinstance(secret, unicode):
1197            secret = secret.encode("utf-8")
1198
1199        if self.version == 1:
1200            # version 1 -- old version just ran secret through sha256(),
1201            # though this could be vulnerable to a breach attach
1202            # (c.f. issue 114); which is why v2 switched to hmac wrapper.
1203            digest = sha256(secret).digest()
1204        else:
1205            # version 2 -- running secret through HMAC keyed off salt.
1206            # this prevents known secret -> sha256 password tables from being
1207            # used to test against a bcrypt_sha256 hash.
1208            # keying off salt (instead of constant string) should minimize chances of this
1209            # colliding with existing table of hmac digest lookups as well.
1210            # NOTE: salt in this case is the "bcrypt64"-encoded value, not the raw salt bytes,
1211            #       to make things easier for parallel implementations of this hash --
1212            #       saving them the trouble of implementing a "bcrypt64" decoder.
1213            salt = self.salt
1214            if salt[-1] not in self.final_salt_chars:
1215                # forbidding salts with padding bits set, because bcrypt implementations
1216                # won't consistently hash them the same.  since we control this format,
1217                # just prevent these from even getting used.
1218                raise ValueError("invalid salt string")
1219            digest = compile_hmac("sha256", salt.encode("ascii"))(secret)
1220
1221        # NOTE: output of b64encode() uses "+/" altchars, "=" padding chars,
1222        #       and no leading/trailing whitespace.
1223        key = b64encode(digest)
1224
1225        # hand result off to normal bcrypt algorithm
1226        return super(bcrypt_sha256, self)._calc_checksum(key)
1227
1228    #===================================================================
1229    # other
1230    #===================================================================
1231
1232    def _calc_needs_update(self, **kwds):
1233        if self.version < type(self).version:
1234            return True
1235        return super(bcrypt_sha256, self)._calc_needs_update(**kwds)
1236
1237    #===================================================================
1238    # eoc
1239    #===================================================================
1240
1241#=============================================================================
1242# eof
1243#=============================================================================
1244