From 1525874238fd705ec17a066291935a9316bd3044 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Thu, 23 Feb 2012 22:51:14 +0000 Subject: [PATCH] Improved get_random_string(). Improved the behavior of get_random_string to re-seed itself each time it is called if the system does not have a secure random number generator. This will change the properties of the random string produced, but will be unpredictable to an attacker. git-svn-id: http://code.djangoproject.com/svn/django/trunk@17581 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/crypto.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/django/utils/crypto.py b/django/utils/crypto.py index a54c455e1b..44b7faf492 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -7,12 +7,18 @@ import struct import hashlib import binascii import operator +import time +# Use the system PRNG if possible import random try: random = random.SystemRandom() + using_sysrandom = True except NotImplementedError: - pass + import warnings + warnings.warn('A secure pseudo-random number generator is not available ' + 'on your system. Falling back to Mersenne Twister.') + using_sysrandom = False from django.conf import settings @@ -47,11 +53,25 @@ def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): """ - Returns a random string of length characters from the set of a-z, A-Z, 0-9. + Returns a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns a 71-bit value. log_2((26+26+10)^12) =~ 71 bits """ + if not using_sysrandom: + # This is ugly, and a hack, but it makes things better than + # the alternative of predictability. This re-seeds the PRNG + # using a value that is hard for an attacker to predict, every + # time a random string is required. This may change the + # properties of the chosen random sequence slightly, but this + # is better than absolute predictability. + random.seed( + hashlib.sha256( + "%s%s%s" % ( + random.getstate(), + time.time(), + settings.SECRET_KEY) + ).digest()) return ''.join([random.choice(allowed_chars) for i in range(length)])