Python  

Generate and Verify TOTP (Time-Based One-Time Passwords) Using Python

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:

  • Change every 30 seconds

  • Are derived from a shared secret and current Unix time

  • Cannot be reused

  • Work offline (no SMS or network needed)

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

  1. Shared Secret: A base32-encoded secret is shared between server and user (e.g., via QR code).

  2. Time Counter: Current Unix time is divided by the time step (usually 30 seconds) to get a counter T.

  3. HMAC-SHA1: Compute HMAC-SHA1(secret, T)

  4. Dynamic Truncation: Extract 4 bytes from the hash and convert to a 6-digit code

  5. 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 - Copy1

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.