diff --git a/lib/hashing_passwords.py b/lib/hashing_passwords.py index 40df80e3..f340079e 100644 --- a/lib/hashing_passwords.py +++ b/lib/hashing_passwords.py @@ -19,10 +19,7 @@ import hashlib from os import urandom from base64 import b64encode, b64decode - - -# From https://github.com/mitsuhiko/python-pbkdf2 -from pbkdf2 import pbkdf2_bin +from hashlib import pbkdf2_hmac # Parameters to PBKDF2. Only affect new passwords. @@ -43,9 +40,8 @@ def make_hash(password): return 'PBKDF2${}${}${}${}'.format( HASH_FUNCTION, COST_FACTOR, - salt, - b64encode(pbkdf2_bin(password, salt, COST_FACTOR, KEY_LENGTH, - getattr(hashlib, HASH_FUNCTION)))) + salt.decode('utf-8'), + b64encode(pbkdf2_hmac(HASH_FUNCTION, password, salt, COST_FACTOR, KEY_LENGTH)).decode('utf-8')) def check_hash(password, hash_): @@ -54,13 +50,12 @@ def check_hash(password, hash_): password = password.encode('utf-8') algorithm, hash_function, cost_factor, salt, hash_a = hash_.split('$') assert algorithm == 'PBKDF2' - hash_a = b64decode(hash_a) - hash_b = pbkdf2_bin(password, salt, int(cost_factor), len(hash_a), - getattr(hashlib, hash_function)) + hash_a = b64decode(hash_a.encode('utf-8')) + hash_b = pbkdf2_hmac(hash_function, password, salt.encode('utf-8'), int(cost_factor), len(hash_a)) assert len(hash_a) == len(hash_b) # we requested this from pbkdf2_bin() # Same as "return hash_a == hash_b" but takes a constant time. # See http://carlos.bueno.org/2011/10/timing.html diff = 0 for char_a, char_b in zip(hash_a, hash_b): - diff |= ord(char_a) ^ ord(char_b) - return diff == 0 \ No newline at end of file + diff |= char_a ^ char_b + return diff == 0 diff --git a/lib/pbkdf2.py b/lib/pbkdf2.py deleted file mode 100644 index d362b852..00000000 --- a/lib/pbkdf2.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- 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. -""" -import hmac -import hashlib -from struct import Struct -from operator import xor -from itertools import starmap - - -_pack_int = Struct('>I').pack - - -def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None): - """Like :func:`pbkdf2_bin` but returns a hex encoded string.""" - return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).encode('hex') - - -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(data, None, hashfunc) - def _pseudorandom(x, mac=mac): - h = mac.copy() - h.update(x) - return bytearray(h.digest()) - buf = [] - for block in range(1, -(-keylen // mac.digest_size) + 1): - rv = u = _pseudorandom(salt + _pack_int(block)) - for i in range(iterations - 1): - u = _pseudorandom(''.join(map(chr, u))) - rv = list(starmap(xor, zip(rv, u))) - buf.extend(rv) - 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) - print() - 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()