secrets.token_hex(16) returns 32 hex characters made from 16 bytes of os.urandom, the operating system’s cryptographic random source: 128 bits, enough for a session ID, an API key or a reset token. The secrets module has been in the standard library since Python 3.6.
import secrets
print(secrets.token_hex(16))
print(secrets.token_urlsafe(16))
0c91694658d154ba85b74986a23b401c
gXeFS6zUPLCf5dt4-QQDkg
token_urlsafe writes the bytes as base64url without padding, safe in URLs and file names.
The argument is bytes, not characters
Both functions take a number of bytes, so token_hex(32) is 64 characters and token_urlsafe(32) is 43, not 32. With no argument they use CPython’s default of 32 bytes:
print(len(secrets.token_hex(32)), len(secrets.token_urlsafe(32)))
print(len(secrets.token_hex()), len(secrets.token_urlsafe()))
64 43
64 43
Hex carries 4 bits a character and base64url 6, so 128 bits takes 32 hex or 22 base64url characters. The encoding adds nothing: 16 bytes are 128 bits however they’re written. The tool below counts 22 base64url characters drawn one at a time as 22 characters × log2(64) = 132 bits, while token_urlsafe(16) spells 128 bits in the same 22, its last character carrying only 2. OWASP’s Session Management Cheat Sheet sets 64 bits of entropy as the floor for a session ID; 128 is the usual target.
Letters and digits
To draw from a set of characters, call secrets.choice once per character:
import secrets, string
alphabet = string.ascii_letters + string.digits
print(''.join(secrets.choice(alphabet) for _ in range(22)))
# rNnVMu7IarUIR918GlFrIg
secrets.choice picks its index by rejection, redrawing any value past the end of the alphabet, so each of the 62 characters is equally likely. Twenty-two of them make 22 characters × log2(62) = 131 bits, as the tool shows with Custom, Letters and digits and Length 22.
Why not random
The random module is a Mersenne Twister, built for simulations, and its documentation warns against using it for security. Seeded the same, it prints the same string on every run:
import random, string
alphabet = string.ascii_letters + string.digits
random.seed(1234)
print(''.join(random.choices(alphabet, k=22)))
# 7Ba46KPfVobWvMMjlhaE7e, every time
Unseeded, it starts from os.urandom, but that doesn’t save it: 624 consecutive 32-bit outputs are enough to rebuild its state and predict everything after them.
Other ways
os.urandom(16).hex()gives the same astoken_hex(16).uuid.uuid4().hexgives 32 hex characters, of which 122 bits are random.- In a shell:
python3 -c 'import secrets; print(secrets.token_hex(16))'.