123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- """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__ = [
- 'base64ToLong',
- 'binaryToLong',
- 'hmacSha1',
- 'hmacSha256',
- 'longToBase64',
- 'longToBinary',
- 'randomString',
- 'randrange',
- 'sha1',
- 'sha256',
- ]
- import hmac
- import os
- import random
- from openid.oidutil import toBase64, fromBase64
- try:
- import hashlib
- except ImportError:
- import sha as sha1_module
- try:
- from Crypto.Hash import SHA256 as sha256_module
- except ImportError:
- sha256_module = None
- else:
- class HashContainer(object):
- def __init__(self, hash_constructor):
- self.new = hash_constructor
- self.digest_size = hash_constructor().digest_size
- sha1_module = HashContainer(hashlib.sha1)
- sha256_module = HashContainer(hashlib.sha256)
- def hmacSha1(key, text):
- return hmac.new(key, text, sha1_module).digest()
- def sha1(s):
- return sha1_module.new(s).digest()
- if sha256_module is not None:
- def hmacSha256(key, text):
- return hmac.new(key, text, sha256_module).digest()
- def sha256(s):
- return sha256_module.new(s).digest()
- SHA256_AVAILABLE = True
- else:
- _no_sha256 = NotImplementedError(
- 'Use Python 2.5, install pycrypto or install hashlib to use SHA256')
- def hmacSha256(unused_key, unused_text):
- raise _no_sha256
- def sha256(s):
- raise _no_sha256
- SHA256_AVAILABLE = False
- 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 IOError:
- 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 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)])
|