Jingwen Chen | 475b3cc | 2021-01-05 21:45:16 -0500 | [diff] [blame] | 1 | """Generate cryptographically strong pseudo-random numbers suitable for |
| 2 | managing secrets such as account authentication, tokens, and similar. |
| 3 | |
| 4 | See PEP 506 for more information. |
| 5 | https://www.python.org/dev/peps/pep-0506/ |
| 6 | |
| 7 | """ |
| 8 | |
| 9 | __all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom', |
| 10 | 'token_bytes', 'token_hex', 'token_urlsafe', |
| 11 | 'compare_digest', |
| 12 | ] |
| 13 | |
| 14 | |
| 15 | import base64 |
| 16 | import binascii |
Jingwen Chen | 475b3cc | 2021-01-05 21:45:16 -0500 | [diff] [blame] | 17 | |
| 18 | from hmac import compare_digest |
| 19 | from random import SystemRandom |
| 20 | |
| 21 | _sysrand = SystemRandom() |
| 22 | |
| 23 | randbits = _sysrand.getrandbits |
| 24 | choice = _sysrand.choice |
| 25 | |
| 26 | def randbelow(exclusive_upper_bound): |
| 27 | """Return a random int in the range [0, n).""" |
| 28 | if exclusive_upper_bound <= 0: |
| 29 | raise ValueError("Upper bound must be positive.") |
| 30 | return _sysrand._randbelow(exclusive_upper_bound) |
| 31 | |
| 32 | DEFAULT_ENTROPY = 32 # number of bytes to return by default |
| 33 | |
| 34 | def token_bytes(nbytes=None): |
| 35 | """Return a random byte string containing *nbytes* bytes. |
| 36 | |
| 37 | If *nbytes* is ``None`` or not supplied, a reasonable |
| 38 | default is used. |
| 39 | |
| 40 | >>> token_bytes(16) #doctest:+SKIP |
| 41 | b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b' |
| 42 | |
| 43 | """ |
| 44 | if nbytes is None: |
| 45 | nbytes = DEFAULT_ENTROPY |
Elliott Hughes | 96c2b6b | 2021-01-26 11:15:15 -0800 | [diff] [blame] | 46 | return _sysrand.randbytes(nbytes) |
Jingwen Chen | 475b3cc | 2021-01-05 21:45:16 -0500 | [diff] [blame] | 47 | |
| 48 | def token_hex(nbytes=None): |
| 49 | """Return a random text string, in hexadecimal. |
| 50 | |
| 51 | The string has *nbytes* random bytes, each byte converted to two |
| 52 | hex digits. If *nbytes* is ``None`` or not supplied, a reasonable |
| 53 | default is used. |
| 54 | |
| 55 | >>> token_hex(16) #doctest:+SKIP |
| 56 | 'f9bf78b9a18ce6d46a0cd2b0b86df9da' |
| 57 | |
| 58 | """ |
| 59 | return binascii.hexlify(token_bytes(nbytes)).decode('ascii') |
| 60 | |
| 61 | def token_urlsafe(nbytes=None): |
| 62 | """Return a random URL-safe text string, in Base64 encoding. |
| 63 | |
| 64 | The string has *nbytes* random bytes. If *nbytes* is ``None`` |
| 65 | or not supplied, a reasonable default is used. |
| 66 | |
| 67 | >>> token_urlsafe(16) #doctest:+SKIP |
| 68 | 'Drmhze6EPcv0fN_81Bj-nA' |
| 69 | |
| 70 | """ |
| 71 | tok = token_bytes(nbytes) |
| 72 | return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii') |