summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-04-04 12:02:41 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-04-04 12:02:41 -0400
commitff072bb815bc284574a8410ccf9bbf6358e7cffd (patch)
treeff70f4a09680f9f4eee8f6c83333fe915db00930
parentd95a843a40a70b36b6f25cbb960495f80b541a61 (diff)
downloadpasslib-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.rst31
-rw-r--r--passlib/handlers/pbkdf2.py53
-rw-r--r--passlib/registry.py5
-rw-r--r--passlib/tests/test_drivers.py19
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 = (