entropy.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2009-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. import os
  17. import hashlib
  18. import random
  19. import time
  20. try:
  21. import threading as _threading
  22. except ImportError: # pragma: no cover
  23. import dummy_threading as _threading # type: ignore
  24. class EntropyPool:
  25. # This is an entropy pool for Python implementations that do not
  26. # have a working SystemRandom. I'm not sure there are any, but
  27. # leaving this code doesn't hurt anything as the library code
  28. # is used if present.
  29. def __init__(self, seed=None):
  30. self.pool_index = 0
  31. self.digest = None
  32. self.next_byte = 0
  33. self.lock = _threading.Lock()
  34. self.hash = hashlib.sha1()
  35. self.hash_len = 20
  36. self.pool = bytearray(b'\0' * self.hash_len)
  37. if seed is not None:
  38. self._stir(bytearray(seed))
  39. self.seeded = True
  40. self.seed_pid = os.getpid()
  41. else:
  42. self.seeded = False
  43. self.seed_pid = 0
  44. def _stir(self, entropy):
  45. for c in entropy:
  46. if self.pool_index == self.hash_len:
  47. self.pool_index = 0
  48. b = c & 0xff
  49. self.pool[self.pool_index] ^= b
  50. self.pool_index += 1
  51. def stir(self, entropy):
  52. with self.lock:
  53. self._stir(entropy)
  54. def _maybe_seed(self):
  55. if not self.seeded or self.seed_pid != os.getpid():
  56. try:
  57. seed = os.urandom(16)
  58. except Exception: # pragma: no cover
  59. try:
  60. with open('/dev/urandom', 'rb', 0) as r:
  61. seed = r.read(16)
  62. except Exception:
  63. seed = str(time.time())
  64. self.seeded = True
  65. self.seed_pid = os.getpid()
  66. self.digest = None
  67. seed = bytearray(seed)
  68. self._stir(seed)
  69. def random_8(self):
  70. with self.lock:
  71. self._maybe_seed()
  72. if self.digest is None or self.next_byte == self.hash_len:
  73. self.hash.update(bytes(self.pool))
  74. self.digest = bytearray(self.hash.digest())
  75. self._stir(self.digest)
  76. self.next_byte = 0
  77. value = self.digest[self.next_byte]
  78. self.next_byte += 1
  79. return value
  80. def random_16(self):
  81. return self.random_8() * 256 + self.random_8()
  82. def random_32(self):
  83. return self.random_16() * 65536 + self.random_16()
  84. def random_between(self, first, last):
  85. size = last - first + 1
  86. if size > 4294967296:
  87. raise ValueError('too big')
  88. if size > 65536:
  89. rand = self.random_32
  90. max = 4294967295
  91. elif size > 256:
  92. rand = self.random_16
  93. max = 65535
  94. else:
  95. rand = self.random_8
  96. max = 255
  97. return first + size * rand() // (max + 1)
  98. pool = EntropyPool()
  99. try:
  100. system_random = random.SystemRandom()
  101. except Exception: # pragma: no cover
  102. system_random = None
  103. def random_16():
  104. if system_random is not None:
  105. return system_random.randrange(0, 65536)
  106. else:
  107. return pool.random_16()
  108. def between(first, last):
  109. if system_random is not None:
  110. return system_random.randrange(first, last + 1)
  111. else:
  112. return pool.random_between(first, last)