randbytes.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # -*- test-case-name: twisted.test.test_randbytes -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Cryptographically secure random implementation, with fallback on normal random.
  6. """
  7. from __future__ import division, absolute_import
  8. import warnings, os, random, string
  9. from twisted.python.compat import _PY3
  10. getrandbits = getattr(random, 'getrandbits', None)
  11. if _PY3:
  12. _fromhex = bytes.fromhex
  13. else:
  14. def _fromhex(hexBytes):
  15. return hexBytes.decode('hex')
  16. class SecureRandomNotAvailable(RuntimeError):
  17. """
  18. Exception raised when no secure random algorithm is found.
  19. """
  20. class SourceNotAvailable(RuntimeError):
  21. """
  22. Internal exception used when a specific random source is not available.
  23. """
  24. class RandomFactory(object):
  25. """
  26. Factory providing L{secureRandom} and L{insecureRandom} methods.
  27. You shouldn't have to instantiate this class, use the module level
  28. functions instead: it is an implementation detail and could be removed or
  29. changed arbitrarily.
  30. """
  31. # This variable is no longer used, and will eventually be removed.
  32. randomSources = ()
  33. getrandbits = getrandbits
  34. def _osUrandom(self, nbytes):
  35. """
  36. Wrapper around C{os.urandom} that cleanly manage its absence.
  37. """
  38. try:
  39. return os.urandom(nbytes)
  40. except (AttributeError, NotImplementedError) as e:
  41. raise SourceNotAvailable(e)
  42. def secureRandom(self, nbytes, fallback=False):
  43. """
  44. Return a number of secure random bytes.
  45. @param nbytes: number of bytes to generate.
  46. @type nbytes: C{int}
  47. @param fallback: Whether the function should fallback on non-secure
  48. random or not. Default to C{False}.
  49. @type fallback: C{bool}
  50. @return: a string of random bytes.
  51. @rtype: C{str}
  52. """
  53. try:
  54. return self._osUrandom(nbytes)
  55. except SourceNotAvailable:
  56. pass
  57. if fallback:
  58. warnings.warn(
  59. "urandom unavailable - "
  60. "proceeding with non-cryptographically secure random source",
  61. category=RuntimeWarning,
  62. stacklevel=2)
  63. return self.insecureRandom(nbytes)
  64. else:
  65. raise SecureRandomNotAvailable("No secure random source available")
  66. def _randBits(self, nbytes):
  67. """
  68. Wrapper around C{os.getrandbits}.
  69. """
  70. if self.getrandbits is not None:
  71. n = self.getrandbits(nbytes * 8)
  72. hexBytes = ("%%0%dx" % (nbytes * 2)) % n
  73. return _fromhex(hexBytes)
  74. raise SourceNotAvailable("random.getrandbits is not available")
  75. if _PY3:
  76. _maketrans = bytes.maketrans
  77. def _randModule(self, nbytes):
  78. """
  79. Wrapper around the C{random} module.
  80. """
  81. return b"".join([
  82. bytes([random.choice(self._BYTES)]) for i in range(nbytes)])
  83. else:
  84. _maketrans = string.maketrans
  85. def _randModule(self, nbytes):
  86. """
  87. Wrapper around the C{random} module.
  88. """
  89. return b"".join([
  90. random.choice(self._BYTES) for i in range(nbytes)])
  91. _BYTES = _maketrans(b'', b'')
  92. def insecureRandom(self, nbytes):
  93. """
  94. Return a number of non secure random bytes.
  95. @param nbytes: number of bytes to generate.
  96. @type nbytes: C{int}
  97. @return: a string of random bytes.
  98. @rtype: C{str}
  99. """
  100. for src in ("_randBits", "_randModule"):
  101. try:
  102. return getattr(self, src)(nbytes)
  103. except SourceNotAvailable:
  104. pass
  105. factory = RandomFactory()
  106. secureRandom = factory.secureRandom
  107. insecureRandom = factory.insecureRandom
  108. del factory
  109. __all__ = ["secureRandom", "insecureRandom", "SecureRandomNotAvailable"]