1mod aes_gcm;
6mod key;
7mod xchacha20;
8
9pub use aes_gcm::{decrypt_aes_gcm, encrypt_aes_gcm};
10pub use key::{
11 derive_key_from_shared_secret, derive_key_hkdf, derive_key_hkdf_raw, derive_key_pbkdf2,
12 generate_key, generate_x25519_key_pair, validate_pbkdf2_parameters, x25519_shared_secret, Key,
13 X25519KeyPair, HKDF_SHA256_MAX_OUTPUT, PBKDF2_MAX_ITERATIONS, PBKDF2_MAX_SALT_SIZE,
14 PBKDF2_MIN_ITERATIONS, PBKDF2_MIN_SALT_SIZE, X25519_KEY_SIZE,
15};
16pub use xchacha20::{decrypt_xchacha20, encrypt_xchacha20};
17
18use crate::{Error, Result, FORMAT_VERSION, MAGIC_ENCRYPTED};
19use alloc::vec::Vec;
20use serde::{Deserialize, Serialize};
21use zeroize::Zeroize;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[repr(u8)]
26pub enum Algorithm {
27 Aes256Gcm = 0x01,
29 XChaCha20Poly1305 = 0x02,
31}
32
33impl Algorithm {
34 pub fn from_byte(byte: u8) -> Result<Self> {
36 match byte {
37 0x01 => Ok(Algorithm::Aes256Gcm),
38 0x02 => Ok(Algorithm::XChaCha20Poly1305),
39 _ => Err(Error::UnsupportedAlgorithm(byte)),
40 }
41 }
42
43 pub fn nonce_len(&self) -> usize {
45 match self {
46 Algorithm::Aes256Gcm => 12,
47 Algorithm::XChaCha20Poly1305 => 24,
48 }
49 }
50
51 pub fn name(&self) -> &'static str {
53 match self {
54 Algorithm::Aes256Gcm => "aes-256-gcm",
55 Algorithm::XChaCha20Poly1305 => "xchacha20-poly1305",
56 }
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct EncryptionResult {
63 pub ciphertext: Vec<u8>,
65 pub algorithm: Algorithm,
67 pub nonce: Vec<u8>,
69 pub tag: Vec<u8>,
71}
72
73impl EncryptionResult {
74 pub fn to_bytes(&self) -> Vec<u8> {
76 let nonce_len = self.nonce.len();
77 let total_len = 8 + nonce_len + self.ciphertext.len() + self.tag.len();
78 let mut buf = Vec::with_capacity(total_len);
79
80 buf.extend_from_slice(MAGIC_ENCRYPTED);
82 buf.push(FORMAT_VERSION);
84 buf.push(self.algorithm as u8);
86 buf.push(nonce_len as u8);
88 buf.push(0x00);
90 buf.extend_from_slice(&self.nonce);
92 buf.extend_from_slice(&self.ciphertext);
94 buf.extend_from_slice(&self.tag);
96
97 buf
98 }
99
100 pub fn from_bytes(data: &[u8]) -> Result<Self> {
102 if data.len() < 8 {
103 return Err(Error::TruncatedPayload {
104 expected: 8,
105 actual: data.len(),
106 });
107 }
108
109 if &data[0..4] != MAGIC_ENCRYPTED {
111 return Err(Error::InvalidFormat);
112 }
113
114 let version = data[4];
116 if version != FORMAT_VERSION {
117 return Err(Error::UnsupportedVersion(version));
118 }
119
120 let algorithm = Algorithm::from_byte(data[5])?;
122
123 let nonce_len = data[6] as usize;
125 if nonce_len != algorithm.nonce_len() {
126 return Err(Error::InvalidNonceLength {
127 expected: algorithm.nonce_len(),
128 actual: nonce_len,
129 });
130 }
131
132 if data[7] != 0 {
135 return Err(Error::InvalidFormat);
136 }
137
138 let min_size = 8 + nonce_len + 16; if data.len() < min_size {
141 return Err(Error::TruncatedPayload {
142 expected: min_size,
143 actual: data.len(),
144 });
145 }
146
147 let nonce = data[8..8 + nonce_len].to_vec();
149
150 let tag = data[data.len() - 16..].to_vec();
152
153 let ciphertext = data[8 + nonce_len..data.len() - 16].to_vec();
155
156 Ok(EncryptionResult {
157 ciphertext,
158 algorithm,
159 nonce,
160 tag,
161 })
162 }
163
164 pub fn to_json(&self) -> Result<String> {
166 #[derive(Serialize)]
167 struct JsonFormat<'a> {
168 v: &'static str,
169 alg: &'a str,
170 nonce: String,
171 ct: String,
172 tag: String,
173 }
174
175 use base64::{engine::general_purpose::STANDARD, Engine};
176
177 let json = JsonFormat {
178 v: "1.0",
179 alg: self.algorithm.name(),
180 nonce: STANDARD.encode(&self.nonce),
181 ct: STANDARD.encode(&self.ciphertext),
182 tag: STANDARD.encode(&self.tag),
183 };
184
185 serde_json::to_string(&json).map_err(|e| Error::SerializationError(e.to_string()))
186 }
187
188 pub fn from_json(json: &str) -> Result<Self> {
190 #[derive(Deserialize)]
191 struct JsonFormat {
192 v: String,
193 alg: String,
194 nonce: String,
195 ct: String,
196 tag: String,
197 }
198
199 let parsed: JsonFormat = serde_json::from_str(json)?;
200
201 if parsed.v != "1.0" {
202 return Err(Error::UnsupportedVersion(0));
203 }
204
205 let algorithm = match parsed.alg.as_str() {
206 "aes-256-gcm" => Algorithm::Aes256Gcm,
207 "xchacha20-poly1305" => Algorithm::XChaCha20Poly1305,
208 _ => return Err(Error::UnsupportedAlgorithm(0)),
209 };
210
211 use base64::{engine::general_purpose::STANDARD, Engine};
212
213 Ok(EncryptionResult {
214 ciphertext: STANDARD.decode(&parsed.ct)?,
215 algorithm,
216 nonce: STANDARD.decode(&parsed.nonce)?,
217 tag: STANDARD.decode(&parsed.tag)?,
218 })
219 }
220}
221
222impl Drop for EncryptionResult {
223 fn drop(&mut self) {
224 self.ciphertext.zeroize();
225 self.nonce.zeroize();
226 self.tag.zeroize();
227 }
228}
229
230#[derive(Debug, Clone, Default)]
232pub struct EncryptOptions {
233 pub algorithm: Option<Algorithm>,
235 pub aad: Option<Vec<u8>>,
237}
238
239pub fn encrypt(
254 plaintext: &[u8],
255 key: &Key,
256 options: Option<EncryptOptions>,
257) -> Result<EncryptionResult> {
258 let opts = options.unwrap_or_default();
259 let algorithm = opts.algorithm.unwrap_or(Algorithm::XChaCha20Poly1305);
260 let aad = opts.aad.as_deref().unwrap_or(&[]);
261
262 match algorithm {
263 Algorithm::Aes256Gcm => encrypt_aes_gcm(plaintext, key, aad),
264 Algorithm::XChaCha20Poly1305 => encrypt_xchacha20(plaintext, key, aad),
265 }
266}
267
268pub fn decrypt(encrypted: &EncryptionResult, key: &Key) -> Result<Vec<u8>> {
281 decrypt_with_aad(encrypted, key, &[])
282}
283
284pub fn decrypt_with_aad(encrypted: &EncryptionResult, key: &Key, aad: &[u8]) -> Result<Vec<u8>> {
296 match encrypted.algorithm {
297 Algorithm::Aes256Gcm => decrypt_aes_gcm(encrypted, key, aad),
298 Algorithm::XChaCha20Poly1305 => decrypt_xchacha20(encrypted, key, aad),
299 }
300}
301
302pub fn decrypt_bytes(data: &[u8], key: &Key) -> Result<Vec<u8>> {
304 let encrypted = EncryptionResult::from_bytes(data)?;
305 decrypt(&encrypted, key)
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_encrypt_decrypt_roundtrip() {
314 let key = generate_key();
315 let plaintext = b"Hello, World!";
316
317 let encrypted = encrypt(plaintext, &key, None).unwrap();
318 let decrypted = decrypt(&encrypted, &key).unwrap();
319
320 assert_eq!(plaintext, &decrypted[..]);
321 }
322
323 #[test]
324 fn test_binary_serialization() {
325 let key = generate_key();
326 let plaintext = b"Test data for serialization";
327
328 let encrypted = encrypt(plaintext, &key, None).unwrap();
329 let bytes = encrypted.to_bytes();
330 let restored = EncryptionResult::from_bytes(&bytes).unwrap();
331
332 assert_eq!(encrypted.algorithm, restored.algorithm);
333 assert_eq!(encrypted.nonce, restored.nonce);
334 assert_eq!(encrypted.ciphertext, restored.ciphertext);
335 assert_eq!(encrypted.tag, restored.tag);
336 }
337
338 #[test]
339 fn test_binary_serialization_rejects_noncanonical_reserved_byte() {
340 let key = generate_key();
341 let encrypted = encrypt(b"canonical", &key, None).unwrap();
342 let mut bytes = encrypted.to_bytes();
343 bytes[7] = 1;
344 assert!(matches!(
345 EncryptionResult::from_bytes(&bytes),
346 Err(Error::InvalidFormat)
347 ));
348 }
349
350 #[test]
351 fn test_json_serialization() {
352 let key = generate_key();
353 let plaintext = b"Test data for JSON";
354
355 let encrypted = encrypt(plaintext, &key, None).unwrap();
356 let json = encrypted.to_json().unwrap();
357 let restored = EncryptionResult::from_json(&json).unwrap();
358
359 assert_eq!(encrypted.algorithm, restored.algorithm);
360 assert_eq!(encrypted.nonce, restored.nonce);
361 assert_eq!(encrypted.ciphertext, restored.ciphertext);
362 assert_eq!(encrypted.tag, restored.tag);
363 }
364}