entropy.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # Copyright (C) 2009, 2011 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. import os
  16. import random
  17. import time
  18. from ._compat import long, binary_type
  19. try:
  20. import threading as _threading
  21. except ImportError:
  22. import dummy_threading as _threading
  23. class 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 ImportError:
  34. try:
  35. import sha
  36. self.hash = sha.new()
  37. self.hash_len = 20
  38. except ImportError:
  39. import md5 # pylint: disable=import-error
  40. self.hash = md5.new()
  41. self.hash_len = 16
  42. self.pool = bytearray(b'\0' * self.hash_len)
  43. if seed is not None:
  44. self.stir(bytearray(seed))
  45. self.seeded = True
  46. self.seed_pid = os.getpid()
  47. else:
  48. self.seeded = False
  49. self.seed_pid = 0
  50. def stir(self, entropy, already_locked=False):
  51. if not already_locked:
  52. self.lock.acquire()
  53. try:
  54. for c in entropy:
  55. if self.pool_index == self.hash_len:
  56. self.pool_index = 0
  57. b = c & 0xff
  58. self.pool[self.pool_index] ^= b
  59. self.pool_index += 1
  60. finally:
  61. if not already_locked:
  62. self.lock.release()
  63. def _maybe_seed(self):
  64. if not self.seeded or self.seed_pid != os.getpid():
  65. try:
  66. seed = os.urandom(16)
  67. except Exception:
  68. try:
  69. r = open('/dev/urandom', 'rb', 0)
  70. try:
  71. seed = r.read(16)
  72. finally:
  73. r.close()
  74. except Exception:
  75. seed = str(time.time())
  76. self.seeded = True
  77. self.seed_pid = os.getpid()
  78. self.digest = None
  79. seed = bytearray(seed)
  80. self.stir(seed, True)
  81. def random_8(self):
  82. self.lock.acquire()
  83. try:
  84. self._maybe_seed()
  85. if self.digest is None or self.next_byte == self.hash_len:
  86. self.hash.update(binary_type(self.pool))
  87. self.digest = bytearray(self.hash.digest())
  88. self.stir(self.digest, True)
  89. self.next_byte = 0
  90. value = self.digest[self.next_byte]
  91. self.next_byte += 1
  92. finally:
  93. self.lock.release()
  94. return value
  95. def random_16(self):
  96. return self.random_8() * 256 + self.random_8()
  97. def random_32(self):
  98. return self.random_16() * 65536 + self.random_16()
  99. def random_between(self, first, last):
  100. size = last - first + 1
  101. if size > long(4294967296):
  102. raise ValueError('too big')
  103. if size > 65536:
  104. rand = self.random_32
  105. max = long(4294967295)
  106. elif size > 256:
  107. rand = self.random_16
  108. max = 65535
  109. else:
  110. rand = self.random_8
  111. max = 255
  112. return first + size * rand() // (max + 1)
  113. pool = EntropyPool()
  114. try:
  115. system_random = random.SystemRandom()
  116. except Exception:
  117. system_random = None
  118. def random_16():
  119. if system_random is not None:
  120. return system_random.randrange(0, 65536)
  121. else:
  122. return pool.random_16()
  123. def between(first, last):
  124. if system_random is not None:
  125. return system_random.randrange(first, last + 1)
  126. else:
  127. return pool.random_between(first, last)