envelope_cli/crypto/
encryption.rs1use aes_gcm::aead::rand_core::RngCore;
7use aes_gcm::{
8 aead::{Aead, KeyInit, OsRng},
9 Aes256Gcm, Nonce,
10};
11use serde::{Deserialize, Serialize};
12
13use crate::error::{EnvelopeError, EnvelopeResult};
14
15use super::DerivedKey;
16
17const NONCE_SIZE: usize = 12;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EncryptedData {
23 pub nonce: String,
25 pub ciphertext: String,
27 #[serde(default = "default_version")]
29 pub version: u8,
30}
31
32fn default_version() -> u8 {
33 1
34}
35
36impl EncryptedData {
37 fn new(nonce: &[u8], ciphertext: &[u8]) -> Self {
39 use base64::{engine::general_purpose::STANDARD, Engine};
40 Self {
41 nonce: STANDARD.encode(nonce),
42 ciphertext: STANDARD.encode(ciphertext),
43 version: 1,
44 }
45 }
46
47 fn decode_nonce(&self) -> EnvelopeResult<Vec<u8>> {
49 use base64::{engine::general_purpose::STANDARD, Engine};
50 STANDARD
51 .decode(&self.nonce)
52 .map_err(|e| EnvelopeError::Encryption(format!("Invalid nonce encoding: {}", e)))
53 }
54
55 fn decode_ciphertext(&self) -> EnvelopeResult<Vec<u8>> {
57 use base64::{engine::general_purpose::STANDARD, Engine};
58 STANDARD
59 .decode(&self.ciphertext)
60 .map_err(|e| EnvelopeError::Encryption(format!("Invalid ciphertext encoding: {}", e)))
61 }
62}
63
64pub fn encrypt(plaintext: &[u8], key: &DerivedKey) -> EnvelopeResult<EncryptedData> {
68 let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
70 .map_err(|e| EnvelopeError::Encryption(format!("Failed to create cipher: {}", e)))?;
71
72 let mut nonce_bytes = [0u8; NONCE_SIZE];
74 OsRng.fill_bytes(&mut nonce_bytes);
75 let nonce = Nonce::from_slice(&nonce_bytes);
76
77 let ciphertext = cipher
79 .encrypt(nonce, plaintext)
80 .map_err(|e| EnvelopeError::Encryption(format!("Encryption failed: {}", e)))?;
81
82 Ok(EncryptedData::new(&nonce_bytes, &ciphertext))
83}
84
85pub fn decrypt(encrypted: &EncryptedData, key: &DerivedKey) -> EnvelopeResult<Vec<u8>> {
87 if encrypted.version != 1 {
89 return Err(EnvelopeError::Encryption(format!(
90 "Unsupported encryption version: {}",
91 encrypted.version
92 )));
93 }
94
95 let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
97 .map_err(|e| EnvelopeError::Encryption(format!("Failed to create cipher: {}", e)))?;
98
99 let nonce_bytes = encrypted.decode_nonce()?;
101 if nonce_bytes.len() != NONCE_SIZE {
102 return Err(EnvelopeError::Encryption(format!(
103 "Invalid nonce size: expected {}, got {}",
104 NONCE_SIZE,
105 nonce_bytes.len()
106 )));
107 }
108 let nonce = Nonce::from_slice(&nonce_bytes);
109
110 let ciphertext = encrypted.decode_ciphertext()?;
111
112 let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err(|_| {
114 EnvelopeError::Encryption("Decryption failed: invalid key or corrupted data".to_string())
115 })?;
116
117 Ok(plaintext)
118}
119
120pub fn encrypt_string(plaintext: &str, key: &DerivedKey) -> EnvelopeResult<EncryptedData> {
122 encrypt(plaintext.as_bytes(), key)
123}
124
125pub fn decrypt_string(encrypted: &EncryptedData, key: &DerivedKey) -> EnvelopeResult<String> {
127 let plaintext = decrypt(encrypted, key)?;
128 String::from_utf8(plaintext)
129 .map_err(|e| EnvelopeError::Encryption(format!("Invalid UTF-8 in decrypted data: {}", e)))
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::crypto::key_derivation::{derive_key, KeyDerivationParams};
136
137 fn test_key() -> DerivedKey {
138 let params = KeyDerivationParams::new();
139 derive_key("test_passphrase", ¶ms).unwrap()
140 }
141
142 #[test]
143 fn test_encrypt_decrypt() {
144 let key = test_key();
145 let plaintext = b"Hello, World!";
146
147 let encrypted = encrypt(plaintext, &key).unwrap();
148 let decrypted = decrypt(&encrypted, &key).unwrap();
149
150 assert_eq!(plaintext, decrypted.as_slice());
151 }
152
153 #[test]
154 fn test_encrypt_decrypt_string() {
155 let key = test_key();
156 let plaintext = "Hello, World!";
157
158 let encrypted = encrypt_string(plaintext, &key).unwrap();
159 let decrypted = decrypt_string(&encrypted, &key).unwrap();
160
161 assert_eq!(plaintext, decrypted);
162 }
163
164 #[test]
165 fn test_different_nonces() {
166 let key = test_key();
167 let plaintext = b"Hello, World!";
168
169 let encrypted1 = encrypt(plaintext, &key).unwrap();
170 let encrypted2 = encrypt(plaintext, &key).unwrap();
171
172 assert_ne!(encrypted1.nonce, encrypted2.nonce);
174 assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext);
175 }
176
177 #[test]
178 fn test_wrong_key_fails() {
179 let key1 = test_key();
180 let params2 = KeyDerivationParams::new();
181 let key2 = derive_key("different_passphrase", ¶ms2).unwrap();
182
183 let plaintext = b"Hello, World!";
184 let encrypted = encrypt(plaintext, &key1).unwrap();
185
186 let result = decrypt(&encrypted, &key2);
188 assert!(result.is_err());
189 }
190
191 #[test]
192 fn test_tampered_ciphertext_fails() {
193 let key = test_key();
194 let plaintext = b"Hello, World!";
195
196 let mut encrypted = encrypt(plaintext, &key).unwrap();
197
198 use base64::{engine::general_purpose::STANDARD, Engine};
200 let mut ciphertext = STANDARD.decode(&encrypted.ciphertext).unwrap();
201 if !ciphertext.is_empty() {
202 ciphertext[0] ^= 0xFF;
203 }
204 encrypted.ciphertext = STANDARD.encode(&ciphertext);
205
206 let result = decrypt(&encrypted, &key);
208 assert!(result.is_err());
209 }
210
211 #[test]
212 fn test_empty_plaintext() {
213 let key = test_key();
214 let plaintext = b"";
215
216 let encrypted = encrypt(plaintext, &key).unwrap();
217 let decrypted = decrypt(&encrypted, &key).unwrap();
218
219 assert_eq!(plaintext, decrypted.as_slice());
220 }
221
222 #[test]
223 fn test_large_plaintext() {
224 let key = test_key();
225 let plaintext: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
226
227 let encrypted = encrypt(&plaintext, &key).unwrap();
228 let decrypted = decrypt(&encrypted, &key).unwrap();
229
230 assert_eq!(plaintext, decrypted);
231 }
232}