shadow_crypto/
encryption.rs1use chacha20poly1305::{
2 aead::{Aead, KeyInit, OsRng},
3 ChaCha20Poly1305, Nonce,
4};
5use serde::{Deserialize, Serialize};
6use zeroize::Zeroize;
7use anyhow::{Result, Context};
8
9#[derive(Clone, Zeroize, Serialize, Deserialize)]
11#[zeroize(drop)]
12pub struct EncryptionKey([u8; 32]);
13
14impl EncryptionKey {
15 pub fn generate() -> Self {
17 let mut key = [0u8; 32];
18 use rand::RngCore;
19 rand::rngs::OsRng.fill_bytes(&mut key);
20 Self(key)
21 }
22
23 pub fn from_bytes(bytes: [u8; 32]) -> Self {
25 Self(bytes)
26 }
27
28 pub fn as_bytes(&self) -> &[u8; 32] {
30 &self.0
31 }
32
33 pub fn from_password(password: &str, salt: &[u8]) -> Result<Self> {
35 use hkdf::Hkdf;
36 use sha2::Sha256;
37
38 let hk = Hkdf::<Sha256>::new(Some(salt), password.as_bytes());
39 let mut okm = [0u8; 32];
40 hk.expand(b"shadow-network-encryption", &mut okm)
41 .map_err(|e| anyhow::anyhow!("HKDF expansion failed: {:?}", e))?;
42
43 Ok(Self(okm))
44 }
45}
46
47impl std::fmt::Debug for EncryptionKey {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "EncryptionKey([REDACTED])")
50 }
51}
52
53pub fn encrypt(key: &EncryptionKey, plaintext: &[u8]) -> Result<Vec<u8>> {
55 let cipher = ChaCha20Poly1305::new(key.as_bytes().into());
56
57 let mut nonce_bytes = [0u8; 12];
59 use rand::RngCore;
60 rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
61 let nonce = Nonce::from_slice(&nonce_bytes);
62
63 let ciphertext = cipher
65 .encrypt(nonce, plaintext)
66 .map_err(|e| anyhow::anyhow!("Encryption failed: {:?}", e))?;
67
68 let mut result = Vec::with_capacity(12 + ciphertext.len());
70 result.extend_from_slice(&nonce_bytes);
71 result.extend_from_slice(&ciphertext);
72
73 Ok(result)
74}
75
76pub fn decrypt(key: &EncryptionKey, ciphertext: &[u8]) -> Result<Vec<u8>> {
78 if ciphertext.len() < 12 {
79 anyhow::bail!("Ciphertext too short (missing nonce)");
80 }
81
82 let cipher = ChaCha20Poly1305::new(key.as_bytes().into());
83
84 let (nonce_bytes, encrypted_data) = ciphertext.split_at(12);
86 let nonce = Nonce::from_slice(nonce_bytes);
87
88 let plaintext = cipher
90 .decrypt(nonce, encrypted_data)
91 .map_err(|e| anyhow::anyhow!("Decryption failed: {:?}", e))?;
92
93 Ok(plaintext)
94}
95
96pub fn encrypt_with_ad(
98 key: &EncryptionKey,
99 plaintext: &[u8],
100 associated_data: &[u8],
101) -> Result<Vec<u8>> {
102 let cipher = ChaCha20Poly1305::new(key.as_bytes().into());
103
104 let mut nonce_bytes = [0u8; 12];
105 use rand::RngCore;
106 rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
107 let nonce = Nonce::from_slice(&nonce_bytes);
108
109 let ciphertext = cipher
111 .encrypt(nonce, chacha20poly1305::aead::Payload {
112 msg: plaintext,
113 aad: associated_data,
114 })
115 .map_err(|e| anyhow::anyhow!("Encryption with AD failed: {:?}", e))?;
116
117 let mut result = Vec::with_capacity(12 + ciphertext.len());
118 result.extend_from_slice(&nonce_bytes);
119 result.extend_from_slice(&ciphertext);
120
121 Ok(result)
122}
123
124pub fn decrypt_with_ad(
126 key: &EncryptionKey,
127 ciphertext: &[u8],
128 associated_data: &[u8],
129) -> Result<Vec<u8>> {
130 if ciphertext.len() < 12 {
131 anyhow::bail!("Ciphertext too short (missing nonce)");
132 }
133
134 let cipher = ChaCha20Poly1305::new(key.as_bytes().into());
135
136 let (nonce_bytes, encrypted_data) = ciphertext.split_at(12);
137 let nonce = Nonce::from_slice(nonce_bytes);
138
139 let plaintext = cipher
140 .decrypt(nonce, chacha20poly1305::aead::Payload {
141 msg: encrypted_data,
142 aad: associated_data,
143 })
144 .map_err(|e| anyhow::anyhow!("Decryption with AD failed: {:?}", e))?;
145
146 Ok(plaintext)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_encrypt_decrypt() {
155 let key = EncryptionKey::generate();
156 let plaintext = b"Hello, Shadow Network!";
157
158 let ciphertext = encrypt(&key, plaintext).unwrap();
159 assert_ne!(ciphertext.as_slice(), plaintext);
160
161 let decrypted = decrypt(&key, &ciphertext).unwrap();
162 assert_eq!(decrypted.as_slice(), plaintext);
163 }
164
165 #[test]
166 fn test_wrong_key_fails() {
167 let key1 = EncryptionKey::generate();
168 let key2 = EncryptionKey::generate();
169 let plaintext = b"Secret data";
170
171 let ciphertext = encrypt(&key1, plaintext).unwrap();
172 assert!(decrypt(&key2, &ciphertext).is_err());
173 }
174
175 #[test]
176 fn test_password_derivation() {
177 let salt = b"some-random-salt";
178 let key1 = EncryptionKey::from_password("my-password", salt).unwrap();
179 let key2 = EncryptionKey::from_password("my-password", salt).unwrap();
180
181 assert_eq!(key1.as_bytes(), key2.as_bytes());
182 }
183}