summaryrefslogtreecommitdiffstats
path: root/Lib/secrets.py
blob: ed018ad5412f970b2093d4d0a05fe2e062a72e7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Generate cryptographically strong pseudo-random numbers suitable for
managing secrets such as account authentication, tokens, and similar.
See PEP 506 for more information.

https://www.python.org/dev/peps/pep-0506/


Random numbers
==============

The ``secrets`` module provides the following pseudo-random functions, based
on SystemRandom, which in turn uses the most secure source of randomness your
operating system provides.


    choice(sequence)
        Choose a random element from a non-empty sequence.

    randbelow(n)
        Return a random int in the range [0, n).

    randbits(k)
        Generates an int with k random bits.

    SystemRandom
        Class for generating random numbers using sources provided by
        the operating system. See the ``random`` module for documentation.


Token functions
===============

The ``secrets`` module provides a number of functions for generating secure
tokens, suitable for applications such as password resets, hard-to-guess
URLs, and similar. All the ``token_*`` functions take an optional single
argument specifying the number of bytes of randomness to use. If that is
not given, or is ``None``, a reasonable default is used. That default is
subject to change at any time, including during maintenance releases.


    token_bytes(nbytes=None)
        Return a random byte-string containing ``nbytes`` number of bytes.

        >>> secrets.token_bytes(16)  #doctest:+SKIP
        b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b'


    token_hex(nbytes=None)
        Return a random text-string, in hexadecimal. The string has ``nbytes``
        random bytes, each byte converted to two hex digits.

        >>> secrets.token_hex(16)  #doctest:+SKIP
        'f9bf78b9a18ce6d46a0cd2b0b86df9da'

    token_urlsafe(nbytes=None)
        Return a random URL-safe text-string, containing ``nbytes`` random
        bytes. On average, each byte results in approximately 1.3 characters
        in the final result.

        >>> secrets.token_urlsafe(16)  #doctest:+SKIP
        'Drmhze6EPcv0fN_81Bj-nA'


(The examples above assume Python 3. In Python 2, byte-strings will display
using regular quotes ``''`` with no prefix, and text-strings will have a
``u`` prefix.)


Other functions
===============

    compare_digest(a, b)
        Return True if strings a and b are equal, otherwise False.
        Performs the equality comparison in such a way as to reduce the
        risk of timing attacks.

        See http://codahale.com/a-lesson-in-timing-attacks/ for a
        discussion on how timing attacks against ``==`` can reveal
        secrets from your application.


"""

__all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom',
           'token_bytes', 'token_hex', 'token_urlsafe',
           'compare_digest',
           ]


import base64
import binascii
import os

try:
    from hmac import compare_digest
except ImportError:
    # Python version is too old. Fall back to a pure-Python version.

    import operator
    from functools import reduce

    def compare_digest(a, b):
        """Return ``a == b`` using an approach resistant to timing analysis.

        a and b must both be of the same type: either both text strings,
        or both byte strings.

        Note: If a and b are of different lengths, or if an error occurs,
        a timing attack could theoretically reveal information about the
        types and lengths of a and b, but not their values.
        """
        # For a similar approach, see
        # http://codahale.com/a-lesson-in-timing-attacks/
        for T in (bytes, str):
            if isinstance(a, T) and isinstance(b, T):
                break
        else:  # for...else
            raise TypeError("arguments must be both strings or both bytes")
        if len(a) != len(b):
            return False
        # Thanks to Raymond Hettinger for this one-liner.
        return reduce(operator.and_, map(operator.eq, a, b), True)



from random import SystemRandom

_sysrand = SystemRandom()

randbits = _sysrand.getrandbits
choice = _sysrand.choice

def randbelow(exclusive_upper_bound):
    return _sysrand._randbelow(exclusive_upper_bound)

DEFAULT_ENTROPY = 32  # number of bytes to return by default

def token_bytes(nbytes=None):
    if nbytes is None:
        nbytes = DEFAULT_ENTROPY
    return os.urandom(nbytes)

def token_hex(nbytes=None):
    return binascii.hexlify(token_bytes(nbytes)).decode('ascii')

def token_urlsafe(nbytes=None):
    tok = token_bytes(nbytes)
    return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')