[Checkins] SVN: AccessControl/trunk/ LP #1071067: Use a stronger random number generator and a constant time comparison function.
Hano Schlichting
cvs-admin at zope.org
Wed Oct 31 14:10:05 UTC 2012
Log message for revision 128155:
LP #1071067: Use a stronger random number generator and a constant time comparison function.
Changed:
U AccessControl/trunk/CHANGES.txt
U AccessControl/trunk/setup.py
U AccessControl/trunk/src/AccessControl/AuthEncoding.py
-=-
Modified: AccessControl/trunk/CHANGES.txt
===================================================================
--- AccessControl/trunk/CHANGES.txt 2012-10-31 14:09:25 UTC (rev 128154)
+++ AccessControl/trunk/CHANGES.txt 2012-10-31 14:10:05 UTC (rev 128155)
@@ -1,9 +1,11 @@
Changelog
=========
-3.0.6 (unreleased)
+3.0.6 (2012-10-31)
------------------
+- LP #1071067: Use a stronger random number generator and a constant time
+ comparison function.
3.0.5 (2012-10-21)
------------------
Modified: AccessControl/trunk/setup.py
===================================================================
--- AccessControl/trunk/setup.py 2012-10-31 14:09:25 UTC (rev 128154)
+++ AccessControl/trunk/setup.py 2012-10-31 14:10:05 UTC (rev 128155)
@@ -16,7 +16,7 @@
from setuptools import setup, find_packages, Extension
setup(name='AccessControl',
- version='3.0.6dev',
+ version='3.0.6',
url='http://pypi.python.org/pypi/AccessControl',
license='ZPL 2.1',
description="Security framework for Zope2.",
Modified: AccessControl/trunk/src/AccessControl/AuthEncoding.py
===================================================================
--- AccessControl/trunk/src/AccessControl/AuthEncoding.py 2012-10-31 14:09:25 UTC (rev 128154)
+++ AccessControl/trunk/src/AccessControl/AuthEncoding.py 2012-10-31 14:10:05 UTC (rev 128155)
@@ -11,18 +11,59 @@
#
##############################################################################
-__version__='$Revision: 1.9 $'[11:-2]
+import binascii
+from binascii import b2a_base64, a2b_base64
+from hashlib import sha1 as sha
+from hashlib import sha256
+from os import getpid
+import time
+# Use the system PRNG if possible
+import random
try:
- from hashlib import sha1 as sha
-except:
- from sha import new as sha
+ random = random.SystemRandom()
+ using_sysrandom = True
+except NotImplementedError:
+ using_sysrandom = False
-import binascii
-from binascii import b2a_base64, a2b_base64
-from random import choice, randrange
+def _reseed():
+ 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(sha256(
+ "%s%s%s" % (random.getstate(), time.time(), getpid())
+ ).digest())
+
+def _choice(c):
+ _reseed()
+ return random.choice(c)
+
+
+def _randrange(r):
+ _reseed()
+ return random.randrange(r)
+
+
+def constant_time_compare(val1, val2):
+ """
+ Returns True if the two strings are equal, False otherwise.
+
+ The time taken is independent of the number of characters that match.
+ """
+ if len(val1) != len(val2):
+ return False
+ result = 0
+ for x, y in zip(val1, val2):
+ result |= ord(x) ^ ord(y)
+ return result == 0
+
+
class PasswordEncryptionScheme: # An Interface
def encrypt(pw):
@@ -40,12 +81,14 @@
_schemes = []
+
def registerScheme(id, s):
'''
Registers an LDAP password encoding scheme.
'''
_schemes.append((id, '{%s}' % id, s))
+
def listSchemes():
r = []
for id, prefix, scheme in _schemes:
@@ -67,7 +110,7 @@
# All 256 characters are available.
salt = ''
for n in range(7):
- salt += chr(randrange(256))
+ salt += chr(_randrange(256))
return salt
def encrypt(self, pw):
@@ -83,7 +126,7 @@
return 0
salt = ref[20:]
compare = b2a_base64(sha(attempt + salt).digest() + salt)[:-1]
- return (compare == reference)
+ return constant_time_compare(compare, reference)
registerScheme('SSHA', SSHADigestScheme())
@@ -95,7 +138,7 @@
def validate(self, reference, attempt):
compare = b2a_base64(sha(attempt).digest())[:-1]
- return (compare == reference)
+ return constant_time_compare(compare, reference)
registerScheme('SHA', SHADigestScheme())
@@ -114,14 +157,14 @@
choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789./")
- return choice(choices) + choice(choices)
+ return _choice(choices) + _choice(choices)
def encrypt(self, pw):
return crypt(pw, self.generate_salt())
def validate(self, reference, attempt):
a = crypt(attempt, reference[:2])
- return (a == reference)
+ return constant_time_compare(a, reference)
registerScheme('CRYPT', CryptDigestScheme())
@@ -144,7 +187,7 @@
def validate(self, reference, attempt):
a = self.encrypt(attempt)
- return (a == reference)
+ return constant_time_compare(a, reference)
registerScheme('MYSQL', MySQLDigestScheme())
@@ -158,8 +201,9 @@
if reference[:lp] == prefix:
return scheme.validate(reference[lp:], attempt)
# Assume cleartext.
- return (reference == attempt)
+ return constant_time_compare(reference, attempt)
+
def is_encrypted(pw):
for id, prefix, scheme in _schemes:
lp = len(prefix)
@@ -167,12 +211,13 @@
return 1
return 0
+
def pw_encrypt(pw, encoding='SSHA'):
"""Encrypt the provided plain text password using the encoding if provided
and return it in an LDAP-style representation."""
for id, prefix, scheme in _schemes:
if encoding == id:
return prefix + scheme.encrypt(pw)
- raise ValueError, 'Not supported: %s' % encoding
+ raise ValueError('Not supported: %s' % encoding)
pw_encode = pw_encrypt # backward compatibility
More information about the checkins
mailing list