61 lines
2.1 KiB
Python
Raw Normal View History

2025-11-10 01:58:41 +01:00
import pyotp
import qrcode
import io
import base64
import secrets
import hashlib
from typing import List, Optional
def generate_totp_secret() -> str:
"""Generates a random base32 TOTP secret."""
return pyotp.random_base32()
def generate_totp_uri(secret: str, account_name: str, issuer_name: str) -> str:
"""Generates a Google Authenticator-compatible TOTP URI."""
return pyotp.totp.TOTP(secret).provisioning_uri(name=account_name, issuer_name=issuer_name)
def generate_qr_code_base64(uri: str) -> str:
"""Generates a base64 encoded QR code image for a given URI."""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffered = io.BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")
def verify_totp_code(secret: str, code: str) -> bool:
"""Verifies a TOTP code against a secret."""
totp = pyotp.TOTP(secret)
return totp.verify(code)
def generate_recovery_codes(num_codes: int = 10) -> List[str]:
"""Generates a list of random recovery codes."""
return [secrets.token_urlsafe(16) for _ in range(num_codes)]
def hash_recovery_code(code: str) -> str:
"""Hashes a single recovery code using SHA256."""
return hashlib.sha256(code.encode('utf-8')).hexdigest()
def verify_recovery_code(plain_code: str, hashed_code: str) -> bool:
"""Verifies a plain recovery code against its hashed version."""
return hash_recovery_code(plain_code) == hashed_code
def hash_recovery_codes(codes: List[str]) -> List[str]:
"""Hashes a list of recovery codes."""
return [hash_recovery_code(code) for code in codes]
def verify_recovery_codes(plain_code: str, hashed_codes: List[str]) -> bool:
"""Verifies if a plain recovery code matches any of the hashed recovery codes."""
for hashed_code in hashed_codes:
if verify_recovery_code(plain_code, hashed_code):
return True
return False