1use crate::classic::crypto_secretbox_impl::*;
35use crate::constants::{
36 CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
37 CRYPTO_SECRETBOX_NONCEBYTES,
38};
39use crate::error::Error;
40use crate::rng::copy_randombytes;
41use crate::types::*;
42
43pub type Mac = [u8; CRYPTO_SECRETBOX_MACBYTES];
45pub type Nonce = [u8; CRYPTO_SECRETBOX_NONCEBYTES];
47pub type Key = [u8; CRYPTO_SECRETBOX_KEYBYTES];
49
50fn validate_message_len(message_len: usize, context: crate::ErrorContext) -> Result<(), Error> {
51 if message_len > CRYPTO_SECRETBOX_MESSAGEBYTES_MAX {
52 Err(length_error!(
53 context,
54 message_len,
55 max CRYPTO_SECRETBOX_MESSAGEBYTES_MAX
56 ))
57 } else {
58 Ok(())
59 }
60}
61
62pub fn crypto_secretbox_keygen_inplace(key: &mut Key) {
64 copy_randombytes(key)
65}
66
67pub fn crypto_secretbox_keygen() -> Key {
70 Key::generate()
71}
72
73pub fn crypto_secretbox_detached(
82 ciphertext: &mut [u8],
83 mac: &mut Mac,
84 message: &[u8],
85 nonce: &Nonce,
86 key: &Key,
87) -> Result<(), Error> {
88 validate_message_len(message.len(), crate::ErrorContext::Message)?;
89
90 if ciphertext.len() < message.len() {
91 return Err(
92 length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min message.len()),
93 );
94 }
95
96 crypto_secretbox_detached_b2b(&mut ciphertext[..message.len()], mac, message, nonce, key);
97 Ok(())
98}
99
100pub fn crypto_secretbox_open_detached(
109 message: &mut [u8],
110 mac: &Mac,
111 ciphertext: &[u8],
112 nonce: &Nonce,
113 key: &Key,
114) -> Result<(), Error> {
115 let c_len = ciphertext.len();
116 validate_message_len(c_len, crate::ErrorContext::Ciphertext)?;
117
118 if message.len() < c_len {
119 return Err(length_error!(crate::ErrorContext::Message, message.len(), min c_len));
120 }
121
122 crypto_secretbox_open_detached_b2b(&mut message[..c_len], mac, ciphertext, nonce, key)
123}
124
125pub fn crypto_secretbox_easy(
134 ciphertext: &mut [u8],
135 message: &[u8],
136 nonce: &Nonce,
137 key: &Key,
138) -> Result<(), Error> {
139 validate_message_len(message.len(), crate::ErrorContext::Message)?;
140
141 let expected_len = message.len() + CRYPTO_SECRETBOX_MACBYTES;
142 if ciphertext.len() != expected_len {
143 return Err(
144 length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), exact expected_len),
145 );
146 }
147
148 let mut mac = Mac::default();
149 crypto_secretbox_detached(
150 &mut ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
151 &mut mac,
152 message,
153 nonce,
154 key,
155 )?;
156
157 ciphertext[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(&mac);
158
159 Ok(())
160}
161
162pub fn crypto_secretbox_open_easy(
171 message: &mut [u8],
172 ciphertext: &[u8],
173 nonce: &Nonce,
174 key: &Key,
175) -> Result<(), Error> {
176 if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
177 Err(
178 length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_SECRETBOX_MACBYTES),
179 )
180 } else if message.len() != ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES {
181 Err(length_error!(
182 crate::ErrorContext::Message,
183 message.len(),
184 exact ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES
185 ))
186 } else {
187 let (mac, ciphertext) = ciphertext.split_at(CRYPTO_SECRETBOX_MACBYTES);
188 let mac = ByteArray::as_array(mac);
189 crypto_secretbox_open_detached(message, mac, ciphertext, nonce, key)
190 }
191}
192
193pub fn crypto_secretbox_easy_inplace(
200 data: &mut [u8],
201 nonce: &Nonce,
202 key: &Key,
203) -> Result<(), Error> {
204 if data.len() < CRYPTO_SECRETBOX_MACBYTES {
205 return Err(
206 length_error!(crate::ErrorContext::Data, data.len(), min CRYPTO_SECRETBOX_MACBYTES),
207 );
208 }
209 data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
210 let (mac, data) = data.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
211 let mac = MutByteArray::as_mut_array(mac);
212
213 crypto_secretbox_detached_inplace(data, mac, nonce, key);
214
215 Ok(())
216}
217
218pub fn crypto_secretbox_open_easy_inplace(
226 ciphertext: &mut [u8],
227 nonce: &Nonce,
228 key: &Key,
229) -> Result<(), Error> {
230 if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
231 Err(
232 length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_SECRETBOX_MACBYTES),
233 )
234 } else {
235 let (mac, data) = ciphertext.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
236 let mac = ByteArray::as_array(mac);
237
238 crypto_secretbox_open_detached_inplace(data, mac, nonce, key)?;
239
240 ciphertext.rotate_left(CRYPTO_SECRETBOX_MACBYTES);
241
242 Ok(())
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 #[cfg(all(feature = "nightly", dryoc_native_tests))]
249 extern crate test;
250
251 use super::*;
252
253 #[test]
254 fn rejects_lengths_above_the_libsodium_limit() {
255 let too_long = CRYPTO_SECRETBOX_MESSAGEBYTES_MAX + 1;
256
257 assert!(matches!(
258 validate_message_len(too_long, crate::ErrorContext::Message),
259 Err(Error::InvalidLength {
260 context: crate::ErrorContext::Message,
261 actual,
262 constraint: crate::LengthConstraint::AtMost(CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
263 }) if actual == too_long
264 ));
265 }
266
267 #[test]
268 fn test_crypto_secretbox_rejects_invalid_buffer_lengths_without_mutation() {
269 let key = Key::default();
270 let nonce = Nonce::default();
271 let message = b"buffer length validation";
272
273 let mut short_detached = vec![0xa5; message.len() - 1];
274 let original_short_detached = short_detached.clone();
275 let mut mac = [0x5a; CRYPTO_SECRETBOX_MACBYTES];
276 let original_mac = mac;
277 assert!(
278 crypto_secretbox_detached(&mut short_detached, &mut mac, message, &nonce, &key)
279 .is_err()
280 );
281 assert_eq!(short_detached, original_short_detached);
282 assert_eq!(mac, original_mac);
283
284 for output_len in [
285 message.len() + CRYPTO_SECRETBOX_MACBYTES - 1,
286 message.len() + CRYPTO_SECRETBOX_MACBYTES + 1,
287 ] {
288 let mut output = vec![0xa5; output_len];
289 let original = output.clone();
290 assert!(crypto_secretbox_easy(&mut output, message, &nonce, &key).is_err());
291 assert_eq!(output, original);
292 }
293
294 let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
295 crypto_secretbox_easy(&mut ciphertext, message, &nonce, &key).expect("encrypt failed");
296
297 for output_len in [message.len() - 1, message.len() + 1] {
298 let mut output = vec![0xa5; output_len];
299 let original = output.clone();
300 assert!(crypto_secretbox_open_easy(&mut output, &ciphertext, &nonce, &key).is_err());
301 assert_eq!(output, original);
302 }
303
304 let mut short_open = vec![0xa5; message.len() - 1];
305 let original_short_open = short_open.clone();
306 assert!(
307 crypto_secretbox_open_detached(
308 &mut short_open,
309 ByteArray::as_array(&ciphertext[..CRYPTO_SECRETBOX_MACBYTES]),
310 &ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
311 &nonce,
312 &key,
313 )
314 .is_err()
315 );
316 assert_eq!(short_open, original_short_open);
317
318 let mut too_short_inplace = vec![0xa5; CRYPTO_SECRETBOX_MACBYTES - 1];
319 let original_too_short_inplace = too_short_inplace.clone();
320 assert!(crypto_secretbox_easy_inplace(&mut too_short_inplace, &nonce, &key).is_err());
321 assert_eq!(too_short_inplace, original_too_short_inplace);
322 }
323
324 #[cfg(dryoc_native_tests)]
325 #[test]
326 fn test_crypto_secretbox_easy() {
327 for i in 0..20 {
328 use base64::Engine as _;
329 use base64::engine::general_purpose;
330 use sodiumoxide::crypto::secretbox;
331 use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
332
333 let key: Key = crypto_secretbox_keygen();
334 let nonce = Nonce::generate();
335
336 let words = vec!["love Doge".to_string(); i];
337 let message = words.join(" <3 ");
338
339 let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
340 crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
341 .expect("encrypt failed");
342 let so_ciphertext = secretbox::seal(
343 message.as_bytes(),
344 &SONonce::from_slice(&nonce).unwrap(),
345 &SOKey::from_slice(&key).unwrap(),
346 );
347 assert_eq!(
348 general_purpose::STANDARD.encode(&ciphertext),
349 general_purpose::STANDARD.encode(&so_ciphertext)
350 );
351
352 let mut decrypted = vec![0u8; message.len()];
353 crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key)
354 .expect("decrypt failed");
355 let so_decrypted = secretbox::open(
356 &ciphertext,
357 &SONonce::from_slice(&nonce).unwrap(),
358 &SOKey::from_slice(&key).unwrap(),
359 )
360 .unwrap();
361
362 assert_eq!(decrypted, message.as_bytes());
363 assert_eq!(decrypted, so_decrypted);
364 }
365 }
366
367 #[cfg(dryoc_native_tests)]
368 #[test]
369 fn test_crypto_secretbox_easy_inplace() {
370 for i in 0..20 {
371 use base64::Engine as _;
372 use base64::engine::general_purpose;
373 use sodiumoxide::crypto::secretbox;
374 use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
375
376 let key = crypto_secretbox_keygen();
377 let nonce = Nonce::generate();
378
379 let words = vec!["love Doge".to_string(); i];
380 let message: Vec<u8> = words.join(" <3 ").into();
381 let message_copy = message.clone();
382
383 let mut ciphertext = message.clone();
384 ciphertext.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
385 crypto_secretbox_easy_inplace(&mut ciphertext, &nonce, &key).expect("encrypt failed");
386 let so_ciphertext = secretbox::seal(
387 &message_copy,
388 &SONonce::from_slice(&nonce).unwrap(),
389 &SOKey::from_slice(&key).unwrap(),
390 );
391 assert_eq!(
392 general_purpose::STANDARD.encode(&ciphertext),
393 general_purpose::STANDARD.encode(&so_ciphertext)
394 );
395
396 let mut decrypted = ciphertext.clone();
397 crypto_secretbox_open_easy_inplace(&mut decrypted, &nonce, &key)
398 .expect("decrypt failed");
399 decrypted.resize(ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES, 0);
400 let so_decrypted = secretbox::open(
401 &ciphertext,
402 &SONonce::from_slice(&nonce).unwrap(),
403 &SOKey::from_slice(&key).unwrap(),
404 )
405 .expect("decrypt failed");
406
407 assert_eq!(&decrypted, &message_copy);
408 assert_eq!(decrypted, so_decrypted);
409 }
410 }
411
412 #[test]
413 fn test_crypto_secretbox_detached_only_touches_message_len() {
414 let key = crypto_secretbox_keygen();
415 let nonce = Nonce::generate();
416 let message = b"detached secretbox buffer prefix";
417 let mut ciphertext = vec![0xa5; message.len() + 8];
418 let mut mac = Mac::default();
419
420 crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
421 .expect("encrypt failed");
422
423 assert_eq!(&ciphertext[message.len()..], &[0xa5; 8]);
424
425 let mut decrypted = vec![0x5a; message.len() + 8];
426 crypto_secretbox_open_detached(
427 &mut decrypted,
428 &mac,
429 &ciphertext[..message.len()],
430 &nonce,
431 &key,
432 )
433 .expect("decrypt failed");
434
435 assert_eq!(&decrypted[..message.len()], message);
436 assert_eq!(&decrypted[message.len()..], &[0x5a; 8]);
437 }
438
439 #[test]
440 fn test_crypto_secretbox_open_failure_keeps_output() {
441 let key = crypto_secretbox_keygen();
442 let nonce = Nonce::generate();
443 let message = b"authenticated plaintext";
444 let mut ciphertext = vec![0u8; message.len()];
445 let mut mac = Mac::default();
446
447 crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
448 .expect("encrypt failed");
449 mac[0] ^= 1;
450
451 let mut decrypted = vec![0x5a; message.len()];
452 let original_decrypted = decrypted.clone();
453 assert!(
454 crypto_secretbox_open_detached(&mut decrypted, &mac, &ciphertext, &nonce, &key)
455 .is_err()
456 );
457 assert_eq!(decrypted, original_decrypted);
458
459 let mut inplace = ciphertext.clone();
460 assert!(crypto_secretbox_open_detached_inplace(&mut inplace, &mac, &nonce, &key).is_err());
461 assert_eq!(inplace, ciphertext);
462 }
463
464 #[cfg(all(feature = "nightly", dryoc_native_tests))]
465 fn bench_crypto_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
466 let key: Key = crypto_secretbox_keygen();
467 let nonce = Nonce::generate();
468 let mut message = vec![0u8; message_len];
469 crate::rng::copy_randombytes(&mut message);
470 let mut ciphertext = vec![0u8; message_len];
471 let mut mac = Mac::default();
472
473 b.bytes = message_len as u64;
474 b.iter(|| {
475 crypto_secretbox_detached(
476 test::black_box(&mut ciphertext),
477 test::black_box(&mut mac),
478 test::black_box(&message),
479 test::black_box(&nonce),
480 test::black_box(&key),
481 )
482 .expect("encrypt failed");
483 });
484 }
485
486 #[cfg(all(feature = "nightly", dryoc_native_tests))]
487 #[bench]
488 fn crypto_secretbox_detached_64b_bench(b: &mut test::Bencher) {
489 bench_crypto_secretbox_detached(b, 64);
490 }
491
492 #[cfg(all(feature = "nightly", dryoc_native_tests))]
493 #[bench]
494 fn crypto_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
495 bench_crypto_secretbox_detached(b, 1024);
496 }
497
498 #[cfg(all(feature = "nightly", dryoc_native_tests))]
499 #[bench]
500 fn crypto_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
501 bench_crypto_secretbox_detached(b, 16 * 1024);
502 }
503
504 #[cfg(all(feature = "nightly", dryoc_native_tests))]
505 #[bench]
506 fn crypto_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
507 bench_crypto_secretbox_detached(b, 1024 * 1024);
508 }
509}