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