summaryrefslogtreecommitdiff
path: root/django/utils/crypto.py
diff options
context:
space:
mode:
authorPaul McMillan <Paul@McMillan.ws>2011-12-23 03:46:06 +0000
committerPaul McMillan <Paul@McMillan.ws>2011-12-23 03:46:06 +0000
commitdce820ff70f00e974afd3e6e310aa825bc55319f (patch)
tree967230e97ba3e9d38063fe0d33baffc43176f089 /django/utils/crypto.py
parenta976159db0d40d8e1bd1a7bc70f7508839e8a95d (diff)
Renovated password hashing. Many thanks to Justine Tunney for help with the initial patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17253 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils/crypto.py')
-rw-r--r--django/utils/crypto.py96
1 files changed, 95 insertions, 1 deletions
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index 95af6808fa..ff6096c6f9 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -2,10 +2,18 @@
Django's standard crypto functions and utilities.
"""
-import hashlib
import hmac
+import struct
+import hashlib
+import binascii
+import operator
from django.conf import settings
+
+trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)])
+trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)])
+
+
def salted_hmac(key_salt, value, secret=None):
"""
Returns the HMAC-SHA1 of 'value', using a key generated from key_salt and a
@@ -27,6 +35,23 @@ def salted_hmac(key_salt, value, secret=None):
# However, we need to ensure that we *always* do this.
return hmac.new(key, msg=value, digestmod=hashlib.sha1)
+
+def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
+ """
+ Returns a random string of length characters from the set of a-z, A-Z, 0-9
+ for use as a salt.
+
+ The default length of 12 with the a-z, A-Z, 0-9 character set returns
+ a 71-bit salt. log_2((26+26+10)^12) =~ 71 bits
+ """
+ import random
+ try:
+ random = random.SystemRandom()
+ except NotImplementedError:
+ pass
+ return ''.join([random.choice(allowed_chars) for i in range(length)])
+
+
def constant_time_compare(val1, val2):
"""
Returns True if the two strings are equal, False otherwise.
@@ -39,3 +64,72 @@ def constant_time_compare(val1, val2):
for x, y in zip(val1, val2):
result |= ord(x) ^ ord(y)
return result == 0
+
+
+def bin_to_long(x):
+ """
+ Convert a binary string into a long integer
+
+ This is a clever optimization for fast xor vector math
+ """
+ return long(x.encode('hex'), 16)
+
+
+def long_to_bin(x):
+ """
+ Convert a long integer into a binary string
+ """
+ hex = "%x" % (x)
+ if len(hex) % 2 == 1:
+ hex = '0' + hex
+ return binascii.unhexlify(hex)
+
+
+def fast_hmac(key, msg, digest):
+ """
+ A trimmed down version of Python's HMAC implementation
+ """
+ dig1, dig2 = digest(), digest()
+ if len(key) > dig1.block_size:
+ key = digest(key).digest()
+ key += chr(0) * (dig1.block_size - len(key))
+ dig1.update(key.translate(trans_36))
+ dig1.update(msg)
+ dig2.update(key.translate(trans_5c))
+ dig2.update(dig1.digest())
+ return dig2
+
+
+def pbkdf2(password, salt, iterations, dklen=0, digest=None):
+ """
+ Implements PBKDF2 as defined in RFC 2898, section 5.2
+
+ HMAC+SHA256 is used as the default pseudo random function.
+
+ Right now 10,000 iterations is the recommended default which takes
+ 100ms on a 2.2Ghz Core 2 Duo. This is probably the bare minimum
+ for security given 1000 iterations was recommended in 2001. This
+ code is very well optimized for CPython and is only four times
+ slower than openssl's implementation.
+ """
+ assert iterations > 0
+ if not digest:
+ digest = hashlib.sha256
+ hlen = digest().digest_size
+ if not dklen:
+ dklen = hlen
+ if dklen > (2 ** 32 - 1) * hlen:
+ raise OverflowError('dklen too big')
+ l = -(-dklen // hlen)
+ r = dklen - (l - 1) * hlen
+
+ def F(i):
+ def U():
+ u = salt + struct.pack('>I', i)
+ for j in xrange(int(iterations)):
+ u = fast_hmac(password, u, digest).digest()
+ yield bin_to_long(u)
+ return long_to_bin(reduce(operator.xor, U()))
+
+ T = [F(x) for x in range(1, l + 1)]
+ return ''.join(T[:-1]) + T[-1][:r]