CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cryptography

Cryptographic recipes and primitives for Python developers

Pending
Overview
Eval results
Files

two-factor-auth.mddocs/

Two-Factor Authentication

Time-based and HMAC-based one-time password (OTP) generation and verification for implementing two-factor authentication systems. Compatible with standard authenticator apps.

Core Imports

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 InvalidToken

Capabilities

TOTP (Time-based One-Time Password)

class 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
        """

HOTP (HMAC-based One-Time Password)

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
        """

Usage Examples

Basic TOTP Implementation

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")

TOTP QR Code Generation for Authenticator Apps

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()}")

HOTP Counter-based Authentication

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'}")

Complete 2FA Authentication System

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}")

Security Considerations

  • Secret Storage: Store TOTP secrets securely (encrypted in database)
  • Time Synchronization: Ensure server time is accurate (NTP)
  • Window Tolerance: Allow small time window (±30 seconds) for clock skew
  • Rate Limiting: Implement rate limiting for OTP verification attempts
  • Backup Codes: Provide backup codes for account recovery
  • Secret Generation: Use cryptographically secure random number generator
  • Algorithm Choice: SHA-1 most compatible, SHA-256 for higher security
  • Counter Management: For HOTP, implement proper counter synchronization
  • Replay Prevention: Track recently used tokens to prevent replay attacks

Install with Tessl CLI

npx tessl i tessl/pypi-cryptography

docs

aead-ciphers.md

asymmetric-cryptography.md

hash-functions.md

index.md

key-derivation.md

key-serialization.md

message-authentication.md

symmetric-ciphers.md

symmetric-encryption.md

two-factor-auth.md

utilities.md

x509-certificates.md

tile.json