### Cargo.toml ```toml [dependencies] chacha20poly1305 = { version = "0.10", features = ["xchacha20"] } rand = "0.8" sha2 = "0.10" ``` ### `crypto_factory.rs` ```rust use chacha20poly1305::{ aead::{Aead, KeyInit, OsRng}, XChaCha20Poly1305, Key, XNonce, }; use rand::RngCore; use sha2::{Digest, Sha256}; const VERSION: u8 = 1; const NONCE_LEN: usize = 24; const TAG_LEN: usize = 16; #[derive(Debug)] pub enum CryptoError { Format, // wrong length / header Version(u8), // unknown version Decrypt, // wrong key or corrupted data } /// Super-simple factory: new(secret) + encrypt(bytes) + decrypt(bytes) pub struct CryptoFactory { key: Key, } impl CryptoFactory { /// Accepts any secret bytes; turns them into a 32-byte key (SHA-256). /// (If your secret is already 32 bytes, this is still fine.) pub fn new>(secret: S) -> Self { let mut h = Sha256::new(); h.update(b"xchacha20poly1305-factory:v1"); // domain separation h.update(secret.as_ref()); let digest = h.finalize(); // 32 bytes let key = Key::::from_slice(&digest).to_owned(); Self { key } } /// Output layout: [version:1][nonce:24][ciphertext||tag] pub fn encrypt(&self, plaintext: &[u8]) -> Vec { let cipher = XChaCha20Poly1305::new(&self.key); let mut nonce_bytes = [0u8; NONCE_LEN]; OsRng.fill_bytes(&mut nonce_bytes); let nonce = XNonce::from_slice(&nonce_bytes); let mut out = Vec::with_capacity(1 + NONCE_LEN + plaintext.len() + TAG_LEN); out.push(VERSION); out.extend_from_slice(&nonce_bytes); let ct = cipher.encrypt(nonce, plaintext).expect("encrypt"); out.extend_from_slice(&ct); out } pub fn decrypt(&self, blob: &[u8]) -> Result, CryptoError> { if blob.len() < 1 + NONCE_LEN + TAG_LEN { return Err(CryptoError::Format); } let ver = blob[0]; if ver != VERSION { return Err(CryptoError::Version(ver)); } let nonce = XNonce::from_slice(&blob[1..1 + NONCE_LEN]); let ct = &blob[1 + NONCE_LEN..]; let cipher = XChaCha20Poly1305::new(&self.key); cipher.decrypt(nonce, ct).map_err(|_| CryptoError::Decrypt) } } ``` ### Tiny usage example ```rust fn main() { let f = CryptoFactory::new(b"super-secret-key-material"); let val = b"\x00\xFFbinary\x01\x02\x03"; let blob = f.encrypt(val); let roundtrip = f.decrypt(&blob).unwrap(); assert_eq!(roundtrip, val); } ``` That’s it: `new(secret)`, `encrypt(bytes)`, `decrypt(bytes)`. You can stash the returned `blob` directly in your storage layer behind Redis.