🔒 Repository is read-only – file editing is disabled.
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]