Generate a random string in Python

import secrets; print(secrets.token_hex(16))

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 as token_hex(16).
  • uuid.uuid4().hex gives 32 hex characters, of which 122 bits are random.
  • In a shell: python3 -c 'import secrets; print(secrets.token_hex(16))'.

Try it in the Random String Generator

Drawing bytes from your browser’s random source…

Runs in your browser. Nothing you enter is sent anywhere.