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