[Zope-Checkins] SVN: Zope/branches/2.12/ 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:13:08 UTC 2012
Log message for revision 128158:
LP #1071067: Use a stronger random number generator and a constant time comparison function.
Changed:
U Zope/branches/2.12/doc/CHANGES.rst
U Zope/branches/2.12/setup.py
U Zope/branches/2.12/src/AccessControl/AuthEncoding.py
U Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py
U Zope/branches/2.12/versions.cfg
-=-
Modified: Zope/branches/2.12/doc/CHANGES.rst
===================================================================
--- Zope/branches/2.12/doc/CHANGES.rst 2012-10-31 14:10:50 UTC (rev 128157)
+++ Zope/branches/2.12/doc/CHANGES.rst 2012-10-31 14:13:07 UTC (rev 128158)
@@ -5,9 +5,12 @@
Change information for previous versions of Zope can be found at
http://docs.zope.org/zope2/releases/.
-2.12.26 (unreleased)
+2.12.26 (2012-10-31)
--------------------
+- LP #1071067: Use a stronger random number generator and a constant time
+ comparison function.
+
- LP #930812: Scrub headers a bit more.
2.12.25 (2012-09-18)
Modified: Zope/branches/2.12/setup.py
===================================================================
--- Zope/branches/2.12/setup.py 2012-10-31 14:10:50 UTC (rev 128157)
+++ Zope/branches/2.12/setup.py 2012-10-31 14:13:07 UTC (rev 128158)
@@ -16,7 +16,7 @@
from setuptools import setup, find_packages, Extension
setup(name='Zope2',
- version='2.12.26dev',
+ version='2.12.26',
url='http://www.zope.org',
license='ZPL 2.1',
description='Zope2 application server / web framework',
Modified: Zope/branches/2.12/src/AccessControl/AuthEncoding.py
===================================================================
--- Zope/branches/2.12/src/AccessControl/AuthEncoding.py 2012-10-31 14:10:50 UTC (rev 128157)
+++ Zope/branches/2.12/src/AccessControl/AuthEncoding.py 2012-10-31 14:13:07 UTC (rev 128158)
@@ -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
Modified: Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py
===================================================================
--- Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py 2012-10-31 14:10:50 UTC (rev 128157)
+++ Zope/branches/2.12/src/Products/Sessions/BrowserIdManager.py 2012-10-31 14:13:07 UTC (rev 128158)
@@ -1,5 +1,5 @@
############################################################################
-#
+#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
@@ -13,8 +13,9 @@
import binascii
from cgi import escape
+from hashlib import sha256
import logging
-import random
+import os
import re
import string
import sys
@@ -63,6 +64,29 @@
LOG = logging.getLogger('Zope.BrowserIdManager')
+# Use the system PRNG if possible
+import random
+try:
+ random = random.SystemRandom()
+ using_sysrandom = True
+except NotImplementedError:
+ using_sysrandom = False
+
+
+def _randint(start, end):
+ 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(), os.getpid())
+ ).digest())
+ return random.randint(start, end)
+
+
def constructBrowserIdManager(
self, id=BROWSERID_MANAGER_NAME, title='', idname='_ZopeId',
location=('cookies', 'form'), cookiepath='/', cookiedomain='',
@@ -558,7 +582,7 @@
return None
-def getNewBrowserId(randint=random.randint, maxint=99999999):
+def getNewBrowserId(randint=_randint, maxint=99999999):
""" Returns 19-character string browser id
'AAAAAAAABBBBBBBB'
where:
@@ -573,5 +597,4 @@
An example is: 89972317A0C3EHnUi90w
"""
- return '%08i%s' % (randint(0, maxint-1), getB64TStamp())
-
+ return '%08i%s' % (randint(0, maxint - 1), getB64TStamp())
Modified: Zope/branches/2.12/versions.cfg
===================================================================
--- Zope/branches/2.12/versions.cfg 2012-10-31 14:10:50 UTC (rev 128157)
+++ Zope/branches/2.12/versions.cfg 2012-10-31 14:13:07 UTC (rev 128158)
@@ -2,7 +2,7 @@
versions = versions
[versions]
-Zope2 =
+Zope2 = 2.12.26
Acquisition = 2.13.8
buildout.dumppickedversions = 0.4
ClientForm = 0.2.10
More information about the Zope-Checkins
mailing list