How To Generate OTP Using Python

I’m a big fan of practical mini‑projects the kind you can build, test, and extend in a single sitting. Today I’ll walk you through one of my favorites: a one‑time password (OTP) generator written in pure Python. Along the way I’ll show you a mistake that bites beginners, then layer in a few extra features so you can keep practicing.

The 6‑Digit OTP Code

secrets

def generate_otp(length: int = 6) -> str:
"""Return a string of `length` random digits."""
return ''.join(str(secrets.randbelow(10)) for _ in range(length))

print("Generated OTP:", generate_otp())

What’s Happening

  • secrets module – Python’s built‑in, cryptographically secure random‑number toolkit.
  • generate_otp() – I accept a length argument (default = 6). Inside a generator expression I call secrets.randbelow(10) that spits out a single digit (0‑9).
  • ''.join() – Strings are immutable, so I glue each digit together in one efficient step.
  • Print – Finally I display the OTP so you can copy‑paste it while testing.

A Sneaky Error You Might Hit

If you ever drop the str() cast by accident, like this:

''.join(secrets.randbelow(10) for _ in range(length))

Python yells back:

TypeError: 'int' object is not iterable

join() expects strings inside an iterable. Passing raw integers violates both rules there’s nothing to iterate and nothing to join as text. The quick fix is to wrap each number with str() just like I did in the working snippet.

Level‑Up Practice Features

Feel free to copy these into a fresh file and experiment.

Alphanumeric OTPs

secrets, string

def generate_alphanumeric_otp(length: int = 6) -> str:
chars = string.ascii_letters + string.digits # a‑z A‑Z 0‑9
return ''.join(secrets.choice(chars) for _ in range(length))

print("Alphanumeric OTP:", generate_alphanumeric_otp())

Sometimes security policy demands letters and digits. That’s a one‑liner with string:

Expiry Timer (5‑Minute Window)

secrets, string, time

def generate_otp_with_expiry(length: int = 6, expiry: int = 300):
otp = ''.join(secrets.choice(string.digits) for _ in range(length))
ts = time.time() # seconds since epoch
return otp, ts, expiry

def is_otp_valid(ts: float, expiry: int) -> bool:
return time.time() - ts <= expiry

otp, ts, expiry = generate_otp_with_expiry()
print(f"OTP {otp} expires in {expiry} s")

# Later…
print("Still valid?" , is_otp_valid(ts, expiry))

Batch OTPs for Multiple Users

secrets

def generate_batch_otps(users: list[str], length: int = 6) -> dict[str, str]:
return {u: ''.join(str(secrets.randbelow(10)) for _ in range(length))
for u in users}

users = ["alice", "bob", "carol"]
print("User OTP map:", generate_batch_otps(users))

Project Ideas to Keep You Coding

  1. SMS / Email Delivery – Hook the generator to Twilio or an SMTP library.
  2. GUI Front‑End – Use tkinter or PyQt to build a tiny desktop OTP tool.
  3. Database Storage – Save the OTP, timestamp, and username in SQLite; mark as “used” after verification.
  4. Unit Tests – Write pytest cases to confirm length, uniqueness, and expiry logic.

Final Thoughts

I’ve shown you how I build a secure 6‑digit OTP, the common TypeError trap, and a handful of add‑ons that turn a toy snippet into a production‑ready helper. Grab the code, play with the settings, and bolt on new features because the best way to learn Python security tricks is to code, break, and fix them yourself.

Related blog posts