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.py48
1 files changed, 35 insertions, 13 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 28a4eea..e17282c 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -26,7 +26,7 @@ except ImportError: #pragma: no cover - though should run whole suite w/o bcrypt
bcryptor_engine = None
#libs
from passlib.utils import safe_os_crypt, classproperty, handlers as uh, \
- h64, to_hash_str, rng, getrandstr
+ h64, to_hash_str, rng, getrandstr, bytes
#pkg
#local
@@ -36,11 +36,12 @@ __all__ = [
# base64 character->value mapping used by bcrypt.
# this is same as as H64_CHARS, but the positions are different.
-BCHARS = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+BCHARS = u"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
# last bcrypt salt char should have 4 padding bits set to 0.
# thus, only the following chars are allowed:
-BCLAST = ".Oeu"
+BSLAST = u".Oeu"
+BHLAST = u'.CGKOSWaeimquy26'
#=========================================================
#handler
@@ -96,7 +97,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
#--HasSalt--
min_salt_size = max_salt_size = 22
salt_chars = BCHARS
- #NOTE: 22nd salt char must be in BCLAST, not full BCHARS
+ #NOTE: 22nd salt char must be in BSLAST, not full BCHARS
#--HasRounds--
default_rounds = 12 #current passlib default
@@ -109,7 +110,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
#=========================================================
@classmethod
- def from_string(cls, hash):
+ def from_string(cls, hash, strict=True):
if not hash:
raise ValueError("no hash specified")
if isinstance(hash, bytes):
@@ -121,7 +122,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
raise ValueError("invalid bcrypt hash")
rounds, data = hash[len(ident):].split(u"$")
rval = int(rounds)
- if rounds != u'%02d' % (rval,):
+ if strict and rounds != u'%02d' % (rval,):
raise ValueError("invalid bcrypt hash (no rounds padding)")
salt, chk = data[:22], data[22:]
return cls(
@@ -129,7 +130,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
salt=salt,
checksum=chk or None,
ident=ident,
- strict=bool(chk),
+ strict=strict and bool(chk),
)
def to_string(self, native=True):
@@ -141,10 +142,18 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
#=========================================================
@classmethod
+ def _hash_needs_update(cls, hash, **opts):
+ if isinstance(hash, bytes):
+ hash = hash.decode("ascii")
+ if hash.startswith(u"$2a$") and hash[28] not in BSLAST:
+ return True
+ return False
+
+ @classmethod
def normhash(cls, hash):
"helper to normalize hash, correcting any bcrypt padding bits"
if cls.identify(hash):
- return cls.from_string(hash).to_string()
+ return cls.from_string(hash, strict=False).to_string()
else:
return hash
@@ -153,21 +162,34 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
assert cls.min_salt_size == cls.max_salt_size == cls.default_salt_size == 22
if salt_size is not None and salt_size != 22:
raise ValueError("bcrypt salts must be 22 characters in length")
- return getrandstr(rng, BCHARS, 21) + getrandstr(rng, BCLAST, 1)
+ return getrandstr(rng, BCHARS, 21) + getrandstr(rng, BSLAST, 1)
@classmethod
def norm_salt(cls, *args, **kwds):
salt = super(bcrypt, cls).norm_salt(*args, **kwds)
- if salt and salt[-1] not in BCLAST:
+ if salt and salt[-1] not in BSLAST:
salt = salt[:-1] + BCHARS[BCHARS.index(salt[-1]) & ~15]
- assert salt[-1] in BCLAST
+ assert salt[-1] in BSLAST
warn(
"encountered a bcrypt hash with incorrectly set padding bits; "
- "you may want to run bcrypt.normhash() "
- "to fix these hashes (see Passlib issue 25)."
+ "you may want to use bcrypt.normhash() "
+ "to fix this; see Passlib 1.5.3 changelog."
)
return salt
+ @classmethod
+ def norm_checksum(cls, *args, **kwds):
+ checksum = super(bcrypt, cls).norm_checksum(*args, **kwds)
+ if checksum and checksum[-1] not in BHLAST:
+ checksum = checksum[:-1] + BCHARS[BCHARS.index(checksum[-1]) & ~3]
+ assert checksum[-1] in BHLAST
+ warn(
+ "encountered a bcrypt hash with incorrectly set padding bits; "
+ "you may want to use bcrypt.normhash() "
+ "to fix this; see Passlib 1.5.3 changelog."
+ )
+ return checksum
+
#=========================================================
#primary interface
#=========================================================