150 lines
3.9 KiB
Python
150 lines
3.9 KiB
Python
# -*- test-case-name: twisted.test.test_randbytes -*-
|
|
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Cryptographically secure random implementation, with fallback on normal random.
|
|
"""
|
|
|
|
from __future__ import division, absolute_import
|
|
|
|
import warnings, os, random, string
|
|
|
|
from twisted.python.compat import _PY3
|
|
|
|
getrandbits = getattr(random, 'getrandbits', None)
|
|
|
|
if _PY3:
|
|
_fromhex = bytes.fromhex
|
|
else:
|
|
def _fromhex(hexBytes):
|
|
return hexBytes.decode('hex')
|
|
|
|
|
|
class SecureRandomNotAvailable(RuntimeError):
|
|
"""
|
|
Exception raised when no secure random algorithm is found.
|
|
"""
|
|
|
|
|
|
|
|
class SourceNotAvailable(RuntimeError):
|
|
"""
|
|
Internal exception used when a specific random source is not available.
|
|
"""
|
|
|
|
|
|
|
|
class RandomFactory(object):
|
|
"""
|
|
Factory providing L{secureRandom} and L{insecureRandom} methods.
|
|
|
|
You shouldn't have to instantiate this class, use the module level
|
|
functions instead: it is an implementation detail and could be removed or
|
|
changed arbitrarily.
|
|
"""
|
|
|
|
# This variable is no longer used, and will eventually be removed.
|
|
randomSources = ()
|
|
|
|
getrandbits = getrandbits
|
|
|
|
|
|
def _osUrandom(self, nbytes):
|
|
"""
|
|
Wrapper around C{os.urandom} that cleanly manage its absence.
|
|
"""
|
|
try:
|
|
return os.urandom(nbytes)
|
|
except (AttributeError, NotImplementedError) as e:
|
|
raise SourceNotAvailable(e)
|
|
|
|
|
|
def secureRandom(self, nbytes, fallback=False):
|
|
"""
|
|
Return a number of secure random bytes.
|
|
|
|
@param nbytes: number of bytes to generate.
|
|
@type nbytes: C{int}
|
|
@param fallback: Whether the function should fallback on non-secure
|
|
random or not. Default to C{False}.
|
|
@type fallback: C{bool}
|
|
|
|
@return: a string of random bytes.
|
|
@rtype: C{str}
|
|
"""
|
|
try:
|
|
return self._osUrandom(nbytes)
|
|
except SourceNotAvailable:
|
|
pass
|
|
|
|
if fallback:
|
|
warnings.warn(
|
|
"urandom unavailable - "
|
|
"proceeding with non-cryptographically secure random source",
|
|
category=RuntimeWarning,
|
|
stacklevel=2)
|
|
return self.insecureRandom(nbytes)
|
|
else:
|
|
raise SecureRandomNotAvailable("No secure random source available")
|
|
|
|
|
|
def _randBits(self, nbytes):
|
|
"""
|
|
Wrapper around C{os.getrandbits}.
|
|
"""
|
|
if self.getrandbits is not None:
|
|
n = self.getrandbits(nbytes * 8)
|
|
hexBytes = ("%%0%dx" % (nbytes * 2)) % n
|
|
return _fromhex(hexBytes)
|
|
raise SourceNotAvailable("random.getrandbits is not available")
|
|
|
|
|
|
if _PY3:
|
|
_maketrans = bytes.maketrans
|
|
def _randModule(self, nbytes):
|
|
"""
|
|
Wrapper around the C{random} module.
|
|
"""
|
|
return b"".join([
|
|
bytes([random.choice(self._BYTES)]) for i in range(nbytes)])
|
|
else:
|
|
_maketrans = string.maketrans
|
|
def _randModule(self, nbytes):
|
|
"""
|
|
Wrapper around the C{random} module.
|
|
"""
|
|
return b"".join([
|
|
random.choice(self._BYTES) for i in range(nbytes)])
|
|
|
|
_BYTES = _maketrans(b'', b'')
|
|
|
|
|
|
def insecureRandom(self, nbytes):
|
|
"""
|
|
Return a number of non secure random bytes.
|
|
|
|
@param nbytes: number of bytes to generate.
|
|
@type nbytes: C{int}
|
|
|
|
@return: a string of random bytes.
|
|
@rtype: C{str}
|
|
"""
|
|
for src in ("_randBits", "_randModule"):
|
|
try:
|
|
return getattr(self, src)(nbytes)
|
|
except SourceNotAvailable:
|
|
pass
|
|
|
|
|
|
|
|
factory = RandomFactory()
|
|
|
|
secureRandom = factory.secureRandom
|
|
|
|
insecureRandom = factory.insecureRandom
|
|
|
|
del factory
|
|
|
|
|
|
__all__ = ["secureRandom", "insecureRandom", "SecureRandomNotAvailable"]
|