1use aes_gcm::{
20 aead::{Aead, KeyInit},
21 Aes256Gcm, Nonce,
22};
23use hkdf::Hkdf;
24use rand::RngCore;
25use sha2::{Digest, Sha256};
26
27pub type EncryptionKey = [u8; 32];
29
30const NONCE_SIZE: usize = 12;
32
33const TAG_SIZE: usize = 16;
35
36const CHK_SALT: &[u8] = b"hashtree-chk";
38
39#[derive(Debug, thiserror::Error)]
41pub enum CryptoError {
42 #[error("Encryption failed: {0}")]
43 EncryptionFailed(String),
44 #[error("Decryption failed: {0}")]
45 DecryptionFailed(String),
46 #[error("Encrypted data too short")]
47 DataTooShort,
48 #[error("Invalid key length")]
49 InvalidKeyLength,
50 #[error("Unsupported encryption suite: {0}")]
51 UnsupportedSuite(u8),
52 #[error("Key derivation failed")]
53 KeyDerivationFailed,
54}
55
56fn derive_key(content_hash: &[u8; 32]) -> Result<[u8; 32], CryptoError> {
58 let hk = Hkdf::<Sha256>::new(Some(CHK_SALT), content_hash);
59
60 let mut key = [0u8; 32];
61 hk.expand(b"encryption-key", &mut key)
62 .map_err(|_| CryptoError::KeyDerivationFailed)?;
63
64 Ok(key)
65}
66
67pub fn generate_key() -> EncryptionKey {
69 let mut key = [0u8; 32];
70 rand::thread_rng().fill_bytes(&mut key);
71 key
72}
73
74pub fn content_hash(data: &[u8]) -> EncryptionKey {
76 let hash = Sha256::digest(data);
77 let mut result = [0u8; 32];
78 result.copy_from_slice(&hash);
79 result
80}
81
82pub fn encrypt_chk(plaintext: &[u8]) -> Result<(Vec<u8>, EncryptionKey), CryptoError> {
93 let chash = content_hash(plaintext);
94 let key = derive_key(&chash)?;
95 let zero_nonce = [0u8; NONCE_SIZE];
96
97 let cipher = Aes256Gcm::new_from_slice(&key)
98 .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
99
100 let ciphertext = cipher
101 .encrypt(&Nonce::from(zero_nonce), plaintext)
102 .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
103
104 Ok((ciphertext, chash))
105}
106
107pub fn decrypt_chk(ciphertext: &[u8], key: &EncryptionKey) -> Result<Vec<u8>, CryptoError> {
111 if ciphertext.len() < TAG_SIZE {
112 return Err(CryptoError::DataTooShort);
113 }
114
115 let enc_key = derive_key(key)?;
116 let zero_nonce = [0u8; NONCE_SIZE];
117
118 let cipher = Aes256Gcm::new_from_slice(&enc_key)
119 .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))?;
120
121 let plaintext = cipher
122 .decrypt(&Nonce::from(zero_nonce), ciphertext)
123 .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))?;
124 if content_hash(&plaintext) != *key {
125 return Err(CryptoError::DecryptionFailed(
126 "CHK plaintext hash mismatch".into(),
127 ));
128 }
129 Ok(plaintext)
130}
131
132pub fn encrypt(plaintext: &[u8], key: &EncryptionKey) -> Result<Vec<u8>, CryptoError> {
136 let cipher =
137 Aes256Gcm::new_from_slice(key).map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
138
139 let mut nonce_bytes = [0u8; NONCE_SIZE];
140 rand::thread_rng().fill_bytes(&mut nonce_bytes);
141 let nonce = Nonce::from(nonce_bytes);
142
143 let ciphertext = cipher
144 .encrypt(&nonce, plaintext)
145 .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
146
147 let mut result = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
148 result.extend_from_slice(&nonce_bytes);
149 result.extend_from_slice(&ciphertext);
150
151 Ok(result)
152}
153
154pub fn decrypt(encrypted: &[u8], key: &EncryptionKey) -> Result<Vec<u8>, CryptoError> {
158 if encrypted.len() < NONCE_SIZE + TAG_SIZE {
159 return Err(CryptoError::DataTooShort);
160 }
161
162 let cipher =
163 Aes256Gcm::new_from_slice(key).map_err(|e| CryptoError::DecryptionFailed(e.to_string()))?;
164
165 let nonce = Nonce::try_from(&encrypted[..NONCE_SIZE]).map_err(|_| CryptoError::DataTooShort)?;
166 let ciphertext = &encrypted[NONCE_SIZE..];
167
168 cipher
169 .decrypt(&nonce, ciphertext)
170 .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
171}
172
173pub fn could_be_encrypted(data: &[u8]) -> bool {
175 data.len() >= NONCE_SIZE + TAG_SIZE
176}
177
178pub fn encrypted_size(plaintext_size: usize) -> usize {
180 NONCE_SIZE + plaintext_size + TAG_SIZE
181}
182
183pub fn encrypted_size_chk(plaintext_size: usize) -> usize {
185 plaintext_size + TAG_SIZE
186}
187
188pub fn plaintext_size(encrypted_size: usize) -> usize {
190 encrypted_size.saturating_sub(NONCE_SIZE + TAG_SIZE)
191}
192
193pub fn key_to_hex(key: &EncryptionKey) -> String {
195 hex::encode(key)
196}
197
198pub fn key_from_hex(hex_str: &str) -> Result<EncryptionKey, CryptoError> {
200 let bytes = hex::decode(hex_str).map_err(|_| CryptoError::InvalidKeyLength)?;
201 if bytes.len() != 32 {
202 return Err(CryptoError::InvalidKeyLength);
203 }
204 let mut key = [0u8; 32];
205 key.copy_from_slice(&bytes);
206 Ok(key)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_chk_encrypt_decrypt() {
215 let plaintext = b"Hello, World!";
216
217 let (ciphertext, key) = encrypt_chk(plaintext).unwrap();
218 let decrypted = decrypt_chk(&ciphertext, &key).unwrap();
219
220 assert_eq!(decrypted, plaintext);
221 }
222
223 #[test]
224 fn test_chk_deterministic() {
225 let plaintext = b"Same content produces same ciphertext";
226
227 let (ciphertext1, key1) = encrypt_chk(plaintext).unwrap();
228 let (ciphertext2, key2) = encrypt_chk(plaintext).unwrap();
229
230 assert_eq!(key1, key2);
232 assert_eq!(ciphertext1, ciphertext2);
233 }
234
235 #[test]
236 fn test_chk_different_content() {
237 let (ciphertext1, key1) = encrypt_chk(b"Content A").unwrap();
238 let (ciphertext2, key2) = encrypt_chk(b"Content B").unwrap();
239
240 assert_ne!(key1, key2);
242 assert_ne!(ciphertext1, ciphertext2);
243 }
244
245 #[test]
246 fn test_chk_wrong_key_fails() {
247 let (ciphertext, _key) = encrypt_chk(b"Secret data").unwrap();
248 let wrong_key = generate_key();
249
250 let result = decrypt_chk(&ciphertext, &wrong_key);
251 assert!(result.is_err());
252 }
253
254 #[test]
255 fn test_non_chk_encrypt_decrypt() {
256 let key = generate_key();
257 let plaintext = b"Hello, World!";
258
259 let encrypted = encrypt(plaintext, &key).unwrap();
260 let decrypted = decrypt(&encrypted, &key).unwrap();
261
262 assert_eq!(decrypted, plaintext);
263 }
264
265 #[test]
266 fn test_non_chk_random_nonce() {
267 let key = generate_key();
268 let plaintext = b"Same content";
269
270 let encrypted1 = encrypt(plaintext, &key).unwrap();
271 let encrypted2 = encrypt(plaintext, &key).unwrap();
272
273 assert_ne!(encrypted1, encrypted2);
275
276 assert_eq!(decrypt(&encrypted1, &key).unwrap(), plaintext);
278 assert_eq!(decrypt(&encrypted2, &key).unwrap(), plaintext);
279 }
280
281 #[test]
282 fn test_empty_data() {
283 let (ciphertext, key) = encrypt_chk(b"").unwrap();
284 let decrypted = decrypt_chk(&ciphertext, &key).unwrap();
285 assert_eq!(decrypted, b"");
286 }
287
288 #[test]
289 fn test_large_data() {
290 let plaintext = vec![0u8; 1024 * 1024]; let (ciphertext, key) = encrypt_chk(&plaintext).unwrap();
293 let decrypted = decrypt_chk(&ciphertext, &key).unwrap();
294
295 assert_eq!(decrypted, plaintext);
296 }
297
298 #[test]
299 fn test_key_hex_roundtrip() {
300 let key = generate_key();
301 let hex_str = key_to_hex(&key);
302 let key2 = key_from_hex(&hex_str).unwrap();
303 assert_eq!(key, key2);
304 }
305
306 #[test]
307 fn test_encrypted_size_chk() {
308 let plaintext = b"Test data";
309 let (ciphertext, _) = encrypt_chk(plaintext).unwrap();
310 assert_eq!(ciphertext.len(), encrypted_size_chk(plaintext.len()));
311 }
312
313 #[test]
314 fn test_tampered_data_fails() {
315 let (mut ciphertext, key) = encrypt_chk(b"Important data").unwrap();
316
317 if let Some(byte) = ciphertext.last_mut() {
319 *byte ^= 0xFF;
320 }
321
322 let result = decrypt_chk(&ciphertext, &key);
323 assert!(result.is_err());
324 }
325}