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