54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
import base64
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import time
|
|
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
from app.config import settings
|
|
|
|
|
|
def get_aes_key() -> bytes:
|
|
"""Derive a 32-byte AES key from the configured encryption key."""
|
|
return hashlib.sha256(settings.ENCRYPTION_KEY.encode()).digest()
|
|
|
|
|
|
def encrypt_temp_token(record_id: str, expires_in: int = 3600) -> str:
|
|
"""Create an encrypted token containing record_id and expiry timestamp."""
|
|
payload = json.dumps({"rid": record_id, "exp": int(time.time()) + expires_in})
|
|
aesgcm = AESGCM(get_aes_key())
|
|
nonce = AESGCM.generate_key(bit_length=96)
|
|
ciphertext = aesgcm.encrypt(nonce, payload.encode(), None)
|
|
token_bytes = nonce + ciphertext
|
|
return base64.urlsafe_b64encode(token_bytes).decode()
|
|
|
|
|
|
def decrypt_temp_token(token: str) -> str | None:
|
|
"""Decrypt token and return record_id if valid and not expired."""
|
|
try:
|
|
token_bytes = base64.urlsafe_b64decode(token)
|
|
nonce = token_bytes[:12]
|
|
ciphertext = token_bytes[12:]
|
|
aesgcm = AESGCM(get_aes_key())
|
|
payload = aesgcm.decrypt(nonce, ciphertext, None)
|
|
data = json.loads(payload)
|
|
if data["exp"] < time.time():
|
|
return None
|
|
return data["rid"]
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def hmac_sign(data: str) -> str:
|
|
"""Create HMAC-SHA256 signature."""
|
|
return hmac.new(
|
|
settings.SECRET_KEY.encode(), data.encode(), hashlib.sha256
|
|
).hexdigest()
|
|
|
|
|
|
def hmac_verify(data: str, signature: str) -> bool:
|
|
"""Verify HMAC-SHA256 signature."""
|
|
expected = hmac_sign(data)
|
|
return hmac.compare_digest(expected, signature)
|