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, x25519_shared_secret, Key, X25519KeyPair,
13 X25519_KEY_SIZE,
14};
15pub use xchacha20::{decrypt_xchacha20, encrypt_xchacha20};
16
17use crate::{Error, Result, FORMAT_VERSION, MAGIC_ENCRYPTED};
18use alloc::vec::Vec;
19use serde::{Deserialize, Serialize};
20use zeroize::Zeroize;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[repr(u8)]
25pub enum Algorithm {
26 Aes256Gcm = 0x01,
28 XChaCha20Poly1305 = 0x02,
30}
31
32impl Algorithm {
33 pub fn from_byte(byte: u8) -> Result<Self> {
35 match byte {
36 0x01 => Ok(Algorithm::Aes256Gcm),
37 0x02 => Ok(Algorithm::XChaCha20Poly1305),
38 _ => Err(Error::UnsupportedAlgorithm(byte)),
39 }
40 }
41
42 pub fn nonce_len(&self) -> usize {
44 match self {
45 Algorithm::Aes256Gcm => 12,
46 Algorithm::XChaCha20Poly1305 => 24,
47 }
48 }
49
50 pub fn name(&self) -> &'static str {
52 match self {
53 Algorithm::Aes256Gcm => "aes-256-gcm",
54 Algorithm::XChaCha20Poly1305 => "xchacha20-poly1305",
55 }
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct EncryptionResult {
62 pub ciphertext: Vec<u8>,
64 pub algorithm: Algorithm,
66 pub nonce: Vec<u8>,
68 pub tag: Vec<u8>,
70}
71
72impl EncryptionResult {
73 pub fn to_bytes(&self) -> Vec<u8> {
75 let nonce_len = self.nonce.len();
76 let total_len = 8 + nonce_len + self.ciphertext.len() + self.tag.len();
77 let mut buf = Vec::with_capacity(total_len);
78
79 buf.extend_from_slice(MAGIC_ENCRYPTED);
81 buf.push(FORMAT_VERSION);
83 buf.push(self.algorithm as u8);
85 buf.push(nonce_len as u8);
87 buf.push(0x00);
89 buf.extend_from_slice(&self.nonce);
91 buf.extend_from_slice(&self.ciphertext);
93 buf.extend_from_slice(&self.tag);
95
96 buf
97 }
98
99 pub fn from_bytes(data: &[u8]) -> Result<Self> {
101 if data.len() < 8 {
102 return Err(Error::TruncatedPayload {
103 expected: 8,
104 actual: data.len(),
105 });
106 }
107
108 if &data[0..4] != MAGIC_ENCRYPTED {
110 return Err(Error::InvalidFormat);
111 }
112
113 let version = data[4];
115 if version != FORMAT_VERSION {
116 return Err(Error::UnsupportedVersion(version));
117 }
118
119 let algorithm = Algorithm::from_byte(data[5])?;
121
122 let nonce_len = data[6] as usize;
124 if nonce_len != algorithm.nonce_len() {
125 return Err(Error::InvalidNonceLength {
126 expected: algorithm.nonce_len(),
127 actual: nonce_len,
128 });
129 }
130
131 let min_size = 8 + nonce_len + 16; if data.len() < min_size {
134 return Err(Error::TruncatedPayload {
135 expected: min_size,
136 actual: data.len(),
137 });
138 }
139
140 let nonce = data[8..8 + nonce_len].to_vec();
142
143 let tag = data[data.len() - 16..].to_vec();
145
146 let ciphertext = data[8 + nonce_len..data.len() - 16].to_vec();
148
149 Ok(EncryptionResult {
150 ciphertext,
151 algorithm,
152 nonce,
153 tag,
154 })
155 }
156
157 pub fn to_json(&self) -> Result<String> {
159 #[derive(Serialize)]
160 struct JsonFormat<'a> {
161 v: &'static str,
162 alg: &'a str,
163 nonce: String,
164 ct: String,
165 tag: String,
166 }
167
168 use base64::{engine::general_purpose::STANDARD, Engine};
169
170 let json = JsonFormat {
171 v: "1.0",
172 alg: self.algorithm.name(),
173 nonce: STANDARD.encode(&self.nonce),
174 ct: STANDARD.encode(&self.ciphertext),
175 tag: STANDARD.encode(&self.tag),
176 };
177
178 serde_json::to_string(&json).map_err(|e| Error::SerializationError(e.to_string()))
179 }
180
181 pub fn from_json(json: &str) -> Result<Self> {
183 #[derive(Deserialize)]
184 struct JsonFormat {
185 v: String,
186 alg: String,
187 nonce: String,
188 ct: String,
189 tag: String,
190 }
191
192 let parsed: JsonFormat = serde_json::from_str(json)?;
193
194 if parsed.v != "1.0" {
195 return Err(Error::UnsupportedVersion(0));
196 }
197
198 let algorithm = match parsed.alg.as_str() {
199 "aes-256-gcm" => Algorithm::Aes256Gcm,
200 "xchacha20-poly1305" => Algorithm::XChaCha20Poly1305,
201 _ => return Err(Error::UnsupportedAlgorithm(0)),
202 };
203
204 use base64::{engine::general_purpose::STANDARD, Engine};
205
206 Ok(EncryptionResult {
207 ciphertext: STANDARD.decode(&parsed.ct)?,
208 algorithm,
209 nonce: STANDARD.decode(&parsed.nonce)?,
210 tag: STANDARD.decode(&parsed.tag)?,
211 })
212 }
213}
214
215impl Drop for EncryptionResult {
216 fn drop(&mut self) {
217 self.ciphertext.zeroize();
218 self.nonce.zeroize();
219 self.tag.zeroize();
220 }
221}
222
223#[derive(Debug, Clone, Default)]
225pub struct EncryptOptions {
226 pub algorithm: Option<Algorithm>,
228 pub aad: Option<Vec<u8>>,
230}
231
232pub fn encrypt(
246 plaintext: &[u8],
247 key: &Key,
248 options: Option<EncryptOptions>,
249) -> Result<EncryptionResult> {
250 let opts = options.unwrap_or_default();
251 let algorithm = opts.algorithm.unwrap_or(Algorithm::Aes256Gcm);
252 let aad = opts.aad.as_deref().unwrap_or(&[]);
253
254 match algorithm {
255 Algorithm::Aes256Gcm => encrypt_aes_gcm(plaintext, key, aad),
256 Algorithm::XChaCha20Poly1305 => encrypt_xchacha20(plaintext, key, aad),
257 }
258}
259
260pub fn decrypt(encrypted: &EncryptionResult, key: &Key) -> Result<Vec<u8>> {
273 decrypt_with_aad(encrypted, key, &[])
274}
275
276pub fn decrypt_with_aad(encrypted: &EncryptionResult, key: &Key, aad: &[u8]) -> Result<Vec<u8>> {
288 match encrypted.algorithm {
289 Algorithm::Aes256Gcm => decrypt_aes_gcm(encrypted, key, aad),
290 Algorithm::XChaCha20Poly1305 => decrypt_xchacha20(encrypted, key, aad),
291 }
292}
293
294pub fn decrypt_bytes(data: &[u8], key: &Key) -> Result<Vec<u8>> {
296 let encrypted = EncryptionResult::from_bytes(data)?;
297 decrypt(&encrypted, key)
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_encrypt_decrypt_roundtrip() {
306 let key = generate_key();
307 let plaintext = b"Hello, World!";
308
309 let encrypted = encrypt(plaintext, &key, None).unwrap();
310 let decrypted = decrypt(&encrypted, &key).unwrap();
311
312 assert_eq!(plaintext, &decrypted[..]);
313 }
314
315 #[test]
316 fn test_binary_serialization() {
317 let key = generate_key();
318 let plaintext = b"Test data for serialization";
319
320 let encrypted = encrypt(plaintext, &key, None).unwrap();
321 let bytes = encrypted.to_bytes();
322 let restored = EncryptionResult::from_bytes(&bytes).unwrap();
323
324 assert_eq!(encrypted.algorithm, restored.algorithm);
325 assert_eq!(encrypted.nonce, restored.nonce);
326 assert_eq!(encrypted.ciphertext, restored.ciphertext);
327 assert_eq!(encrypted.tag, restored.tag);
328 }
329
330 #[test]
331 fn test_json_serialization() {
332 let key = generate_key();
333 let plaintext = b"Test data for JSON";
334
335 let encrypted = encrypt(plaintext, &key, None).unwrap();
336 let json = encrypted.to_json().unwrap();
337 let restored = EncryptionResult::from_json(&json).unwrap();
338
339 assert_eq!(encrypted.algorithm, restored.algorithm);
340 assert_eq!(encrypted.nonce, restored.nonce);
341 assert_eq!(encrypted.ciphertext, restored.ciphertext);
342 assert_eq!(encrypted.tag, restored.tag);
343 }
344}