Newer
Older
gnexus-creds / gnexus_creds / crypto.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy 4 days ago 1 KB Implement initial gnexus-creds MVP scaffold
"""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()