_UserFriendlyRNG.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Random/_UserFriendlyRNG.py : A user-friendly random number generator
  4. #
  5. # Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
  6. #
  7. # ===================================================================
  8. # The contents of this file are dedicated to the public domain. To
  9. # the extent that dedication to the public domain is not available,
  10. # everyone is granted a worldwide, perpetual, royalty-free,
  11. # non-exclusive license to exercise all rights associated with the
  12. # contents of this file for any purpose whatsoever.
  13. # No rights are reserved.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  19. # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  20. # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  21. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. # ===================================================================
  24. __revision__ = "$Id$"
  25. import sys
  26. if sys.version_info[0] == 2 and sys.version_info[1] == 1:
  27. from Crypto.Util.py21compat import *
  28. import os
  29. import threading
  30. import struct
  31. import time
  32. from math import floor
  33. from Crypto.Random import OSRNG
  34. from Crypto.Random.Fortuna import FortunaAccumulator
  35. class _EntropySource(object):
  36. def __init__(self, accumulator, src_num):
  37. self._fortuna = accumulator
  38. self._src_num = src_num
  39. self._pool_num = 0
  40. def feed(self, data):
  41. self._fortuna.add_random_event(self._src_num, self._pool_num, data)
  42. self._pool_num = (self._pool_num + 1) & 31
  43. class _EntropyCollector(object):
  44. def __init__(self, accumulator):
  45. self._osrng = OSRNG.new()
  46. self._osrng_es = _EntropySource(accumulator, 255)
  47. self._time_es = _EntropySource(accumulator, 254)
  48. self._clock_es = _EntropySource(accumulator, 253)
  49. def reinit(self):
  50. # Add 256 bits to each of the 32 pools, twice. (For a total of 16384
  51. # bits collected from the operating system.)
  52. for i in range(2):
  53. block = self._osrng.read(32*32)
  54. for p in range(32):
  55. self._osrng_es.feed(block[p*32:(p+1)*32])
  56. block = None
  57. self._osrng.flush()
  58. def collect(self):
  59. # Collect 64 bits of entropy from the operating system and feed it to Fortuna.
  60. self._osrng_es.feed(self._osrng.read(8))
  61. # Add the fractional part of time.time()
  62. t = time.time()
  63. self._time_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))
  64. # Add the fractional part of time.clock()
  65. t = time.clock()
  66. self._clock_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))
  67. class _UserFriendlyRNG(object):
  68. def __init__(self):
  69. self.closed = False
  70. self._fa = FortunaAccumulator.FortunaAccumulator()
  71. self._ec = _EntropyCollector(self._fa)
  72. self.reinit()
  73. def reinit(self):
  74. """Initialize the random number generator and seed it with entropy from
  75. the operating system.
  76. """
  77. # Save the pid (helps ensure that Crypto.Random.atfork() gets called)
  78. self._pid = os.getpid()
  79. # Collect entropy from the operating system and feed it to
  80. # FortunaAccumulator
  81. self._ec.reinit()
  82. # Override FortunaAccumulator's 100ms minimum re-seed interval. This
  83. # is necessary to avoid a race condition between this function and
  84. # self.read(), which that can otherwise cause forked child processes to
  85. # produce identical output. (e.g. CVE-2013-1445)
  86. #
  87. # Note that if this function can be called frequently by an attacker,
  88. # (and if the bits from OSRNG are insufficiently random) it will weaken
  89. # Fortuna's ability to resist a state compromise extension attack.
  90. self._fa._forget_last_reseed()
  91. def close(self):
  92. self.closed = True
  93. self._osrng = None
  94. self._fa = None
  95. def flush(self):
  96. pass
  97. def read(self, N):
  98. """Return N bytes from the RNG."""
  99. if self.closed:
  100. raise ValueError("I/O operation on closed file")
  101. if not isinstance(N, (long, int)):
  102. raise TypeError("an integer is required")
  103. if N < 0:
  104. raise ValueError("cannot read to end of infinite stream")
  105. # Collect some entropy and feed it to Fortuna
  106. self._ec.collect()
  107. # Ask Fortuna to generate some bytes
  108. retval = self._fa.random_data(N)
  109. # Check that we haven't forked in the meantime. (If we have, we don't
  110. # want to use the data, because it might have been duplicated in the
  111. # parent process.
  112. self._check_pid()
  113. # Return the random data.
  114. return retval
  115. def _check_pid(self):
  116. # Lame fork detection to remind developers to invoke Random.atfork()
  117. # after every call to os.fork(). Note that this check is not reliable,
  118. # since process IDs can be reused on most operating systems.
  119. #
  120. # You need to do Random.atfork() in the child process after every call
  121. # to os.fork() to avoid reusing PRNG state. If you want to avoid
  122. # leaking PRNG state to child processes (for example, if you are using
  123. # os.setuid()) then you should also invoke Random.atfork() in the
  124. # *parent* process.
  125. if os.getpid() != self._pid:
  126. raise AssertionError("PID check failed. RNG must be re-initialized after fork(). Hint: Try Random.atfork()")
  127. class _LockingUserFriendlyRNG(_UserFriendlyRNG):
  128. def __init__(self):
  129. self._lock = threading.Lock()
  130. _UserFriendlyRNG.__init__(self)
  131. def close(self):
  132. self._lock.acquire()
  133. try:
  134. return _UserFriendlyRNG.close(self)
  135. finally:
  136. self._lock.release()
  137. def reinit(self):
  138. self._lock.acquire()
  139. try:
  140. return _UserFriendlyRNG.reinit(self)
  141. finally:
  142. self._lock.release()
  143. def read(self, bytes):
  144. self._lock.acquire()
  145. try:
  146. return _UserFriendlyRNG.read(self, bytes)
  147. finally:
  148. self._lock.release()
  149. class RNGFile(object):
  150. def __init__(self, singleton):
  151. self.closed = False
  152. self._singleton = singleton
  153. # PEP 343: Support for the "with" statement
  154. def __enter__(self):
  155. """PEP 343 support"""
  156. def __exit__(self):
  157. """PEP 343 support"""
  158. self.close()
  159. def close(self):
  160. # Don't actually close the singleton, just close this RNGFile instance.
  161. self.closed = True
  162. self._singleton = None
  163. def read(self, bytes):
  164. if self.closed:
  165. raise ValueError("I/O operation on closed file")
  166. return self._singleton.read(bytes)
  167. def flush(self):
  168. if self.closed:
  169. raise ValueError("I/O operation on closed file")
  170. _singleton_lock = threading.Lock()
  171. _singleton = None
  172. def _get_singleton():
  173. global _singleton
  174. _singleton_lock.acquire()
  175. try:
  176. if _singleton is None:
  177. _singleton = _LockingUserFriendlyRNG()
  178. return _singleton
  179. finally:
  180. _singleton_lock.release()
  181. def new():
  182. return RNGFile(_get_singleton())
  183. def reinit():
  184. _get_singleton().reinit()
  185. def get_random_bytes(n):
  186. """Return the specified number of cryptographically-strong random bytes."""
  187. return _get_singleton().read(n)
  188. # vim:set ts=4 sw=4 sts=4 expandtab: