diff options
author | Donald Stufft <donald@stufft.io> | 2013-02-12 03:36:06 -0500 |
---|---|---|
committer | Donald Stufft <donald@stufft.io> | 2013-02-12 03:36:06 -0500 |
commit | 41362f694af6851b1f3428ba38ebc495e60cad73 (patch) | |
tree | c10119bd7343ff498df9996420c009ade99464de /legacy_passwords.py | |
parent | 622d5b0defc2c08e58a5544c0423cc7d98538cf3 (diff) | |
download | decorator-41362f694af6851b1f3428ba38ebc495e60cad73.tar.gz |
Include a migration path for moving legacy users to a stronger hash
* Includes a method for hashing the sha1 passwords with bcrypt to
increase their security
* bcrypt_sha1 will upgrade to standard bcrypt as per usual with
passlib
* Provides a script that migrates 20 users at a time to bcrypt_sha1
Migration script was modified from one written by Giovanni Bajo
Diffstat (limited to 'legacy_passwords.py')
-rw-r--r-- | legacy_passwords.py | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/legacy_passwords.py b/legacy_passwords.py new file mode 100644 index 0000000..1191488 --- /dev/null +++ b/legacy_passwords.py @@ -0,0 +1,67 @@ +import base64 +import hashlib + +import passlib.exc as exc +import passlib.utils.handlers as uh + +from passlib.registry import get_crypt_handler +from passlib.utils import to_unicode +from passlib.utils.compat import uascii_to_str + + +passlib_bcrypt = get_crypt_handler("bcrypt") + + +class bcrypt_sha1(uh.StaticHandler): + + name = "bcrypt_sha1" + _hash_prefix = u"$bcrypt_sha1$" + + def _calc_checksum(self, secret): + # Hash the secret with sha1 first + secret = hashlib.sha1(secret).hexdigest() + + # Hash it with bcrypt + return passlib_bcrypt.encrypt(secret) + + def to_string(self): + assert self.checksum is not None + return uascii_to_str(self._hash_prefix + base64.b64encode(self.checksum)) + + @classmethod + def from_string(cls, hash, **context): + # default from_string() which strips optional prefix, + # and passes rest unchanged as checksum value. + hash = to_unicode(hash, "ascii", "hash") + hash = cls._norm_hash(hash) + # could enable this for extra strictness + ##pat = cls._hash_regex + ##if pat and pat.match(hash) is None: + ## raise ValueError("not a valid %s hash" % (cls.name,)) + prefix = cls._hash_prefix + if prefix: + if hash.startswith(prefix): + hash = hash[len(prefix):] + else: + raise exc.InvalidHashError(cls) + + # Decode the base64 stored actual hash + hash = unicode(base64.b64decode(hash)) + + return cls(checksum=hash, **context) + + @classmethod + def verify(cls, secret, hash, **context): + # NOTE: classes with multiple checksum encodings should either + # override this method, or ensure that from_string() / _norm_checksum() + # ensures .checksum always uses a single canonical representation. + uh.validate_secret(secret) + self = cls.from_string(hash, **context) + chk = self.checksum + if chk is None: + raise exc.MissingDigestError(cls) + + # Actually use the verify from passlib_bcrypt after hashing the secret + # with sha1 + secret = hashlib.sha1(secret).hexdigest() + return passlib_bcrypt.verify(secret, chk) |