🔒 Repository is read-only – file editing is disabled.

PaganLinux/pagan-web-v2/totp.py main

44 linii Raw ← Powrót
1234567891011121314151617181920212223242526272829303132333435363738394041424344
"""
TOTP – Two-Factor Authentication (RFC 6238)
Uses only stdlib: hmac, base64, struct, time, secrets
"""
import hmac, base64, struct, time, secrets, hashlib

def generate_secret():
    """Generuje 32-znakowy base32 secret dla TOTP."""
    return base64.b32encode(secrets.token_bytes(20)).decode().rstrip("=")

def _hotp(secret: str, counter: int, digits: int = 6) -> str:
    """RFC 4226 HOTP."""
    secret = secret.strip().upper()
    # Padding do wielokrotności 8
    pad = (8 - len(secret) % 8) % 8
    key = base64.b32decode(secret + "=" * pad)
    msg = struct.pack(">Q", counter)
    h = hmac.new(key, msg, hashlib.sha1).digest()
    offset = h[-1] & 0x0F
    binary = struct.unpack(">I", h[offset:offset + 4])[0] & 0x7FFFFFFF
    return str(binary % (10 ** digits)).zfill(digits)

def generate_totp(secret: str, digits: int = 6, period: int = 30) -> str:
    """RFC 6238 TOTP."""
    counter = int(time.time() // period)
    return _hotp(secret, counter, digits)

def verify_totp(secret: str, code: str, window: int = 1) -> bool:
    """Weryfikuje kod TOTP z tolerancją +/- window kroków."""
    if not secret or not code:
        return False
    now = int(time.time() // 30)
    for i in range(-window, window + 1):
        if secrets.compare_digest(_hotp(secret, now + i), code):
            return True
    return False

def generate_recovery_codes(count: int = 8) -> list:
    """Generuje listę kodów zapasowych."""
    return [secrets.token_hex(5).upper() for _ in range(count)]

def hash_recovery_codes(codes: list) -> list:
    """Hashuje kody zapasowe (SHA256) do przechowania w DB."""
    return [hashlib.sha256(c.encode()).hexdigest() for c in codes]