1use chacha20poly1305::{
3 aead::{Aead, KeyInit, OsRng},
4 ChaCha20Poly1305, Key, Nonce,
5};
6use rand::RngCore;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum CryptoError {
11 #[error("Encryption failed")]
12 EncryptionFailed,
13 #[error("Decryption failed")]
14 DecryptionFailed,
15 #[error("Invalid key length")]
16 InvalidKeyLength,
17}
18
19fn derive_key_from_hash(hash: &str) -> Result<Key, CryptoError> {
21 let mut key_bytes = [0u8; 32];
22 let hash_bytes = hash.as_bytes();
23
24 if hash_bytes.len() < 32 {
25 key_bytes[..hash_bytes.len()].copy_from_slice(hash_bytes);
27 } else {
28 key_bytes.copy_from_slice(&hash_bytes[..32]);
29 }
30
31 Ok(Key::from_slice(&key_bytes).clone())
32}
33
34pub fn encrypt_string(hash: &str, plaintext: &str) -> Result<Vec<u8>, CryptoError> {
35 let key = derive_key_from_hash(hash)?;
36 let cipher = ChaCha20Poly1305::new(&key);
37 let mut nonce_bytes = [0u8; 12];
38 OsRng.fill_bytes(&mut nonce_bytes);
39 let nonce = Nonce::from_slice(&nonce_bytes);
40
41 let ciphertext = cipher
42 .encrypt(nonce, plaintext.as_bytes())
43 .map_err(|_| CryptoError::EncryptionFailed)?;
44
45 let mut result = nonce_bytes.to_vec();
47 result.extend(ciphertext);
48 Ok(result)
49}
50
51pub fn decrypt_string(hash: &str, ciphertext: &[u8]) -> Result<String, CryptoError> {
52 let key = derive_key_from_hash(hash)?;
53 let cipher = ChaCha20Poly1305::new(&key);
54
55 if ciphertext.len() < 12 {
56 return Err(CryptoError::DecryptionFailed);
57 }
58
59 let (nonce_bytes, cipher_bytes) = ciphertext.split_at(12);
60 let nonce = Nonce::from_slice(nonce_bytes);
61
62 let decrypted = cipher
63 .decrypt(nonce, cipher_bytes)
64 .map_err(|_| CryptoError::DecryptionFailed)?;
65
66 String::from_utf8(decrypted).map_err(|_| CryptoError::DecryptionFailed)
67}