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 alength
argument (default = 6). Inside a generator expression I callsecrets.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
- SMS / Email Delivery – Hook the generator to Twilio or an SMTP library.
- GUI Front‑End – Use
tkinter
orPyQt
to build a tiny desktop OTP tool. - Database Storage – Save the OTP, timestamp, and username in SQLite; mark as “used” after verification.
- 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.