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