Python random string generator
Generate a string here, or copy the Python below. Every example uses the secrets module and was run on Python 3.14.
More options
The short answer
Use the secrets module. It has been in the standard library since Python 3.6, it exists specifically for passwords, tokens and keys, and everything in it draws from os.urandom, which is the operating system cryptographically secure generator.
For a string of letters and digits at a chosen length, join secrets.choice over your alphabet. secrets.choice is SystemRandom.choice, and SystemRandom picks an index by drawing random bits and discarding values that fall outside the range, rather than taking a remainder. Every character in the alphabet is equally likely.
If you do not need a particular alphabet, the token functions are shorter and just as strong. Their argument is a number of random bytes, not a number of characters, and that is the most common mistake made with them.
| You need | Call | Result |
|---|---|---|
| 24 letters and digits | ''.join(secrets.choice(ALPHABET) for _ in range(24)) | 24 characters, 142.9 bits |
| A URL-safe token | secrets.token_urlsafe(32) | 43 characters, 256 bits |
| A hex token | secrets.token_hex(16) | 32 characters, 128 bits |
| Raw bytes | secrets.token_bytes(32) | 32 bytes, 256 bits |
| A UUID | str(uuid.uuid4()) | 36 characters, 122 random bits |
secrets.token_urlsafe(16) returns 22 characters, not 16. The argument counts bytes of randomness, and base64 turns every three bytes into four characters.
Why random.choices is the wrong tool
The most copied answer to this question is a one-liner built on random.choices(string.ascii_letters + string.digits, k=n). It produces strings that look random, and for test fixtures they are fine. For anything that has to be unguessable they are not, because the random module is a Mersenne Twister, and the Python documentation warns that it should not be used for security purposes.
Mersenne Twister is completely determined by its internal state, and that state can be rebuilt from its own output. 624 consecutive 32-bit values are enough to do it exactly, and partial output such as the characters of generated strings can be solved for with more samples. Someone who collects enough of your values, for example a run of reset tokens sent to accounts they control, can compute the values issued to everyone else. Length does not fix this, because the weakness is predictability rather than size.
The mapping is not the problem. random.choices picks an index as floor(random() * n), and with 53-bit floats the unevenness across a 62-character alphabet is about one part in 145 trillion. The problem is the source.
Seeding does not help. random.seed(os.urandom(32)) gives Mersenne Twister an unpredictable starting point, but every later output is still a deterministic function of state that the outputs reveal. random.SystemRandom is safe, because it replaces the generator with os.urandom, and secrets.choice is a SystemRandom under a clearer name.
| Source | Generator | Safe for secrets |
|---|---|---|
| random.choice, choices, sample, shuffle | Mersenne Twister MT19937 | No |
| numpy.random.default_rng() | PCG64 | No |
| random.SystemRandom() | os.urandom | Yes |
| secrets | os.urandom, through SystemRandom | Yes |
| os.urandom | Operating system CSPRNG | Yes |
Mistakes that pass code review
Each of these runs without an error and produces output that looks fine, which is exactly why they survive review.
- string.printable as the alphabet. It has 100 characters and six of them are whitespace: space, tab, newline, carriage return, vertical tab and form feed. A token containing a newline breaks headers, log lines and copy and paste.
- random.sample(ALPHABET, k) to build a string. Apart from using Mersenne Twister, sample never repeats a character, so 24 characters drawn from 62 carry 135.5 bits instead of 142.9, and the string can never be longer than the alphabet.
- uuid.uuid4().hex[:8] as a short ID. The source is sound, but eight hex characters are 32 bits, and a collision becomes more likely than not after about 77,000 IDs.
- hashlib.md5(str(time.time()).encode()).hexdigest() as a token. Hashing adds no randomness. The input is a timestamp an attacker can guess to within seconds, so the output is only as unpredictable as that guess.
- base64.b64encode(os.urandom(32)) in a URL. Standard base64 uses plus, slash and equals, and all three change meaning in a URL. Use secrets.token_urlsafe, or base64.urlsafe_b64encode with the padding stripped.
Passwords that must contain every character type
Some systems insist on at least one uppercase letter, lowercase letter, digit and symbol. The dependable way to meet that is to generate a whole string, check it, and generate a new one if a class is missing. The result is uniform over every string that passes, and there is nothing subtle to get wrong.
At 20 characters over a 74-character alphabet most candidates pass on the first attempt, so the loop costs almost nothing. The example below uses twelve symbols that are accepted by the common corporate password policies; add or remove symbols to match the system you are generating for.
Many unique strings at once
For a batch of IDs, add strings to a set until it holds as many as you need. At 16 characters from 62 there are 4.77 x 10^28 possible strings, so a million of them collide with a probability of about 1 in 10^17 and the loop essentially never retries. The set turns that probability into a guarantee.
A set in one process knows nothing about strings generated by another. If the IDs are stored, put a unique constraint on the column as well.
Django and other frameworks
Django's get_random_string(length, allowed_chars) is a thin wrapper around secrets.choice in current releases, which was checked in the Django 6.1.1 source, and its default alphabet is upper and lower case letters and digits. It is safe to use directly.
Outside Django, reach for secrets directly rather than a helper whose source you have not read. For any token that grants access, such as a password reset link, store a hash of the token and compare in constant time with secrets.compare_digest.
Doing this in your own code
Python 3.6+ - letters and digits
import secrets
import string
ALPHABET = string.ascii_letters + string.digits
def random_string(length: int = 24) -> str:
# secrets.choice draws from the OS CSPRNG and never takes a remainder,
# so every character in ALPHABET is equally likely.
return "".join(secrets.choice(ALPHABET) for _ in range(length))
for _ in range(3):
print(random_string(24)) Python 3.6+ - tokens without a custom alphabet
import secrets
import uuid
print(secrets.token_urlsafe(32)) # 43 characters, 256 bits
print(secrets.token_hex(16)) # 32 characters, 128 bits
print(uuid.uuid4()) # 36 characters, 122 random bits The argument to token_urlsafe and token_hex is a number of bytes, not characters.
Python 3.6+ - password with every character type
import secrets
import string
SYMBOLS = "!#$%*+-=?@^_"
CLASSES = [string.ascii_uppercase, string.ascii_lowercase, string.digits, SYMBOLS]
ALPHABET = "".join(CLASSES)
def password(length: int = 20) -> str:
if length < len(CLASSES):
raise ValueError("length is shorter than the number of required classes")
# Generate the whole string, then reject it if a class is missing.
# This stays uniform over every valid password.
while True:
candidate = "".join(secrets.choice(ALPHABET) for _ in range(length))
if all(any(c in cls for c in candidate) for cls in CLASSES):
return candidate
for _ in range(5):
print(password(20)) Python 3.9+ - many unique strings
import secrets
import string
ALPHABET = string.ascii_letters + string.digits
def unique_strings(count: int, length: int = 16) -> list[str]:
seen: set[str] = set()
while len(seen) < count:
seen.add("".join(secrets.choice(ALPHABET) for _ in range(length)))
return list(seen)
for value in unique_strings(1000, 16):
print(value) On Python 3.6 to 3.8, write List[str] and Set[str] from the typing module instead.
Questions
How do I generate a random string in Python?
Import secrets and string, set ALPHABET = string.ascii_letters + string.digits, and return ''.join(secrets.choice(ALPHABET) for _ in range(length)). That is secure and unbiased on Python 3.6 and later. For a token with no particular alphabet, secrets.token_urlsafe(32) is shorter.
What is the difference between secrets and random in Python?
random is a Mersenne Twister, built for simulations and reproducible tests, and its output can be predicted from earlier output. secrets draws from os.urandom, the operating system cryptographic generator, and exists for passwords, tokens and keys. Use random when you want repeatability and secrets whenever a value must be unguessable.
How long is the string from secrets.token_urlsafe(32)?
43 characters. The argument is a number of random bytes, 32 bytes is 256 bits, and base64 encoding without padding turns that into 43 URL-safe characters. token_hex(16) gives 32 characters, because each byte becomes two hex digits.
Is random.choices secure enough for a password?
No. random.choices uses Mersenne Twister, whose internal state can be reconstructed from its output, after which every value it produces can be predicted. The strings look random, which is why this mistake is common. Replace random.choices with secrets.choice in a loop.
How do I include special characters in a Python random string?
Add them to the alphabet. string.punctuation holds all 32 ASCII punctuation characters, giving 94 characters and 6.55 bits each, but it includes quotes, a backslash and a backtick, which cause trouble in shells and config files. A shorter explicit set such as !#$%*+-=?@^_ avoids most of that.
Does Python have a built-in random string function?
Not one that takes an alphabet and a length. The secrets module gives you choice, token_hex, token_urlsafe and token_bytes, and a random string over a chosen alphabet is one line built from secrets.choice. Django adds get_random_string, which wraps the same call.