Files
secpaste/crypto/aes.py
nanoclaw ff41256f2f Initial commit: SecPaste encrypted pastebin client
SecPaste is a Python library and CLI tool for sharing encrypted content via
public pastebin services with zero-knowledge architecture.

Features:
- Pluggable crypto backends (AES-256-GCM, ChaCha20-Poly1305, Kyber-768)
- Pluggable pastebin providers (dpaste.com, extensible)
- URL fragment key storage (key never sent to server)
- Both CLI and library usage
- Post-quantum cryptography support (experimental)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-07 22:52:32 +00:00

66 lines
1.9 KiB
Python

"""AES-256-GCM cipher backend."""
import os
import base64
from typing import Tuple
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from .base import CipherBackend
class AESGCMCipher(CipherBackend):
"""AES-256-GCM authenticated encryption."""
def encrypt(self, plaintext: bytes) -> Tuple[bytes, str]:
"""
Encrypt using AES-256-GCM.
Returns:
Tuple of (nonce + ciphertext, base64url-encoded key)
"""
# Generate random 256-bit key
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
# Generate random 96-bit nonce (recommended for GCM)
nonce = os.urandom(12)
# Encrypt and authenticate
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# Prepend nonce to ciphertext for storage
encrypted_data = nonce + ciphertext
# Encode key for URL fragment
key_encoded = base64.urlsafe_b64encode(key).decode('ascii').rstrip('=')
return encrypted_data, key_encoded
def decrypt(self, ciphertext: bytes, key: str) -> bytes:
"""
Decrypt using AES-256-GCM.
Args:
ciphertext: Nonce (12 bytes) + encrypted data
key: Base64url-encoded 256-bit key
"""
# Decode key (add padding if needed)
padding = '=' * (4 - len(key) % 4) if len(key) % 4 else ''
key_bytes = base64.urlsafe_b64decode(key + padding)
if len(key_bytes) != 32:
raise ValueError("Invalid key length. Expected 32 bytes for AES-256.")
# Extract nonce and ciphertext
nonce = ciphertext[:12]
encrypted = ciphertext[12:]
# Decrypt
aesgcm = AESGCM(key_bytes)
plaintext = aesgcm.decrypt(nonce, encrypted, None)
return plaintext
def get_name(self) -> str:
"""Return cipher name."""
return "aes-256-gcm"