diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-04-04 12:02:41 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-04-04 12:02:41 -0400 |
| commit | ff072bb815bc284574a8410ccf9bbf6358e7cffd (patch) | |
| tree | ff70f4a09680f9f4eee8f6c83333fe915db00930 | |
| parent | d95a843a40a70b36b6f25cbb960495f80b541a61 (diff) | |
| download | passlib-ff072bb815bc284574a8410ccf9bbf6358e7cffd.tar.gz | |
added atlassian_pbkdf2_sha1 - supports hash PBKDF2-based hash used by Atlassian (eg Jira's cwd_user table)
| -rw-r--r-- | docs/lib/passlib.hash.pbkdf2_digests.rst | 31 | ||||
| -rw-r--r-- | passlib/handlers/pbkdf2.py | 53 | ||||
| -rw-r--r-- | passlib/registry.py | 5 | ||||
| -rw-r--r-- | passlib/tests/test_drivers.py | 19 |
4 files changed, 106 insertions, 2 deletions
diff --git a/docs/lib/passlib.hash.pbkdf2_digests.rst b/docs/lib/passlib.hash.pbkdf2_digests.rst index d2906b5..666bd83 100644 --- a/docs/lib/passlib.hash.pbkdf2_digests.rst +++ b/docs/lib/passlib.hash.pbkdf2_digests.rst @@ -33,6 +33,11 @@ PassLib supports 5 PBKDF2-based hash schemes: password hash [#grub]_, as generated by the :command:`grub-mkpasswd-pbkdf2` command, and may be found in Grub2 configuration files. +* :class:`!atlassian_pbkdf2_sha1` provides an implementation of + the PBKDF2 based hash used by Atlassian in Jira and other products. + Note that unlike the above PBKDF2 hashes, this one uses a fixed + number of rounds. + Usage ===== These classes support both rounds and salts, @@ -46,6 +51,7 @@ Interface .. autoclass:: pbkdf2_sha512() .. autoclass:: dlitz_pbkdf2_sha1() .. autoclass:: grub_pbkdf2_sha512() +.. autoclass:: atlassian_pbkdf2_sha1() .. rst-class:: html-toggle @@ -154,6 +160,31 @@ Other PBKDF2 Hashes along with the decoded salt, and the number of rounds. The result is then encoded into hexidecimal. +:class:`!atlassian_pbkdf2_sha1` + + All of this scheme's hashes have the format :``{PKCS5S2}<data>``, + where :samp:`<data>` is a 64 character base64 encoded string; + which (when decoded), contains a 16 byte salt, + and a 32 byte checksum. + + A example hash (of ``password``) is: + + ``{PKCS5S2}DQIXJU038u4P7FdsuFTY/+35bm41kfjZa57UrdxHp2Mu3qF2uy+ooD+jF5t1tb8J`` + + Once decoded, the salt value (in hexdecimal octets) is: + + ``0d0217254d37f2ee0fec576cb854d8ff`` + + and the checksum value (in hexidecimal octets) is: + + ``edf96e6e3591f8d96b9ed4addc47a7632edea176bb2fa8a03fa3179b75b5bf09`` + + When calculating the checksum: + the password is encoded into UTF-8 if not already encoded. + Using the specified salt, and a fixed 10000 rounds, + PBKDF2-HMAC-SHA1 is used to generate a 32 byte key, + which appended to the salt and encoded in base64. + Hash Translation ---------------- Note that despite encoding and format differences, diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 825f7e4..966466b 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -4,7 +4,7 @@ #========================================================= #core from binascii import hexlify, unhexlify -from base64 import b64encode +from base64 import b64encode, b64decode import re import logging; log = logging.getLogger(__name__) from warnings import warn @@ -233,6 +233,57 @@ class dlitz_pbkdf2_sha1(ExtendedHandler): #========================================================= #========================================================= +#crowd +#========================================================= +class atlassian_pbkdf2_sha1(ExtendedHandler): + """This class implements the PBKDF2 hash used by Atlassian. + + It supports a fixed-length salt, and a fixed number of rounds. + + The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keyword: + + :param salt: + Optional salt bytes. + If specified, the length must be exactly 16 bytes. + If not specified, a salt will be autogenerated (this is recommended). + """ + name = "atlassian_pbkdf2_sha1" + setting_kwds =("salt",) + _ident = "{PKCS5S2}" + + min_salt_chars = max_salt_chars = 16 + salt_charset = ALL_BYTE_VALUES + checksum_chars = 32 + + @classmethod + def identify(cls, hash): + return bool(hash) and hash.startswith(cls._ident) + + @classmethod + def from_string(cls, hash): + if not hash: + raise ValueError("no hash specified") + ident = cls._ident + if not hash.startswith(ident): + raise ValueError("invalid %s hash" % (cls.name,)) + data = b64decode(hash[len(ident):]) + salt, chk = data[:16], data[16:] + return cls(salt=salt, checksum=chk, strict=True) + + _stub_checksum = "\x00" * 32 + + def to_string(self): + data = self.salt + (self.checksum or self._stub_checksum) + return self._ident + b64encode(data) + + def calc_checksum(self, secret): + #TODO: find out what crowd's policy is re: unicode + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + #crowd seems to use a fixed number of rounds. + return pbkdf2(secret, self.salt, 10000, 32, "hmac-sha1") + +#========================================================= #grub #========================================================= class grub_pbkdf2_sha512(ExtendedHandler): diff --git a/passlib/registry.py b/passlib/registry.py index df60874..faf4d10 100644 --- a/passlib/registry.py +++ b/passlib/registry.py @@ -74,13 +74,16 @@ _handler_locations = { #NOTE: this is a hardcoded list of the handlers built into passlib, #applications should call register_crypt_handler_location() to add their own "apr_md5_crypt": ("passlib.handlers.md5_crypt", "apr_md5_crypt"), + "atlassian_pbkdf2_sha1": + ("passlib.handlers.pbkdf2", "atlassian_pbkdf2_sha1"), "bcrypt": ("passlib.handlers.bcrypt", "bcrypt"), "bigcrypt": ("passlib.handlers.des_crypt", "bigcrypt"), "bsdi_crypt": ("passlib.handlers.des_crypt", "bsdi_crypt"), "crypt16": ("passlib.handlers.des_crypt", "crypt16"), "des_crypt": ("passlib.handlers.des_crypt", "des_crypt"), "dlitz_pbkdf2_sha1":("passlib.handlers.pbkdf2", "dlitz_pbkdf2_sha1"), - "grub_pbkdf2_sha512":("passlib.handlers.pbkdf2", "grub_pbkdf2_sha512"), + "grub_pbkdf2_sha512": + ("passlib.handlers.pbkdf2", "grub_pbkdf2_sha512"), "hex_md4": ("passlib.handlers.digests", "hex_md4"), "hex_md5": ("passlib.handlers.digests", "hex_md5"), "hex_sha1": ("passlib.handlers.digests", "hex_sha1"), diff --git a/passlib/tests/test_drivers.py b/passlib/tests/test_drivers.py index 55181c9..f9ea1dd 100644 --- a/passlib/tests/test_drivers.py +++ b/passlib/tests/test_drivers.py @@ -416,6 +416,25 @@ class Oracle11Test(HandlerCase): #========================================================= from passlib.handlers import pbkdf2 as pk2 +class AtlassianPbkdf2Sha1Test(HandlerCase): + handler = pk2.atlassian_pbkdf2_sha1 + known_correct_hashes = ( + ("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'), + (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', + "{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"), + ) + + known_malformed_hashes = [ + #bad char + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq!/p' + + #bad size, missing padding + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/' + + #bad size, with correct padding + '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/=' + ] + class Pbkdf2Sha1Test(HandlerCase): handler = pk2.pbkdf2_sha1 known_correct_hashes = ( |
