73 lines
2.2 KiB
Rust
73 lines
2.2 KiB
Rust
use chacha20poly1305::{
|
|
aead::{Aead, KeyInit, OsRng},
|
|
XChaCha20Poly1305, 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
|
|
}
|
|
|
|
impl From<CryptoError> for crate::error::DBError {
|
|
fn from(e: CryptoError) -> Self {
|
|
crate::error::DBError(format!("Crypto error: {:?}", e))
|
|
}
|
|
}
|
|
|
|
/// Super-simple factory: new(secret) + encrypt(bytes) + decrypt(bytes)
|
|
pub struct CryptoFactory {
|
|
key: chacha20poly1305::Key,
|
|
}
|
|
|
|
impl CryptoFactory {
|
|
/// Accepts any secret bytes; turns them into a 32-byte key (SHA-256).
|
|
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 = chacha20poly1305::Key::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)
|
|
}
|
|
} |