1# Copyright (C) 2009 Nominum, Inc. 2# 3# Permission to use, copy, modify, and distribute this software and its 4# documentation for any purpose with or without fee is hereby granted, 5# provided that the above copyright notice and this permission notice 6# appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16import os 17import time 18try: 19 import threading as _threading 20except ImportError: 21 import dummy_threading as _threading 22 23class EntropyPool(object): 24 def __init__(self, seed=None): 25 self.pool_index = 0 26 self.digest = None 27 self.next_byte = 0 28 self.lock = _threading.Lock() 29 try: 30 import hashlib 31 self.hash = hashlib.sha1() 32 self.hash_len = 20 33 except: 34 try: 35 import sha 36 self.hash = sha.new() 37 self.hash_len = 20 38 except: 39 import md5 40 self.hash = md5.new() 41 self.hash_len = 16 42 self.pool = '\0' * self.hash_len 43 if not seed is None: 44 self.stir(seed) 45 self.seeded = True 46 else: 47 self.seeded = False 48 49 def stir(self, entropy, already_locked=False): 50 if not already_locked: 51 self.lock.acquire() 52 try: 53 bytes = [ord(c) for c in self.pool] 54 for c in entropy: 55 if self.pool_index == self.hash_len: 56 self.pool_index = 0 57 b = ord(c) & 0xff 58 bytes[self.pool_index] ^= b 59 self.pool_index += 1 60 self.pool = ''.join([chr(c) for c in bytes]) 61 finally: 62 if not already_locked: 63 self.lock.release() 64 65 def _maybe_seed(self): 66 if not self.seeded: 67 try: 68 seed = os.urandom(16) 69 except: 70 try: 71 r = file('/dev/urandom', 'r', 0) 72 try: 73 seed = r.read(16) 74 finally: 75 r.close() 76 except: 77 seed = str(time.time()) 78 self.seeded = True 79 self.stir(seed, True) 80 81 def random_8(self): 82 self.lock.acquire() 83 self._maybe_seed() 84 try: 85 if self.digest is None or self.next_byte == self.hash_len: 86 self.hash.update(self.pool) 87 self.digest = self.hash.digest() 88 self.stir(self.digest, True) 89 self.next_byte = 0 90 value = ord(self.digest[self.next_byte]) 91 self.next_byte += 1 92 finally: 93 self.lock.release() 94 return value 95 96 def random_16(self): 97 return self.random_8() * 256 + self.random_8() 98 99 def random_32(self): 100 return self.random_16() * 65536 + self.random_16() 101 102 def random_between(self, first, last): 103 size = last - first + 1 104 if size > 4294967296L: 105 raise ValueError('too big') 106 if size > 65536: 107 rand = self.random_32 108 max = 4294967295L 109 elif size > 256: 110 rand = self.random_16 111 max = 65535 112 else: 113 rand = self.random_8 114 max = 255 115 return (first + size * rand() // (max + 1)) 116 117pool = EntropyPool() 118 119def random_16(): 120 return pool.random_16() 121 122def between(first, last): 123 return pool.random_between(first, last) 124