| # -*- coding: utf-8 -*- |
| """ |
| pbkdf2 |
| ~~~~~~ |
| |
| This module implements pbkdf2 for Python. It also has some basic |
| tests that ensure that it works. The implementation is straightforward |
| and uses stdlib only stuff and can be easily be copy/pasted into |
| your favourite application. |
| |
| Use this as replacement for bcrypt that does not need a c implementation |
| of a modified blowfish crypto algo. |
| |
| Example usage: |
| |
| >>> pbkdf2_hex('what i want to hash', 'the random salt') |
| 'fa7cc8a2b0a932f8e6ea42f9787e9d36e592e0c222ada6a9' |
| |
| How to use this: |
| |
| 1. Use a constant time string compare function to compare the stored hash |
| with the one you're generating:: |
| |
| def safe_str_cmp(a, b): |
| if len(a) != len(b): |
| return False |
| rv = 0 |
| for x, y in izip(a, b): |
| rv |= ord(x) ^ ord(y) |
| return rv == 0 |
| |
| 2. Use `os.urandom` to generate a proper salt of at least 8 byte. |
| Use a unique salt per hashed password. |
| |
| 3. Store ``algorithm$salt:costfactor$hash`` in the database so that |
| you can upgrade later easily to a different algorithm if you need |
| one. For instance ``PBKDF2-256$thesalt:10000$deadbeef...``. |
| |
| |
| :copyright: (c) Copyright 2011 by Armin Ronacher. |
| :license: BSD, see LICENSE for more details. |
| """ |
| from binascii import hexlify |
| import hmac |
| import hashlib |
| import sys |
| from struct import Struct |
| from operator import xor |
| from itertools import starmap |
| |
| PY3 = sys.version_info[0] == 3 |
| |
| if not PY3: |
| from itertools import izip as zip |
| |
| if PY3: |
| text_type = str |
| else: |
| text_type = unicode |
| |
| |
| _pack_int = Struct(">I").pack |
| |
| |
| def bytes_(s, encoding="utf8", errors="strict"): |
| if isinstance(s, text_type): |
| return s.encode(encoding, errors) |
| return s |
| |
| |
| def hexlify_(s): |
| if PY3: |
| return str(hexlify(s), encoding="utf8") |
| else: |
| return s.encode("hex") |
| |
| |
| def range_(*args): |
| if PY3: |
| return range(*args) |
| else: |
| return xrange(*args) |
| |
| |
| def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None): |
| """Like :func:`pbkdf2_bin` but returns a hex encoded string.""" |
| return hexlify_(pbkdf2_bin(data, salt, iterations, keylen, hashfunc)) |
| |
| |
| def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None): |
| """Returns a binary digest for the PBKDF2 hash algorithm of `data` |
| with the given `salt`. It iterates `iterations` time and produces a |
| key of `keylen` bytes. By default SHA-1 is used as hash function, |
| a different hashlib `hashfunc` can be provided. |
| """ |
| hashfunc = hashfunc or hashlib.sha1 |
| mac = hmac.new(bytes_(data), None, hashfunc) |
| |
| def _pseudorandom(x, mac=mac): |
| h = mac.copy() |
| h.update(bytes_(x)) |
| if PY3: |
| return [x for x in h.digest()] |
| else: |
| return map(ord, h.digest()) |
| |
| buf = [] |
| for block in range_(1, -(-keylen // mac.digest_size) + 1): |
| rv = u = _pseudorandom(bytes_(salt) + _pack_int(block)) |
| for i in range_(iterations - 1): |
| if PY3: |
| u = _pseudorandom(bytes(u)) |
| else: |
| u = _pseudorandom("".join(map(chr, u))) |
| rv = starmap(xor, zip(rv, u)) |
| buf.extend(rv) |
| if PY3: |
| return bytes(buf)[:keylen] |
| else: |
| return "".join(map(chr, buf))[:keylen] |
| |
| |
| def test(): |
| failed = [] |
| |
| def check(data, salt, iterations, keylen, expected): |
| rv = pbkdf2_hex(data, salt, iterations, keylen) |
| if rv != expected: |
| print("Test failed:") |
| print(" Expected: %s" % expected) |
| print(" Got: %s" % rv) |
| print(" Parameters:") |
| print(" data=%s" % data) |
| print(" salt=%s" % salt) |
| print(" iterations=%d" % iterations) |
| failed.append(1) |
| |
| # From RFC 6070 |
| check("password", "salt", 1, 20, "0c60c80f961f0e71f3a9b524af6012062fe037a6") |
| check("password", "salt", 2, 20, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957") |
| check("password", "salt", 4096, 20, "4b007901b765489abead49d926f721d065a429c1") |
| check( |
| "passwordPASSWORDpassword", |
| "saltSALTsaltSALTsaltSALTsaltSALTsalt", |
| 4096, |
| 25, |
| "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038", |
| ) |
| check("pass\x00word", "sa\x00lt", 4096, 16, "56fa6aa75548099dcc37d7f03425e0c3") |
| # This one is from the RFC but it just takes for ages |
| ##check('password', 'salt', 16777216, 20, |
| ## 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984') |
| |
| # From Crypt-PBKDF2 |
| check( |
| "password", "ATHENA.MIT.EDUraeburn", 1, 16, "cdedb5281bb2f801565a1122b2563515" |
| ) |
| check( |
| "password", |
| "ATHENA.MIT.EDUraeburn", |
| 1, |
| 32, |
| "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837", |
| ) |
| check( |
| "password", "ATHENA.MIT.EDUraeburn", 2, 16, "01dbee7f4a9e243e988b62c73cda935d" |
| ) |
| check( |
| "password", |
| "ATHENA.MIT.EDUraeburn", |
| 2, |
| 32, |
| "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86", |
| ) |
| check( |
| "password", |
| "ATHENA.MIT.EDUraeburn", |
| 1200, |
| 32, |
| "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13", |
| ) |
| check( |
| "X" * 64, |
| "pass phrase equals block size", |
| 1200, |
| 32, |
| "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1", |
| ) |
| check( |
| "X" * 65, |
| "pass phrase exceeds block size", |
| 1200, |
| 32, |
| "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a", |
| ) |
| |
| raise SystemExit(bool(failed)) |
| |
| |
| if __name__ == "__main__": |
| test() |