"""Module containing a cryptographic-quality source of randomness and other cryptographically useful functionality Python 2.4 needs no external support for this module, nor does Python 2.3 on a system with /dev/urandom. Other configurations will need a quality source of random bytes and access to a function that will convert binary strings to long integers. This module will work with the Python Cryptography Toolkit (pycrypto) if it is present. pycrypto can be found with a search engine, but is currently found at: http://www.amk.ca/python/code/crypto """ __all__ = ['randrange', 'hmacSha1', 'sha1', 'randomString', 'binaryToLong', 'longToBinary', 'longToBase64', 'base64ToLong'] import hmac import os import random import sha from openid.oidutil import toBase64, fromBase64 try: from Crypto.Util.number import long_to_bytes, bytes_to_long except ImportError: import pickle try: # Check Python compatiblity by raising an exception on import # if the needed functionality is not present. Present in # Python >= 2.3 pickle.encode_long pickle.decode_long except AttributeError: raise ImportError( 'No functionality for serializing long integers found') # Present in Python >= 2.4 try: reversed except NameError: def reversed(seq): return map(seq.__getitem__, xrange(len(seq) - 1, -1, -1)) def longToBinary(l): if l == 0: return '\x00' return ''.join(reversed(pickle.encode_long(l))) def binaryToLong(s): return pickle.decode_long(''.join(reversed(s))) else: # We have pycrypto def longToBinary(l): if l < 0: raise ValueError('This function only supports positive integers') bytes = long_to_bytes(l) if ord(bytes[0]) > 127: return '\x00' + bytes else: return bytes def binaryToLong(bytes): if not bytes: raise ValueError('Empty string passed to strToLong') if ord(bytes[0]) > 127: raise ValueError('This function only supports positive integers') return bytes_to_long(bytes) # A cryptographically safe source of random bytes try: getBytes = os.urandom except AttributeError: try: from Crypto.Util.randpool import RandomPool except ImportError: # Fall back on /dev/urandom, if present. It would be nice to # have Windows equivalent here, but for now, require pycrypto # on Windows. try: _urandom = file('/dev/urandom', 'rb') except OSError: raise ImportError('No adequate source of randomness found!') else: def getBytes(n): bytes = [] while n: chunk = _urandom.read(n) n -= len(chunk) bytes.append(chunk) assert n >= 0 return ''.join(bytes) else: _pool = RandomPool() def getBytes(n, pool=_pool): if pool.entropy < n: pool.randomize() return pool.get_bytes(n) # A randrange function that works for longs try: randrange = random.SystemRandom().randrange except AttributeError: # In Python 2.2's random.Random, randrange does not support # numbers larger than sys.maxint for randrange. For simplicity, # use this implementation for any Python that does not have # random.SystemRandom from math import log, ceil _duplicate_cache = {} def randrange(start, stop=None, step=1): if stop is None: stop = start start = 0 r = (stop - start) // step try: (duplicate, nbytes) = _duplicate_cache[r] except KeyError: rbytes = longToBinary(r) if rbytes[0] == '\x00': nbytes = len(rbytes) - 1 else: nbytes = len(rbytes) mxrand = (256 ** nbytes) # If we get a number less than this, then it is in the # duplicated range. duplicate = mxrand % r if len(_duplicate_cache) > 10: _duplicate_cache.clear() _duplicate_cache[r] = (duplicate, nbytes) while 1: bytes = '\x00' + getBytes(nbytes) n = binaryToLong(bytes) # Keep looping if this value is in the low duplicated range if n >= duplicate: break return start + (n % r) * step def hmacSha1(key, text): return hmac.new(key, text, sha).digest() def sha1(s): return sha.new(s).digest() def longToBase64(l): return toBase64(longToBinary(l)) def base64ToLong(s): return binaryToLong(fromBase64(s)) def randomString(length, chrs=None): """Produce a string of length random bytes, chosen from chrs.""" if chrs is None: return getBytes(length) else: n = len(chrs) return ''.join([chrs[randrange(n)] for _ in xrange(length)])