summaryrefslogtreecommitdiff
path: root/passlib/handlers/bcrypt.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/handlers/bcrypt.py')
-rw-r--r--passlib/handlers/bcrypt.py197
1 files changed, 40 insertions, 157 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index b83b110..9a10f9e 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -10,7 +10,6 @@ TODO:
#=============================================================================
# imports
#=============================================================================
-from __future__ import with_statement, absolute_import
# core
from base64 import b64encode
from hashlib import sha256
@@ -20,8 +19,6 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
# site
_bcrypt = None # dynamically imported by _load_backend_bcrypt()
-_pybcrypt = None # dynamically imported by _load_backend_pybcrypt()
-_bcryptor = None # dynamically imported by _load_backend_bcryptor()
# pkg
_builtin_bcrypt = None # dynamically imported by _load_backend_builtin()
from passlib.crypto.digest import compile_hmac
@@ -30,8 +27,6 @@ from passlib.utils import safe_crypt, repeat_string, to_bytes, parse_version, \
rng, getrandstr, test_crypt, to_unicode, \
utf8_truncate, utf8_repeat_string, crypt_accepts_bytes
from passlib.utils.binary import bcrypt64
-from passlib.utils.compat import get_unbound_method_function
-from passlib.utils.compat import u, uascii_to_str, unicode, str_to_uascii, PY3, error_from
import passlib.utils.handlers as uh
# local
@@ -42,11 +37,11 @@ __all__ = [
#=============================================================================
# support funcs & constants
#=============================================================================
-IDENT_2 = u("$2$")
-IDENT_2A = u("$2a$")
-IDENT_2X = u("$2x$")
-IDENT_2Y = u("$2y$")
-IDENT_2B = u("$2b$")
+IDENT_2 = u"$2$"
+IDENT_2A = u"$2a$"
+IDENT_2X = u"$2x$"
+IDENT_2Y = u"$2y$"
+IDENT_2B = u"$2b$"
_BNULL = b'\x00'
# reference hash of "test", used in various self-checks
@@ -122,8 +117,8 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
#--------------------
default_ident = IDENT_2B
ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y, IDENT_2B)
- ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A, u("2y"): IDENT_2Y,
- u("2b"): IDENT_2B}
+ ident_aliases = {u"2": IDENT_2, u"2a": IDENT_2A, u"2y": IDENT_2Y,
+ u"2b": IDENT_2B}
#--------------------
# HasSalt
@@ -171,9 +166,9 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
if ident == IDENT_2X:
raise ValueError("crypt_blowfish's buggy '2x' hashes are not "
"currently supported")
- rounds_str, data = tail.split(u("$"))
+ rounds_str, data = tail.split(u"$")
rounds = int(rounds_str)
- if rounds_str != u('%02d') % (rounds,):
+ if rounds_str != u'%02d' % (rounds,):
raise uh.exc.MalformedHashError(cls, "malformed cost field")
salt, chk = data[:22], data[22:]
return cls(
@@ -184,15 +179,15 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
)
def to_string(self):
- hash = u("%s%02d$%s%s") % (self.ident, self.rounds, self.salt, self.checksum)
- return uascii_to_str(hash)
+ hash = u"%s%02d$%s%s" % (self.ident, self.rounds, self.salt, self.checksum)
+ return hash
# NOTE: this should be kept separate from to_string()
# so that bcrypt_sha256() can still use it, while overriding to_string()
def _get_config(self, ident):
"""internal helper to prepare config string for backends"""
- config = u("%s%02d$%s") % (ident, self.rounds, self.salt)
- return uascii_to_str(config)
+ config = u"%s%02d$%s" % (ident, self.rounds, self.salt)
+ return config
#===================================================================
# migration
@@ -211,7 +206,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
# TODO: try to detect incorrect 8bit/wraparound hashes using kwds.get("secret")
# hand off to base implementation, so HasRounds can check rounds value.
- return super(_BcryptCommon, cls).needs_update(hash, **kwds)
+ return super().needs_update(hash, **kwds)
#===================================================================
# specialized salt generation - fixes passlib issue 25
@@ -229,12 +224,12 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
def _generate_salt(cls):
# generate random salt as normal,
# but repair last char so the padding bits always decode to zero.
- salt = super(_BcryptCommon, cls)._generate_salt()
+ salt = super()._generate_salt()
return bcrypt64.repair_unused(salt)
@classmethod
def _norm_salt(cls, salt, **kwds):
- salt = super(_BcryptCommon, cls)._norm_salt(salt, **kwds)
+ salt = super()._norm_salt(salt, **kwds)
assert salt is not None, "HasSalt didn't generate new salt!"
changed, salt = bcrypt64.check_repair_unused(salt)
if changed:
@@ -248,7 +243,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
return salt
def _norm_checksum(self, checksum, relaxed=False):
- checksum = super(_BcryptCommon, self)._norm_checksum(checksum, relaxed=relaxed)
+ checksum = super()._norm_checksum(checksum, relaxed=relaxed)
changed, checksum = bcrypt64.check_repair_unused(checksum)
if changed:
warn(
@@ -294,8 +289,6 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
verify = mixin_cls.verify
err_types = (ValueError, uh.exc.MissingBackendError)
- if _bcryptor:
- err_types += (_bcryptor.engine.SaltError,)
def safe_verify(secret, hash):
"""verify() wrapper which traps 'unknown identifier' errors"""
@@ -309,7 +302,6 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
# - InternalBackendError if crypt fails for unknown reason
# (trapped below so we can debug it)
# pybcrypt, bcrypt -- raises ValueError
- # bcryptor -- raises bcryptor.engine.SaltError
return NotImplemented
except uh.exc.InternalBackendError:
# _calc_checksum() code may also throw CryptBackendError
@@ -490,7 +482,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
def _norm_digest_args(cls, secret, ident, new=False):
# make sure secret is unicode
require_valid_utf8_bytes = cls._require_valid_utf8_bytes
- if isinstance(secret, unicode):
+ if isinstance(secret, str):
secret = secret.encode("utf-8")
elif require_valid_utf8_bytes:
# if backend requires utf8 bytes (os_crypt);
@@ -510,7 +502,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents,
cls._check_truncate_policy(secret)
# NOTE: especially important to forbid NULLs for bcrypt, since many
- # backends (bcryptor, bcrypt) happily accept them, and then
+ # backends (bcrypt) happily accept them, and then
# silently truncate the password at first NULL they encounter!
if _BNULL in secret:
raise uh.exc.NullPasswordError(cls)
@@ -591,7 +583,7 @@ class _NoBackend(_BcryptCommon):
self._stub_requires_backend()
# NOTE: have to use super() here so that we don't recursively
# call subclass's wrapped _calc_checksum, e.g. bcrypt_sha256._calc_checksum
- return super(bcrypt, self)._calc_checksum(secret)
+ return super()._calc_checksum(secret)
#===================================================================
# eoc
@@ -630,7 +622,7 @@ class _BcryptBackend(_BcryptCommon):
# # below method has a few edge cases where it chokes though.
# @classmethod
# def verify(cls, secret, hash):
- # if isinstance(hash, unicode):
+ # if isinstance(hash, str):
# hash = hash.encode("ascii")
# ident = hash[:hash.index(b"$", 1)+1].decode("ascii")
# if ident not in cls.ident_values:
@@ -650,7 +642,7 @@ class _BcryptBackend(_BcryptCommon):
# returns ascii bytes
secret, ident = self._prepare_digest_args(secret)
config = self._get_config(ident)
- if isinstance(config, unicode):
+ if isinstance(config, str):
config = config.encode("ascii")
hash = _bcrypt.hashpw(secret, config)
assert isinstance(hash, bytes)
@@ -658,113 +650,6 @@ class _BcryptBackend(_BcryptCommon):
raise uh.exc.CryptBackendError(self, config, hash, source="`bcrypt` package")
return hash[-31:].decode("ascii")
-#-----------------------------------------------------------------------
-# bcryptor backend
-#-----------------------------------------------------------------------
-class _BcryptorBackend(_BcryptCommon):
- """
- backend which uses 'bcryptor' package
- """
-
- @classmethod
- def _load_backend_mixin(mixin_cls, name, dryrun):
- # try to import bcryptor
- global _bcryptor
- try:
- import bcryptor as _bcryptor
- except ImportError: # pragma: no cover
- return False
-
- # deprecated as of 1.7.2
- if not dryrun:
- warn("Support for `bcryptor` is deprecated, and will be removed in Passlib 1.8; "
- "Please use `pip install bcrypt` instead", DeprecationWarning)
-
- return mixin_cls._finalize_backend_mixin(name, dryrun)
-
- def _calc_checksum(self, secret):
- # bcryptor behavior:
- # py2: unicode secret/hash encoded as ascii bytes before use,
- # bytes taken as-is; returns ascii bytes.
- # py3: not supported
- secret, ident = self._prepare_digest_args(secret)
- config = self._get_config(ident)
- hash = _bcryptor.engine.Engine(False).hash_key(secret, config)
- if not hash.startswith(config) or len(hash) != len(config) + 31:
- raise uh.exc.CryptBackendError(self, config, hash, source="bcryptor library")
- return str_to_uascii(hash[-31:])
-
-#-----------------------------------------------------------------------
-# pybcrypt backend
-#-----------------------------------------------------------------------
-class _PyBcryptBackend(_BcryptCommon):
- """
- backend which uses 'pybcrypt' package
- """
-
- #: classwide thread lock used for pybcrypt < 0.3
- _calc_lock = None
-
- @classmethod
- def _load_backend_mixin(mixin_cls, name, dryrun):
- # try to import pybcrypt
- global _pybcrypt
- if not _detect_pybcrypt():
- # not installed, or bcrypt installed instead
- return False
- try:
- import bcrypt as _pybcrypt
- except ImportError: # pragma: no cover
- # XXX: should we raise AssertionError here? (if get here, _detect_pybcrypt() is broken)
- return False
-
- # deprecated as of 1.7.2
- if not dryrun:
- warn("Support for `py-bcrypt` is deprecated, and will be removed in Passlib 1.8; "
- "Please use `pip install bcrypt` instead", DeprecationWarning)
-
- # determine pybcrypt version
- try:
- version = _pybcrypt._bcrypt.__version__
- except:
- log.warning("(trapped) error reading pybcrypt version", exc_info=True)
- version = "<unknown>"
- log.debug("detected 'pybcrypt' backend, version %r", version)
-
- # return calc function based on version
- vinfo = parse_version(version) or (0, 0)
- if vinfo < (0, 3):
- warn("py-bcrypt %s has a major security vulnerability, "
- "you should upgrade to py-bcrypt 0.3 immediately."
- % version, uh.exc.PasslibSecurityWarning)
- if mixin_cls._calc_lock is None:
- import threading
- mixin_cls._calc_lock = threading.Lock()
- mixin_cls._calc_checksum = get_unbound_method_function(mixin_cls._calc_checksum_threadsafe)
-
- return mixin_cls._finalize_backend_mixin(name, dryrun)
-
- def _calc_checksum_threadsafe(self, secret):
- # as workaround for pybcrypt < 0.3's concurrency issue,
- # we wrap everything in a thread lock. as long as bcrypt is only
- # used through passlib, this should be safe.
- with self._calc_lock:
- return self._calc_checksum_raw(secret)
-
- def _calc_checksum_raw(self, secret):
- # py-bcrypt behavior:
- # py2: unicode secret/hash encoded as ascii bytes before use,
- # bytes taken as-is; returns ascii bytes.
- # py3: unicode secret encoded as utf-8 bytes,
- # hash encoded as ascii bytes, returns ascii unicode.
- secret, ident = self._prepare_digest_args(secret)
- config = self._get_config(ident)
- hash = _pybcrypt.hashpw(secret, config)
- if not hash.startswith(config) or len(hash) != len(config) + 31:
- raise uh.exc.CryptBackendError(self, config, hash, source="pybcrypt library")
- return str_to_uascii(hash[-31:])
-
- _calc_checksum = _calc_checksum_raw
#-----------------------------------------------------------------------
# os crypt backend
@@ -810,14 +695,14 @@ class _OsCryptBackend(_BcryptCommon):
# instead of returning None? (would save re-detecting what went wrong)
# XXX: isn't secret ALWAYS bytes at this point?
#
- if PY3 and isinstance(secret, bytes):
+ if isinstance(secret, bytes):
try:
secret.decode("utf-8")
except UnicodeDecodeError:
- raise error_from(uh.exc.PasswordValueError(
+ raise uh.exc.PasswordValueError(
"python3 crypt.crypt() ony supports bytes passwords using UTF8; "
"passlib recommends running `pip install bcrypt` for general bcrypt support.",
- ), None)
+ ) from None
#
# else crypt() call failed for unknown reason.
@@ -943,7 +828,7 @@ class bcrypt(_NoBackend, _BcryptCommon):
# in order to load the appropriate backend.
#: list of potential backends
- backends = ("bcrypt", "pybcrypt", "bcryptor", "os_crypt", "builtin")
+ backends = ("bcrypt", "os_crypt", "builtin")
#: flag that this class's bases should be modified by SubclassBackendMixin
_backend_mixin_target = True
@@ -952,8 +837,6 @@ class bcrypt(_NoBackend, _BcryptCommon):
_backend_mixin_map = {
None: _NoBackend,
"bcrypt": _BcryptBackend,
- "pybcrypt": _PyBcryptBackend,
- "bcryptor": _BcryptorBackend,
"os_crypt": _OsCryptBackend,
"builtin": _BuiltinBackend,
}
@@ -965,7 +848,7 @@ class bcrypt(_NoBackend, _BcryptCommon):
#=============================================================================
# variants
#=============================================================================
-_UDOLLAR = u("$")
+_UDOLLAR = u"$"
# XXX: it might be better to have all the bcrypt variants share a common base class,
# and have the (django_)bcrypt_sha256 wrappers just proxy bcrypt instead of subclassing it.
@@ -983,17 +866,17 @@ class _wrapped_bcrypt(bcrypt):
# def hash(cls, secret, **kwds):
# # bypass bcrypt backend overriding this method
# # XXX: would wrapping bcrypt make this easier than subclassing it?
- # return super(_BcryptCommon, cls).hash(secret, **kwds)
+ # return super().hash(secret, **kwds)
#
# @classmethod
# def verify(cls, secret, hash):
# # bypass bcrypt backend overriding this method
- # return super(_BcryptCommon, cls).verify(secret, hash)
+ # return super().verify(secret, hash)
#
# @classmethod
# def genhash(cls, secret, hash):
# # bypass bcrypt backend overriding this method
- # return super(_BcryptCommon, cls).genhash(secret, hash)
+ # return super().genhash(secret, hash)
@classmethod
def _check_truncate_policy(cls, secret):
@@ -1050,7 +933,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
# class specific
#--------------------
- _supported_versions = set([1, 2])
+ _supported_versions = {1, 2}
#===================================================================
# instance attrs
@@ -1067,7 +950,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
@classmethod
def using(cls, version=None, **kwds):
- subcls = super(bcrypt_sha256, cls).using(**kwds)
+ subcls = super().using(**kwds)
if version is not None:
subcls.version = subcls._norm_version(version)
ident = subcls.default_ident
@@ -1093,7 +976,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
# XXX: we can't use .ident attr due to bcrypt code using it.
# working around that via prefix.
- prefix = u('$bcrypt-sha256$')
+ prefix = u'$bcrypt-sha256$'
#: current version 2 hash format
_v2_hash_re = re.compile(r"""(?x)
@@ -1152,8 +1035,8 @@ class bcrypt_sha256(_wrapped_bcrypt):
checksum=m.group("digest"),
)
- _v2_template = u("$bcrypt-sha256$v=2,t=%s,r=%d$%s$%s")
- _v1_template = u("$bcrypt-sha256$%s,%d$%s$%s")
+ _v2_template = u"$bcrypt-sha256$v=2,t=%s,r=%d$%s$%s"
+ _v1_template = u"$bcrypt-sha256$%s,%d$%s$%s"
def to_string(self):
if self.version == 1:
@@ -1161,7 +1044,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
else:
template = self._v2_template
hash = template % (self.ident.strip(_UDOLLAR), self.rounds, self.salt, self.checksum)
- return uascii_to_str(hash)
+ return hash
#===================================================================
# init
@@ -1170,7 +1053,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
def __init__(self, version=None, **kwds):
if version is not None:
self.version = self._norm_version(version)
- super(bcrypt_sha256, self).__init__(**kwds)
+ super().__init__(**kwds)
#===================================================================
# version
@@ -1193,7 +1076,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
# thus, have to use base64 (44 bytes) rather than hex (64 bytes).
# XXX: it's later come out that 55-72 may be ok, so later revision of bcrypt_sha256
# may switch to hex encoding, since it's simpler to implement elsewhere.
- if isinstance(secret, unicode):
+ if isinstance(secret, str):
secret = secret.encode("utf-8")
if self.version == 1:
@@ -1223,7 +1106,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
key = b64encode(digest)
# hand result off to normal bcrypt algorithm
- return super(bcrypt_sha256, self)._calc_checksum(key)
+ return super()._calc_checksum(key)
#===================================================================
# other
@@ -1232,7 +1115,7 @@ class bcrypt_sha256(_wrapped_bcrypt):
def _calc_needs_update(self, **kwds):
if self.version < type(self).version:
return True
- return super(bcrypt_sha256, self)._calc_needs_update(**kwds)
+ return super()._calc_needs_update(**kwds)
#===================================================================
# eoc