Cryptographic recipes and primitives for Python developers
—
Time-based and HMAC-based one-time password (OTP) generation and verification for implementing two-factor authentication systems. Compatible with standard authenticator apps.
from cryptography.hazmat.primitives.twofactor.totp import TOTP
from cryptography.hazmat.primitives.twofactor.hotp import HOTP
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidTokenclass TOTP:
def __init__(self, key: bytes, length: int, algorithm, time_step: int, backend=None):
"""
Initialize TOTP generator.
Args:
key (bytes): Shared secret key (base32 decoded)
length (int): OTP length (6 or 8 digits)
algorithm: Hash algorithm (hashes.SHA1(), hashes.SHA256(), etc.)
time_step (int): Time step in seconds (usually 30)
backend: Cryptographic backend
"""
def generate(self, time: int) -> bytes:
"""
Generate TOTP for specific time.
Args:
time (int): Unix timestamp
Returns:
bytes: OTP as bytes (e.g., b'123456')
"""
def verify(self, totp: bytes, time: int) -> None:
"""
Verify TOTP for specific time.
Args:
totp (bytes): OTP to verify
time (int): Unix timestamp
Raises:
InvalidToken: If TOTP is invalid for given time
"""class HOTP:
def __init__(self, key: bytes, length: int, algorithm, backend=None):
"""
Initialize HOTP generator.
Args:
key (bytes): Shared secret key
length (int): OTP length (6 or 8 digits)
algorithm: Hash algorithm (hashes.SHA1() most common)
backend: Cryptographic backend
"""
def generate(self, counter: int) -> bytes:
"""
Generate HOTP for specific counter value.
Args:
counter (int): Counter value
Returns:
bytes: OTP as bytes
"""
def verify(self, hotp: bytes, counter: int) -> None:
"""
Verify HOTP for specific counter.
Args:
hotp (bytes): OTP to verify
counter (int): Counter value
Raises:
InvalidToken: If HOTP is invalid for given counter
"""from cryptography.hazmat.primitives.twofactor.totp import TOTP
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidToken
import time
import base64
import secrets
# Generate random secret key
secret_key = secrets.token_bytes(20) # 160-bit key
print(f"Secret key (base32): {base64.b32encode(secret_key).decode()}")
# Create TOTP instance (standard 6-digit, 30-second window)
totp = TOTP(secret_key, 6, hashes.SHA1(), 30)
# Generate TOTP for current time
current_time = int(time.time())
token = totp.generate(current_time)
print(f"Current TOTP: {token.decode()}")
# Verify token
try:
totp.verify(token, current_time)
print("TOTP verification: SUCCESS")
except InvalidToken:
print("TOTP verification: FAILED")
# Test with slightly different time (within window)
totp.verify(token, current_time + 15) # Still valid within 30-second window
print("TOTP still valid within time window")from cryptography.hazmat.primitives.twofactor.totp import TOTP
from cryptography.hazmat.primitives import hashes
import base64
import secrets
import urllib.parse
class TOTPSetup:
def __init__(self, issuer: str = "MyApp"):
self.issuer = issuer
def generate_secret(self) -> str:
"""Generate base32-encoded secret for user"""
secret_bytes = secrets.token_bytes(20)
return base64.b32encode(secret_bytes).decode()
def generate_qr_url(self, secret: str, account_name: str) -> str:
"""Generate URL for QR code compatible with authenticator apps"""
# Standard format: otpauth://totp/issuer:account?secret=SECRET&issuer=ISSUER
secret_clean = secret.replace(' ', '') # Remove spaces
params = {
'secret': secret_clean,
'issuer': self.issuer,
'algorithm': 'SHA1',
'digits': '6',
'period': '30'
}
query_string = urllib.parse.urlencode(params)
label = f"{self.issuer}:{account_name}"
url = f"otpauth://totp/{urllib.parse.quote(label)}?{query_string}"
return url
def create_totp(self, secret: str) -> TOTP:
"""Create TOTP instance from base32 secret"""
secret_bytes = base64.b32decode(secret.upper())
return TOTP(secret_bytes, 6, hashes.SHA1(), 30)
# Usage
setup = TOTPSetup("MySecureApp")
# Generate secret for new user
user_secret = setup.generate_secret()
print(f"User secret: {user_secret}")
# Generate QR code URL
qr_url = setup.generate_qr_url(user_secret, "alice@example.com")
print(f"QR Code URL: {qr_url}")
# Create TOTP instance for verification
user_totp = setup.create_totp(user_secret)
# Server-side verification
import time
current_token = user_totp.generate(int(time.time()))
print(f"Expected token: {current_token.decode()}")from cryptography.hazmat.primitives.twofactor.hotp import HOTP
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidToken
import secrets
class HOTPAuthenticator:
def __init__(self, key: bytes, initial_counter: int = 0):
self.hotp = HOTP(key, 6, hashes.SHA1())
self.counter = initial_counter
def generate_next_token(self) -> str:
"""Generate next HOTP token and increment counter"""
token = self.hotp.generate(self.counter)
self.counter += 1
return token.decode()
def verify_token(self, token: str, window: int = 3) -> bool:
"""
Verify HOTP token within a counter window.
Args:
token: Token to verify
window: Look-ahead window for counter synchronization
Returns:
bool: True if token is valid
"""
token_bytes = token.encode()
# Try current counter and look-ahead window
for i in range(window + 1):
try:
self.hotp.verify(token_bytes, self.counter + i)
# Token valid, update counter
self.counter = self.counter + i + 1
return True
except InvalidToken:
continue
return False
# Usage
secret_key = secrets.token_bytes(20)
authenticator = HOTPAuthenticator(secret_key)
print("Generated HOTP tokens:")
for i in range(5):
token = authenticator.generate_next_token()
print(f"Token {i+1}: {token}")
# Simulate client/server verification
client_auth = HOTPAuthenticator(secret_key, 0) # Reset counter
server_auth = HOTPAuthenticator(secret_key, 0) # Reset counter
# Client generates token
client_token = client_auth.generate_next_token()
print(f"\nClient generated: {client_token}")
# Server verifies token
is_valid = server_auth.verify_token(client_token)
print(f"Server verification: {'SUCCESS' if is_valid else 'FAILED'}")from cryptography.hazmat.primitives.twofactor.totp import TOTP
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidToken
import time
import base64
import secrets
import json
class TwoFactorAuth:
def __init__(self):
self.users = {} # In production, use proper database
def enroll_user(self, username: str) -> dict:
"""Enroll user for 2FA"""
# Generate secret
secret_bytes = secrets.token_bytes(20)
secret_b32 = base64.b32encode(secret_bytes).decode()
# Store user data
self.users[username] = {
'secret': secret_b32,
'secret_bytes': secret_bytes,
'enrolled': True,
'backup_codes': self._generate_backup_codes()
}
# Return enrollment data
return {
'secret': secret_b32,
'qr_url': self._generate_qr_url(secret_b32, username),
'backup_codes': self.users[username]['backup_codes']
}
def verify_setup_token(self, username: str, token: str) -> bool:
"""Verify initial setup token"""
if username not in self.users:
return False
return self._verify_totp_token(username, token)
def authenticate(self, username: str, token: str) -> dict:
"""Authenticate user with 2FA token"""
if username not in self.users or not self.users[username]['enrolled']:
return {'success': False, 'reason': 'User not enrolled'}
# Check if it's a backup code
if token in self.users[username]['backup_codes']:
# Remove used backup code
self.users[username]['backup_codes'].remove(token)
return {'success': True, 'method': 'backup_code'}
# Verify TOTP token with time window tolerance
if self._verify_totp_token_with_window(username, token):
return {'success': True, 'method': 'totp'}
return {'success': False, 'reason': 'Invalid token'}
def _verify_totp_token(self, username: str, token: str) -> bool:
"""Verify TOTP token for exact current time"""
try:
secret_bytes = self.users[username]['secret_bytes']
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
current_time = int(time.time())
totp.verify(token.encode(), current_time)
return True
except (InvalidToken, KeyError):
return False
def _verify_totp_token_with_window(self, username: str, token: str, window: int = 1) -> bool:
"""Verify TOTP token with time window tolerance"""
try:
secret_bytes = self.users[username]['secret_bytes']
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
current_time = int(time.time())
# Try current time and adjacent time windows
for offset in range(-window, window + 1):
try:
test_time = current_time + (offset * 30)
totp.verify(token.encode(), test_time)
return True
except InvalidToken:
continue
return False
except KeyError:
return False
def _generate_backup_codes(self, count: int = 10) -> list:
"""Generate backup codes for recovery"""
codes = []
for _ in range(count):
code = secrets.token_hex(4).upper() # 8-character hex codes
codes.append(code)
return codes
def _generate_qr_url(self, secret: str, username: str) -> str:
"""Generate QR code URL"""
import urllib.parse
params = {
'secret': secret,
'issuer': 'MyApp',
'algorithm': 'SHA1',
'digits': '6',
'period': '30'
}
query_string = urllib.parse.urlencode(params)
label = f"MyApp:{username}"
return f"otpauth://totp/{urllib.parse.quote(label)}?{query_string}"
# Usage Example
auth_system = TwoFactorAuth()
# Enroll user
enrollment_data = auth_system.enroll_user("alice@example.com")
print("Enrollment data:")
print(f"Secret: {enrollment_data['secret']}")
print(f"QR URL: {enrollment_data['qr_url']}")
print(f"Backup codes: {enrollment_data['backup_codes']}")
# Simulate user setting up authenticator app and generating token
# For testing, we'll generate the expected token
secret_bytes = base64.b32decode(enrollment_data['secret'])
totp = TOTP(secret_bytes, 6, hashes.SHA1(), 30)
current_token = totp.generate(int(time.time())).decode()
print(f"\nCurrent expected token: {current_token}")
# Verify setup
setup_verified = auth_system.verify_setup_token("alice@example.com", current_token)
print(f"Setup verification: {'SUCCESS' if setup_verified else 'FAILED'}")
# Test authentication
auth_result = auth_system.authenticate("alice@example.com", current_token)
print(f"Authentication result: {auth_result}")
# Test backup code
backup_code = enrollment_data['backup_codes'][0]
backup_result = auth_system.authenticate("alice@example.com", backup_code)
print(f"Backup code result: {backup_result}")
# Test backup code reuse (should fail)
backup_reuse = auth_system.authenticate("alice@example.com", backup_code)
print(f"Backup code reuse: {backup_reuse}")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography