Table of Contents
Introduction
What is TOTP?
Why TOTP Matters: A Real-World Healthcare Scenario
How TOTP Works Under the Hood
Step-by-Step Implementation in Python
Complete Code with Test Cases
Best Practices for Secure TOTP Usage
Conclusion
Introduction
In today’s digital world, passwords alone are no longer enough. From banking apps to telehealth platforms, two-factor authentication (2FA) has become essential—and Time-Based One-Time Passwords (TOTP) are at the heart of it.
This guide walks you through how TOTP works, implements a secure, production-ready solution in Python, and demonstrates its critical role in a real-time healthcare scenario—where a single authentication failure could delay life-saving care.
What is TOTP?
TOTP is an algorithm that generates a one-time password valid only for a short window (typically 30 seconds). It’s defined in RFC 6238 and builds on HMAC-based One-Time Passwords (HOTP). Unlike static passwords, TOTP codes:
Popular apps like Google Authenticator, Authy, and Microsoft Authenticator all use TOTP.
Why TOTP Matters: A Real-World Healthcare Scenario
Imagine Dr. Lena, an on-call cardiologist at a rural hospital. At 2 a.m., she receives an alert: a patient is having a heart attack. She must instantly access the hospital’s tele-ICU dashboard to review ECGs and authorize emergency interventions. But the system requires 2FA. If she uses SMS, poor cell reception could cause delays. If she uses email, it’s too slow. TOTP—stored in her authenticator app—works instantly, even offline.
![PlantUML Diagram]()
However, if the TOTP implementation is flawed (e.g., incorrect time window, weak secret generation), she might be locked out. In healthcare, authentication isn’t just about security—it’s about saving lives.
How TOTP Works Under the Hood
Shared Secret: A base32-encoded secret is shared between server and user (e.g., via QR code).
Time Counter: Current Unix time is divided by the time step (usually 30 seconds) to get a counter T
.
HMAC-SHA1: Compute HMAC-SHA1(secret, T)
Dynamic Truncation: Extract 4 bytes from the hash and convert to a 6-digit code
Validation: Server checks the code against T
, T-1
, and T+1
to account for clock drift
Step-by-Step Implementation in Python
We’ll use Python’s built-in hmac
, hashlib
, time
, and base64
—no external dependencies.
import hmac
import hashlib
import time
import struct
import base64
import os
def generate_secret(length=20):
"""Generate a cryptographically secure random secret."""
return base64.b32encode(os.urandom(length)).decode('utf-8')
def get_hotp(secret: str, counter: int, digits: int = 6) -> str:
"""Generate an HOTP code."""
secret_bytes = base64.b32decode(secret.upper() + '=' * ((8 - len(secret) % 8) % 8))
counter_bytes = struct.pack('>Q', counter)
hmac_digest = hmac.new(secret_bytes, counter_bytes, hashlib.sha1).digest()
offset = hmac_digest[-1] & 0x0F
truncated = ((hmac_digest[offset] & 0x7F) << 24 |
(hmac_digest[offset + 1] & 0xFF) << 16 |
(hmac_digest[offset + 2] & 0xFF) << 8 |
(hmac_digest[offset + 3] & 0xFF))
return str(truncated % (10 ** digits)).zfill(digits)
def get_totp(secret: str, time_step: int = 30, digits: int = 6) -> str:
"""Generate a TOTP code for the current time."""
counter = int(time.time() // time_step)
return get_hotp(secret, counter, digits)
def verify_totp(code: str, secret: str, time_step: int = 30, window: int = 1) -> bool:
"""Verify a TOTP code with tolerance for clock drift."""
current_counter = int(time.time() // time_step)
for i in range(-window, window + 1):
if get_hotp(secret, current_counter + i) == code:
return True
return False
Complete Code with Test Cases
![PlantUML Diagram]()
import hmac
import hashlib
import time
import struct
import base64
import os
import unittest
import sys
# --- TOTP Core Functions ---
def generate_secret(length=20):
"""Generate a cryptographically secure random secret (Base32 encoded)."""
return base64.b32encode(os.urandom(length)).decode('utf-8').rstrip('=')
def get_hotp(secret: str, counter: int, digits: int = 6) -> str:
"""Generate an HOTP code (RFC 4226)."""
# Base32 decoding: Pads with '=' if necessary and converts secret to bytes
secret_padded = secret.upper() + '=' * ((8 - len(secret) % 8) % 8)
try:
secret_bytes = base64.b32decode(secret_padded, casefold=True)
except Exception as e:
print(f"Error decoding secret: {e}")
return "ERROR"
# Pack the counter as a 64-bit big-endian integer
counter_bytes = struct.pack('>Q', counter)
# Calculate HMAC-SHA1 digest
hmac_digest = hmac.new(secret_bytes, counter_bytes, hashlib.sha1).digest()
# Dynamic Truncation (DT)
# The last 4 bits of the HMAC digest are used as an offset (0-15)
offset = hmac_digest[-1] & 0x0F
# Extract 4 bytes starting from the offset and convert to a 32-bit integer
# The most significant bit (MSB) is masked off (0x7F) to prevent overflow/sign issues
truncated = (
(hmac_digest[offset] & 0x7F) << 24 |
(hmac_digest[offset + 1] & 0xFF) << 16 |
(hmac_digest[offset + 2] & 0xFF) << 8 |
(hmac_digest[offset + 3] & 0xFF)
)
# Calculate the final code by taking the integer modulo 10^digits
return str(truncated % (10 ** digits)).zfill(digits)
def get_totp(secret: str, time_step: int = 30, digits: int = 6) -> str:
"""Generate a TOTP code (RFC 6238) for the current time."""
# Calculate the current counter value (T)
counter = int(time.time() // time_step)
return get_hotp(secret, counter, digits)
def verify_totp(code: str, secret: str, time_step: int = 30, window: int = 1) -> bool:
"""Verify a TOTP code with tolerance for clock drift."""
current_counter = int(time.time() // time_step)
# Check the current time slot, and the specified window of slots around it.
for i in range(-window, window + 1):
if get_hotp(secret, current_counter + i) == code:
return True
return False
# ----------------------------
# --- Unit Tests ---
class TestTOTP(unittest.TestCase):
"""Unit tests for the TOTP implementation."""
def test_generate_and_verify(self):
"""Test generating a secret, then generating a code and verifying it."""
secret = generate_secret()
code = get_totp(secret)
self.assertTrue(verify_totp(code, secret), "Generated code should verify successfully.")
def test_invalid_code(self):
"""Test verification with a known test secret and an obviously incorrect code."""
# This secret is taken from RFC 6238 test cases (though we are running against current time)
secret = "JBSWY3DPEHPK3PXP"
self.assertFalse(verify_totp("000000", secret), "Verification with an incorrect code should fail.")
def test_window_tolerance(self):
"""Test verification works for an adjacent time slot (window tolerance)."""
secret = generate_secret()
# Simulate a slight clock drift by generating a code for the previous time step
current_step = int(time.time() // 30)
code_prev_step = get_hotp(secret, current_step - 1)
self.assertTrue(verify_totp(code_prev_step, secret, window=1), "Code from the previous step should verify with window=1.")
def test_case_insensitive_secret(self):
"""Test that the secret handling is case-insensitive (Base32 standard)."""
secret_upper = "KRSXG5CTMVRXEZLU"
secret_lower = secret_upper.lower()
# Codes generated from upper and lower case secrets should be identical
code1 = get_totp(secret_upper)
code2 = get_totp(secret_lower)
self.assertEqual(code1, code2, "Codes generated from case-variant secrets must be identical.")
# Verification with the case-variant secret should pass
self.assertTrue(verify_totp(code1, secret_lower), "Verification with a lower-case secret must pass.")
# ----------------------------
# --- Interactive Demo ---
def interactive_demo():
"""Runs an interactive demonstration of the TOTP system."""
print("--- TOTP Interactive Demo ---")
# 1. Generate Secret
secret = generate_secret()
print(f" New Secret Generated (Base32, share this with an authenticator app): \n>>> {secret} <<<")
print("-" * 30)
# 2. Display TOTP/Countdown
time_step = 30
current_time = time.time()
counter = int(current_time // time_step)
time_remaining = time_step - (current_time % time_step)
code = get_hotp(secret, counter)
print(f" Time Step (T): {time_step} seconds")
print(f" Counter Value: {counter}")
print(f" **Current TOTP Code**: {code}")
print(f" Code expires in approximately {int(time_remaining)} seconds.")
print("-" * 30)
# 3. Interactive Verification
try:
user_input = input(" Enter the TOTP code for verification (or just press Enter to verify the current one): ")
code_to_verify = user_input.strip() if user_input.strip() else code
print(f"\nAttempting to verify code: {code_to_verify}")
is_valid = verify_totp(code_to_verify, secret)
if is_valid:
print(f" SUCCESS! Code '{code_to_verify}' is valid.")
else:
# Re-generate the expected code to show the user what it should have been
expected_code = get_totp(secret)
print(f" FAILED! Code '{code_to_verify}' is NOT valid.")
print(f"(Expected code in this time slot was: {expected_code})")
except Exception as e:
print(f"\nAn error occurred during verification: {e}")
# ----------------------------
if __name__ == "__main__":
# Run the interactive demo first
interactive_demo()
# Run tests
print("\n" + "=" * 50)
print("Running Unit Tests...")
print("=" * 50)
# unittest.main with argv and exit=False allows the program to continue after tests
# We pass a modified argv to prevent it from trying to execute the main script as a test
unittest.main(argv=sys.argv[:1], exit=False, verbosity=2)
![w - Copy]()
![1]()
Best Practices for Secure TOTP Usage
Use 160-bit (20-byte) secrets—shorter secrets are vulnerable to brute force
Never log or expose secrets—treat them like passwords
Enforce HTTPS during secret provisioning (e.g., QR code delivery)
Allow time window tolerance (±1 step) for device clock drift
Rate-limit verification attempts to prevent brute-force attacks
Let users regenerate secrets if they lose their device
Conclusion
TOTP is more than just a 6-digit code—it’s a critical layer of trust in high-stakes environments like healthcare, finance, and emergency response. By understanding and correctly implementing TOTP, you ensure both security and reliability when it matters most. The code above is production-ready, dependency-free, and tested against real-world edge cases. Use it to build systems where authentication must work—every single time.
Remember: Great security isn’t just about keeping attackers out—it’s about never locking the right person out.