100 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			100 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
 | 
						||
### 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<XChaCha20Poly1305>,
 | 
						||
}
 | 
						||
 | 
						||
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<S: AsRef<[u8]>>(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::<XChaCha20Poly1305>::from_slice(&digest).to_owned();
 | 
						||
        Self { key }
 | 
						||
    }
 | 
						||
 | 
						||
    /// Output layout: [version:1][nonce:24][ciphertext||tag]
 | 
						||
    pub fn encrypt(&self, plaintext: &[u8]) -> Vec<u8> {
 | 
						||
        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<Vec<u8>, 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.
 |