verification code
number generation
OTP
security code
two-factor authentication

How to generate a verification code/number?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

A verification code should be easy for a user to enter but hard for an attacker to guess. That means the problem is not just generating a random number; it also involves choosing the right entropy source, setting an expiration time, storing the code safely, and limiting retry attempts.

Use A Cryptographically Secure Random Source

For verification codes, avoid ordinary pseudo-random generators intended for simulation or games. In Python, the simplest secure choice is secrets.

python
1import secrets
2
3code = ''.join(str(secrets.randbelow(10)) for _ in range(6))
4print(code)

This generates a six-digit numeric code using a cryptographically suitable random source.

If you want an alphanumeric code with ambiguous characters removed, define an explicit alphabet.

python
1import secrets
2import string
3
4alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
5code = ''.join(secrets.choice(alphabet) for _ in range(8))
6print(code)

This avoids confusing characters such as 0, O, I, and 1.

Numeric Codes Versus Alphanumeric Codes

A numeric six-digit code is common because it is easy to type, especially on mobile keyboards. The tradeoff is the smaller search space.

A few practical options are:

  • 6-digit numeric for SMS or email verification
  • 8-digit numeric when you want more guessing resistance
  • 6 to 8 character alphanumeric when user entry conditions allow it

The right choice depends on your attack model and user experience requirements.

Expiration And Attempt Limits Matter As Much As Randomness

A well-generated code is still weak if it never expires or can be guessed indefinitely.

A typical verification flow should include:

  • code expiration, for example 5 to 10 minutes
  • a small attempt limit per code
  • rate limiting per account, phone number, email, or IP address
  • invalidation immediately after successful use

Without those rules, even a strong random code becomes much easier to brute-force.

Store A Hash, Not The Plain Code

If your application stores verification codes server-side, store a hash rather than the raw code whenever practical. That way, a database leak does not immediately expose every active code.

python
1import hashlib
2import secrets
3import time
4
5code = ''.join(str(secrets.randbelow(10)) for _ in range(6))
6code_hash = hashlib.sha256(code.encode()).hexdigest()
7expires_at = int(time.time()) + 300
8
9print("send this code to user:", code)
10print("store this hash:", code_hash)
11print("expires at:", expires_at)

Then compare the hash of the submitted code instead of comparing raw strings stored in the database.

python
1import hashlib
2
3submitted = "123456"
4submitted_hash = hashlib.sha256(submitted.encode()).hexdigest()
5print(submitted_hash == code_hash)

For short-lived OTP-style codes, hashing is not a complete defense by itself, but it is still a sound storage practice.

A Minimal End-To-End Example

This example generates a code record and validates a submitted value.

python
1import hashlib
2import secrets
3import time
4
5
6def issue_code(length=6, ttl_seconds=300):
7    code = ''.join(str(secrets.randbelow(10)) for _ in range(length))
8    return {
9        "code": code,
10        "hash": hashlib.sha256(code.encode()).hexdigest(),
11        "expires_at": time.time() + ttl_seconds,
12        "attempts_left": 5,
13    }
14
15
16def verify_code(record, submitted):
17    if time.time() > record["expires_at"]:
18        return False
19    if record["attempts_left"] <= 0:
20        return False
21
22    record["attempts_left"] -= 1
23    submitted_hash = hashlib.sha256(submitted.encode()).hexdigest()
24    return submitted_hash == record["hash"]
25
26record = issue_code()
27print("issued code:", record["code"])
28print(verify_code(record, record["code"]))

This keeps the example runnable while showing expiration and attempt tracking.

Do Not Confuse Random Codes With TOTP

If you want authenticator-app style one-time codes, that is usually a TOTP problem rather than a "generate a random code and store it" problem. TOTP systems derive time-based codes from a shared secret and current time window.

So there are two common architectures:

  • server-generated random code stored temporarily and delivered by SMS or email
  • TOTP code derived independently by both client and server from a shared secret

Do not mix them casually. They solve related but different verification flows.

Delivery Security Still Matters

Even a perfectly generated code is only as secure as its delivery channel. Email and SMS are common, but each has real security limitations.

That means code generation is only one part of the design. Account recovery rules, session binding, and anti-abuse controls matter just as much.

Common Pitfalls

  • Using random.randint instead of a cryptographically secure source.
  • Storing the raw verification code in the database without need.
  • Forgetting expiration and attempt limits.
  • Choosing a code length based only on convenience and not on brute-force risk.
  • Treating SMS or email delivery as if it were inherently secure enough to ignore the rest of the system design.

Summary

  • Use a cryptographically secure random source such as Python's secrets.
  • Pick a code format that balances usability and guessing resistance.
  • Expire codes quickly and limit retry attempts.
  • Prefer storing a hash instead of the raw code.
  • Distinguish between server-generated random codes and TOTP-based verification flows.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.