"""Encryption helpers."""
import base64
import hashlib
import os
import secrets
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
ALGORITHM = "AESGCM"
def _b64e(value: bytes) -> str:
return base64.urlsafe_b64encode(value).decode().rstrip("=")
def _b64d(value: str) -> bytes:
padding = "=" * (-len(value) % 4)
return base64.urlsafe_b64decode(value + padding)
def derive_master_key(master_key: str) -> bytes:
return hashlib.sha256(master_key.encode()).digest()
def new_raw_key() -> bytes:
return AESGCM.generate_key(bit_length=256)
def encrypt_bytes(key: bytes, plaintext: bytes, *, aad: bytes | None = None) -> tuple[bytes, bytes]:
nonce = os.urandom(12)
ciphertext = AESGCM(key).encrypt(nonce, plaintext, aad)
return nonce, ciphertext
def decrypt_bytes(
key: bytes, nonce: bytes, ciphertext: bytes, *, aad: bytes | None = None
) -> bytes:
return AESGCM(key).decrypt(nonce, ciphertext, aad)
def encrypt_text(key: bytes, key_id: str, value: str) -> dict:
nonce, ciphertext = encrypt_bytes(key, value.encode())
return {
"algorithm": ALGORITHM,
"key_id": key_id,
"nonce": _b64e(nonce),
"ciphertext": _b64e(ciphertext),
}
def decrypt_text(key: bytes, payload: dict) -> str:
return decrypt_bytes(key, _b64d(payload["nonce"]), _b64d(payload["ciphertext"])).decode()
def token_secret() -> str:
return secrets.token_urlsafe(32)
def token_hash(token: str) -> str:
return hashlib.sha256(token.encode()).hexdigest()