diff options
Diffstat (limited to 'passlib/handlers/django.py')
-rw-r--r-- | passlib/handlers/django.py | 72 |
1 files changed, 70 insertions, 2 deletions
diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py index b643a66..83e8860 100644 --- a/passlib/handlers/django.py +++ b/passlib/handlers/django.py @@ -4,12 +4,14 @@ #============================================================================= # core from base64 import b64encode -from hashlib import md5, sha1 +from binascii import hexlify +from hashlib import md5, sha1, sha256 import re import logging; log = logging.getLogger(__name__) from warnings import warn # site # pkg +from passlib.hash import bcrypt from passlib.utils import to_unicode, classproperty from passlib.utils.compat import b, bytes, str_to_uascii, uascii_to_str, unicode, u from passlib.utils.pbkdf2 import pbkdf2 @@ -28,6 +30,8 @@ __all__ = [ #============================================================================= # lazy imports & constants #============================================================================= + +# imported by django_des_crypt._calc_checksum() des_crypt = None def _import_des_crypt(): @@ -162,7 +166,7 @@ class django_salted_md5(DjangoSaltedHash): secret = secret.encode("utf-8") return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest()) -django_bcrypt = uh.PrefixWrapper("django_bcrypt", "bcrypt", +django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt, prefix=u('bcrypt$'), ident=u("bcrypt$"), # NOTE: this docstring is duplicated in the docs, since sphinx # seems to be having trouble reading it via autodata:: @@ -181,6 +185,70 @@ django_bcrypt = uh.PrefixWrapper("django_bcrypt", "bcrypt", """) django_bcrypt.django_name = "bcrypt" +class django_bcrypt_sha256(bcrypt): + """This class implements Django 1.6's Bcrypt+SHA256 hash, and follows the :ref:`password-hash-api`. + + It supports a variable-length salt, and a variable number of rounds. + + While the algorithm and format is somewhat different, + the api and options for this hash are identical to :class:`!bcrypt` itself, + see :doc:`/lib/passlib.hash.bcrypt` for more details. + + .. versionadded:: 1.6.2 + """ + name = "django_bcrypt_sha256" + django_name = "bcrypt_sha256" + _digest = sha256 + + # NOTE: django bcrypt ident locked at "$2a$", so omitting 'ident' support. + setting_kwds = ("salt", "rounds") + + # sample hash: + # bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu + + # XXX: we can't use .ident attr due to bcrypt code using it. + # working around that via django_prefix + django_prefix = u('bcrypt_sha256$') + + @classmethod + def identify(cls, hash): + hash = uh.to_unicode_for_identify(hash) + if not hash: + return False + return hash.startswith(cls.django_prefix) + + @classmethod + def from_string(cls, hash): + hash = to_unicode(hash, "ascii", "hash") + if not hash.startswith(cls.django_prefix): + raise uh.exc.InvalidHashError(cls) + bhash = hash[len(cls.django_prefix):] + if not bhash.startswith("$2"): + raise uh.exc.MalformedHashError(cls) + return super(django_bcrypt_sha256, cls).from_string(bhash) + + def __init__(self, **kwds): + if 'ident' in kwds and kwds.get("use_defaults"): + raise TypeError("%s does not support the ident keyword" % + self.__class__.__name__) + return super(django_bcrypt_sha256, self).__init__(**kwds) + + def to_string(self): + bhash = super(django_bcrypt_sha256, self).to_string() + return uascii_to_str(self.django_prefix) + bhash + + def _calc_checksum(self, secret): + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + secret = hexlify(self._digest(secret).digest()) + return super(django_bcrypt_sha256, self)._calc_checksum(secret) + + # patch set_backend so it modifies bcrypt class, not this one... + # else it would clobber our _calc_checksum() wrapper above. + @classmethod + def set_backend(cls, *args, **kwds): + return bcrypt.set_backend(*args, **kwds) + class django_pbkdf2_sha256(DjangoVariableHash): """This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`. |