Files
secpaste/crypto/chacha.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

"""ChaCha20-Poly1305 cipher backend."""
import os
import base64
from typing import Tuple
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from .base import CipherBackend
class ChaCha20Cipher(CipherBackend):
"""ChaCha20-Poly1305 authenticated encryption."""
def encrypt(self, plaintext: bytes) -> Tuple[bytes, str]:
"""
Encrypt using ChaCha20-Poly1305.
Returns:
Tuple of (nonce + ciphertext, base64url-encoded key)
"""
# Generate random 256-bit key
key = ChaCha20Poly1305.generate_key()
chacha = ChaCha20Poly1305(key)
# Generate random 96-bit nonce
nonce = os.urandom(12)
# Encrypt and authenticate
ciphertext = chacha.encrypt(nonce, plaintext, None)
# Prepend nonce to ciphertext
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 ChaCha20-Poly1305.
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 ChaCha20.")
# Extract nonce and ciphertext
nonce = ciphertext[:12]
encrypted = ciphertext[12:]
# Decrypt
chacha = ChaCha20Poly1305(key_bytes)
plaintext = chacha.decrypt(nonce, encrypted, None)
return plaintext
def get_name(self) -> str:
"""Return cipher name."""
return "chacha20-poly1305"