CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-otp

A pluggable framework for adding two-factor authentication to Django using one-time passwords.

Pending
Overview
Eval results
Files

oath-algorithms.mddocs/

OATH Algorithms

Low-level implementations of HOTP and TOTP algorithms following RFC 4226 and RFC 6238. These functions provide the cryptographic foundation for OTP generation and can be used directly or for custom device implementations.

Capabilities

HOTP Algorithm

HMAC-based one-time password algorithm (RFC 4226).

def hotp(key, counter, digits=6):
    """
    HOTP algorithm implementation (RFC 4226).
    
    Parameters:
    - key: bytes - Secret key for HMAC
    - counter: int - Counter value
    - digits: int - Number of digits in output (default: 6)
    
    Returns:
    int - HOTP value
    """

TOTP Algorithm

Time-based one-time password algorithm (RFC 6238).

def totp(key, step=30, t0=0, digits=6, drift=0):
    """
    TOTP algorithm implementation (RFC 6238).
    
    Parameters:
    - key: bytes - Secret key for HMAC
    - step: int - Time step in seconds (default: 30)
    - t0: int - Unix time to start counting (default: 0)
    - digits: int - Number of digits in output (default: 6)
    - drift: int - Drift compensation in time steps (default: 0)
    
    Returns:
    int - TOTP value
    """

TOTP Class

Advanced TOTP interface with intermediate access and verification capabilities.

class TOTP:
    """TOTP interface with intermediate access."""
    
    def __init__(self, key, step=30, t0=0, digits=6, drift=0):
        """
        Initialize TOTP instance.
        
        Parameters:
        - key: bytes - Secret key
        - step: int - Time step in seconds
        - t0: int - Start time (Unix timestamp)
        - digits: int - Number of digits
        - drift: int - Drift value in time steps
        """
    
    @property
    def time(self):
        """Current time (settable for testing)."""
    
    @time.setter
    def time(self, value):
        """Set current time."""
    
    def token(self) -> int:
        """Compute TOTP token for current time."""
    
    def t(self) -> int:
        """Compute current time step."""
    
    def verify(self, token, tolerance=0, min_t=None) -> int:
        """
        Verify token with tolerance.
        
        Parameters:
        - token: int - Token to verify
        - tolerance: int - Time step tolerance
        - min_t: int or None - Minimum time step to accept
        
        Returns:
        int - Time step if valid, None if invalid
        """

Usage Examples

Basic HOTP Usage

from django_otp.oath import hotp
import secrets

# Generate secret key
key = secrets.token_bytes(20)  # 160-bit key

# Generate HOTP tokens
counter = 0
token1 = hotp(key, counter)     # First token
token2 = hotp(key, counter + 1) # Second token

print(f"Token 1: {token1:06d}")  # Zero-padded 6 digits
print(f"Token 2: {token2:06d}")

# Generate 8-digit tokens
token_8 = hotp(key, counter, digits=8)
print(f"8-digit token: {token_8:08d}")

Basic TOTP Usage

from django_otp.oath import totp
import time
import secrets

# Generate secret key
key = secrets.token_bytes(20)

# Generate current TOTP token
current_token = totp(key)
print(f"Current token: {current_token:06d}")

# Generate token for specific time
specific_time = int(time.time()) - 30  # 30 seconds ago
past_token = totp(key, t0=specific_time)

# Generate token with different step size
token_60s = totp(key, step=60)  # 60-second intervals
print(f"60s step token: {token_60s:06d}")

Advanced TOTP Usage

from django_otp.oath import TOTP
import time

# Create TOTP instance
key = b'secret_key_here'
totp_instance = TOTP(key, step=30, digits=6)

# Get current token
current_token = totp_instance.token()
print(f"Current token: {current_token:06d}")

# Get current time step
current_t = totp_instance.t()
print(f"Current time step: {current_t}")

# Verify token with tolerance
test_token = current_token
result = totp_instance.verify(test_token, tolerance=1)
if result is not None:
    print(f"Token verified at time step: {result}")
else:
    print("Token verification failed")

# Test with historical tokens (prevent replay)
min_acceptable_t = current_t - 5  # Don't accept tokens older than 5 steps
result = totp_instance.verify(test_token, tolerance=1, min_t=min_acceptable_t)

Custom Device Implementation

from django_otp.oath import TOTP
from django_otp.models import Device
import binascii

class CustomTOTPDevice(Device):
    """Custom TOTP device using OATH algorithms directly."""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.secret_key = binascii.unhexlify(self.key)
        self.totp = TOTP(self.secret_key, step=30, digits=6)
        self.last_used_t = -1
    
    def generate_token(self):
        """Generate current TOTP token."""
        return f"{self.totp.token():06d}"
    
    def verify_token(self, token):
        """Verify TOTP token with replay protection."""
        try:
            token_int = int(token)
        except ValueError:
            return False
        
        # Verify with tolerance, preventing replay
        t = self.totp.verify(
            token_int, 
            tolerance=1, 
            min_t=self.last_used_t + 1
        )
        
        if t is not None:
            self.last_used_t = t
            self.save()
            return True
        
        return False

Token Generation Utilities

from django_otp.oath import hotp, totp
import qrcode
import io
import base64

def generate_hotp_sequence(key, start_counter, count=10):
    """Generate a sequence of HOTP tokens."""
    tokens = []
    for i in range(count):
        token = hotp(key, start_counter + i)
        tokens.append(f"{token:06d}")
    return tokens

def generate_totp_window(key, window_size=5):
    """Generate TOTP tokens for current time window."""
    import time
    
    tokens = {}
    current_time = int(time.time())
    
    for offset in range(-window_size, window_size + 1):
        t = current_time + (offset * 30)
        token = totp(key, t0=t)
        tokens[offset] = f"{token:06d}"
    
    return tokens

# Usage
key = b'shared_secret_key'

# Generate HOTP sequence
hotp_tokens = generate_hotp_sequence(key, 0, 5)
print("HOTP sequence:", hotp_tokens)

# Generate TOTP window
totp_window = generate_totp_window(key, 2)
for offset, token in totp_window.items():
    print(f"T{offset:+d}: {token}")

Performance Testing

from django_otp.oath import hotp, totp, TOTP
import time

def benchmark_algorithms():
    """Benchmark OATH algorithm performance."""
    
    key = b'benchmark_secret_key_here_123'
    iterations = 10000
    
    # Benchmark HOTP
    start = time.time()
    for i in range(iterations):
        hotp(key, i)
    hotp_time = time.time() - start
    
    # Benchmark TOTP
    start = time.time()
    for i in range(iterations):
        totp(key)
    totp_time = time.time() - start
    
    # Benchmark TOTP class
    totp_instance = TOTP(key)
    start = time.time()
    for i in range(iterations):
        totp_instance.token()
    totp_class_time = time.time() - start
    
    print(f"HOTP: {hotp_time:.3f}s ({iterations/hotp_time:.0f} ops/sec)")
    print(f"TOTP: {totp_time:.3f}s ({iterations/totp_time:.0f} ops/sec)")
    print(f"TOTP Class: {totp_class_time:.3f}s ({iterations/totp_class_time:.0f} ops/sec)")

# Run benchmark
benchmark_algorithms()

Algorithm Validation

from django_otp.oath import hotp, totp

def validate_rfc_test_vectors():
    """Validate against RFC 4226/6238 test vectors."""
    
    # RFC 4226 HOTP test vectors
    rfc4226_key = b"12345678901234567890"
    rfc4226_vectors = [
        (0, 755224),
        (1, 287082),
        (2, 359152),
        (3, 969429),
        (4, 338314)
    ]
    
    print("RFC 4226 HOTP validation:")
    for counter, expected in rfc4226_vectors:
        result = hotp(rfc4226_key, counter)
        status = "PASS" if result == expected else "FAIL"
        print(f"Counter {counter}: {result} (expected {expected}) - {status}")
    
    # RFC 6238 TOTP test vectors (simplified)
    rfc6238_key = b"12345678901234567890"
    
    # Test vector for Unix time 59 (T0=0, step=30, so T=1)
    test_time = 59
    expected_totp = 94287082  # From RFC 6238
    
    # Calculate T value: (59 - 0) // 30 = 1
    t_value = test_time // 30
    result = hotp(rfc6238_key, t_value)  # TOTP is HOTP with T as counter
    
    print(f"\nRFC 6238 TOTP validation:")
    print(f"Time {test_time}: {result} (expected {expected_totp}) - {'PASS' if result == expected_totp else 'FAIL'}")

# Run validation
validate_rfc_test_vectors()

Install with Tessl CLI

npx tessl i tessl/pypi-django-otp

docs

admin-interface.md

core-authentication.md

device-models.md

django-integration.md

email-devices.md

hotp-devices.md

index.md

oath-algorithms.md

static-tokens.md

totp-devices.md

tile.json