summaryrefslogtreecommitdiff
path: root/legacy_passwords.py
diff options
context:
space:
mode:
authorDonald Stufft <donald@stufft.io>2013-02-12 03:36:06 -0500
committerDonald Stufft <donald@stufft.io>2013-02-12 03:36:06 -0500
commit41362f694af6851b1f3428ba38ebc495e60cad73 (patch)
treec10119bd7343ff498df9996420c009ade99464de /legacy_passwords.py
parent622d5b0defc2c08e58a5544c0423cc7d98538cf3 (diff)
downloaddecorator-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.py67
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)