1use crate::classic::crypto_secretbox_impl::*;
41use crate::constants::{
42 CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
43 CRYPTO_SECRETBOX_NONCEBYTES,
44};
45use crate::error::Error;
46use crate::rng::copy_randombytes;
47use crate::types::*;
48
49pub type Mac = [u8; CRYPTO_SECRETBOX_MACBYTES];
51pub type Nonce = [u8; CRYPTO_SECRETBOX_NONCEBYTES];
53pub type Key = [u8; CRYPTO_SECRETBOX_KEYBYTES];
55
56fn validate_message_len(message_len: usize, context: crate::ErrorContext) -> Result<(), Error> {
57 validate_length!(max CRYPTO_SECRETBOX_MESSAGEBYTES_MAX, message_len, context);
58 Ok(())
59}
60
61pub fn crypto_secretbox_keygen_inplace(key: &mut Key) {
63 copy_randombytes(key)
64}
65
66#[must_use]
69pub 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 validate_length!(min message.len(), ciphertext.len(), crate::ErrorContext::Ciphertext);
90
91 crypto_secretbox_detached_b2b(&mut ciphertext[..message.len()], mac, message, nonce, key);
92 Ok(())
93}
94
95pub fn crypto_secretbox_open_detached(
104 message: &mut [u8],
105 ciphertext: &[u8],
106 mac: &Mac,
107 nonce: &Nonce,
108 key: &Key,
109) -> Result<(), Error> {
110 let c_len = ciphertext.len();
111 validate_message_len(c_len, crate::ErrorContext::Ciphertext)?;
112 validate_length!(min c_len, message.len(), crate::ErrorContext::Message);
113
114 crypto_secretbox_open_detached_b2b(&mut message[..c_len], ciphertext, mac, nonce, key)
115}
116
117pub fn crypto_secretbox_easy(
126 ciphertext: &mut [u8],
127 message: &[u8],
128 nonce: &Nonce,
129 key: &Key,
130) -> Result<(), Error> {
131 validate_message_len(message.len(), crate::ErrorContext::Message)?;
132
133 let expected_len = message.len() + CRYPTO_SECRETBOX_MACBYTES;
134 validate_length!(exact expected_len, ciphertext.len(), crate::ErrorContext::Ciphertext);
135
136 let mut mac = Mac::default();
137 crypto_secretbox_detached(
138 &mut ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
139 &mut mac,
140 message,
141 nonce,
142 key,
143 )?;
144
145 ciphertext[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(&mac);
146
147 Ok(())
148}
149
150pub fn crypto_secretbox_open_easy(
159 message: &mut [u8],
160 ciphertext: &[u8],
161 nonce: &Nonce,
162 key: &Key,
163) -> Result<(), Error> {
164 validate_length!(
165 min CRYPTO_SECRETBOX_MACBYTES,
166 ciphertext.len(),
167 crate::ErrorContext::Ciphertext
168 );
169 validate_length!(
170 exact ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES,
171 message.len(),
172 crate::ErrorContext::Message
173 );
174
175 let (mac, ciphertext) = ciphertext
176 .split_first_chunk::<CRYPTO_SECRETBOX_MACBYTES>()
177 .expect("validated ciphertext length");
178 crypto_secretbox_open_detached(message, ciphertext, mac, nonce, key)
179}
180
181pub fn crypto_secretbox_easy_inplace(
188 data: &mut [u8],
189 nonce: &Nonce,
190 key: &Key,
191) -> Result<(), Error> {
192 validate_length!(min CRYPTO_SECRETBOX_MACBYTES, data.len(), crate::ErrorContext::Data);
193 data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
194 let (mac, data) = data
195 .split_first_chunk_mut::<CRYPTO_SECRETBOX_MACBYTES>()
196 .expect("validated data length");
197
198 crypto_secretbox_detached_inplace(data, mac, nonce, key);
199
200 Ok(())
201}
202
203pub fn crypto_secretbox_open_easy_inplace(
211 ciphertext: &mut [u8],
212 nonce: &Nonce,
213 key: &Key,
214) -> Result<(), Error> {
215 validate_length!(
216 min CRYPTO_SECRETBOX_MACBYTES,
217 ciphertext.len(),
218 crate::ErrorContext::Ciphertext
219 );
220
221 let (mac, data) = ciphertext
222 .split_first_chunk_mut::<CRYPTO_SECRETBOX_MACBYTES>()
223 .expect("validated ciphertext length");
224 let mac = &*mac;
225
226 crypto_secretbox_open_detached_inplace(data, mac, nonce, key)?;
227
228 ciphertext.rotate_left(CRYPTO_SECRETBOX_MACBYTES);
229
230 Ok(())
231}
232
233#[cfg(test)]
234mod tests {
235 use crate::test_prelude::*;
236
237 #[cfg(all(feature = "nightly", dryoc_native_tests))]
238 extern crate test;
239
240 use super::*;
241
242 #[test]
243 fn rejects_lengths_above_the_libsodium_limit() {
244 let too_long = CRYPTO_SECRETBOX_MESSAGEBYTES_MAX + 1;
245
246 assert!(matches!(
247 validate_message_len(too_long, crate::ErrorContext::Message),
248 Err(Error::InvalidLength {
249 context: crate::ErrorContext::Message,
250 actual,
251 constraint: crate::LengthConstraint::AtMost(CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
252 }) if actual == too_long
253 ));
254 }
255
256 #[test]
257 fn test_crypto_secretbox_rejects_invalid_buffer_lengths_without_mutation() {
258 let key = Key::default();
259 let nonce = Nonce::default();
260 let message = b"buffer length validation";
261
262 let mut short_detached = vec![0xa5; message.len() - 1];
263 let original_short_detached = short_detached.clone();
264 let mut mac = [0x5a; CRYPTO_SECRETBOX_MACBYTES];
265 let original_mac = mac;
266 assert!(
267 crypto_secretbox_detached(&mut short_detached, &mut mac, message, &nonce, &key)
268 .is_err()
269 );
270 assert_eq!(short_detached, original_short_detached);
271 assert_eq!(mac, original_mac);
272
273 for output_len in [
274 message.len() + CRYPTO_SECRETBOX_MACBYTES - 1,
275 message.len() + CRYPTO_SECRETBOX_MACBYTES + 1,
276 ] {
277 let mut output = vec![0xa5; output_len];
278 let original = output.clone();
279 assert!(crypto_secretbox_easy(&mut output, message, &nonce, &key).is_err());
280 assert_eq!(output, original);
281 }
282
283 let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
284 crypto_secretbox_easy(&mut ciphertext, message, &nonce, &key).expect("encrypt failed");
285
286 for output_len in [message.len() - 1, message.len() + 1] {
287 let mut output = vec![0xa5; output_len];
288 let original = output.clone();
289 assert!(crypto_secretbox_open_easy(&mut output, &ciphertext, &nonce, &key).is_err());
290 assert_eq!(output, original);
291 }
292
293 let mut short_open = vec![0xa5; message.len() - 1];
294 let original_short_open = short_open.clone();
295 assert!(
296 crypto_secretbox_open_detached(
297 &mut short_open,
298 &ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
299 ciphertext.first_chunk().expect("sealed ciphertext"),
300 &nonce,
301 &key,
302 )
303 .is_err()
304 );
305 assert_eq!(short_open, original_short_open);
306
307 let mut too_short_inplace = vec![0xa5; CRYPTO_SECRETBOX_MACBYTES - 1];
308 let original_too_short_inplace = too_short_inplace.clone();
309 assert!(crypto_secretbox_easy_inplace(&mut too_short_inplace, &nonce, &key).is_err());
310 assert_eq!(too_short_inplace, original_too_short_inplace);
311 }
312
313 #[cfg(dryoc_native_tests)]
314 #[test]
315 fn test_crypto_secretbox_easy() {
316 for i in 0..20 {
317 use base64::Engine as _;
318 use base64::engine::general_purpose;
319
320 use crate::native_test_util::{secretbox_easy, secretbox_open_easy};
321
322 let key: Key = crypto_secretbox_keygen();
323 let nonce = Nonce::generate();
324
325 let words = vec!["love Doge".to_string(); i];
326 let message = words.join(" <3 ");
327
328 let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
329 crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
330 .expect("encrypt failed");
331 let so_ciphertext = secretbox_easy(message.as_bytes(), &nonce, &key);
332 assert_eq!(
333 general_purpose::STANDARD.encode(&ciphertext),
334 general_purpose::STANDARD.encode(&so_ciphertext)
335 );
336
337 let mut decrypted = vec![0u8; message.len()];
338 crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key)
339 .expect("decrypt failed");
340 let so_decrypted = secretbox_open_easy(&ciphertext, &nonce, &key).unwrap();
341
342 assert_eq!(decrypted, message.as_bytes());
343 assert_eq!(decrypted, so_decrypted);
344 }
345 }
346
347 #[cfg(dryoc_native_tests)]
348 #[test]
349 fn test_crypto_secretbox_easy_inplace() {
350 for i in 0..20 {
351 use base64::Engine as _;
352 use base64::engine::general_purpose;
353
354 use crate::native_test_util::{secretbox_easy, secretbox_open_easy};
355
356 let key = crypto_secretbox_keygen();
357 let nonce = Nonce::generate();
358
359 let words = vec!["love Doge".to_string(); i];
360 let message: Vec<u8> = words.join(" <3 ").into();
361 let message_copy = message.clone();
362
363 let mut ciphertext = message.clone();
364 ciphertext.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
365 crypto_secretbox_easy_inplace(&mut ciphertext, &nonce, &key).expect("encrypt failed");
366 let so_ciphertext = secretbox_easy(&message_copy, &nonce, &key);
367 assert_eq!(
368 general_purpose::STANDARD.encode(&ciphertext),
369 general_purpose::STANDARD.encode(&so_ciphertext)
370 );
371
372 let mut decrypted = ciphertext.clone();
373 crypto_secretbox_open_easy_inplace(&mut decrypted, &nonce, &key)
374 .expect("decrypt failed");
375 decrypted.resize(ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES, 0);
376 let so_decrypted =
377 secretbox_open_easy(&ciphertext, &nonce, &key).expect("decrypt failed");
378
379 assert_eq!(&decrypted, &message_copy);
380 assert_eq!(decrypted, so_decrypted);
381 }
382 }
383
384 #[test]
385 fn test_crypto_secretbox_detached_only_touches_message_len() {
386 let key = crypto_secretbox_keygen();
387 let nonce = Nonce::generate();
388 let message = b"detached secretbox buffer prefix";
389 let mut ciphertext = vec![0xa5; message.len() + 8];
390 let mut mac = Mac::default();
391
392 crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
393 .expect("encrypt failed");
394
395 assert_eq!(&ciphertext[message.len()..], &[0xa5; 8]);
396
397 let mut decrypted = vec![0x5a; message.len() + 8];
398 crypto_secretbox_open_detached(
399 &mut decrypted,
400 &ciphertext[..message.len()],
401 &mac,
402 &nonce,
403 &key,
404 )
405 .expect("decrypt failed");
406
407 assert_eq!(&decrypted[..message.len()], message);
408 assert_eq!(&decrypted[message.len()..], &[0x5a; 8]);
409 }
410
411 #[test]
412 fn test_crypto_secretbox_open_failure_keeps_output() {
413 let key = crypto_secretbox_keygen();
414 let nonce = Nonce::generate();
415 let message = b"authenticated plaintext";
416 let mut ciphertext = vec![0u8; message.len()];
417 let mut mac = Mac::default();
418
419 crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
420 .expect("encrypt failed");
421 mac[0] ^= 1;
422
423 let mut decrypted = vec![0x5a; message.len()];
424 let original_decrypted = decrypted.clone();
425 assert!(
426 crypto_secretbox_open_detached(&mut decrypted, &ciphertext, &mac, &nonce, &key)
427 .is_err()
428 );
429 assert_eq!(decrypted, original_decrypted);
430
431 let mut inplace = ciphertext.clone();
432 assert!(crypto_secretbox_open_detached_inplace(&mut inplace, &mac, &nonce, &key).is_err());
433 assert_eq!(inplace, ciphertext);
434 }
435
436 fn fixture(len: usize) -> (Key, Nonce, Vec<u8>) {
438 let mut rng = crate::utils::test_util::XorShift64::new(0x5ec2_e7b0_1d3a_9f41);
439 let key = rng.next_bytes32();
440 let nonce: Nonce = rng.next_bytes32()[..CRYPTO_SECRETBOX_NONCEBYTES]
441 .try_into()
442 .unwrap();
443 let message = (0..len)
444 .map(|i| (i as u8).wrapping_mul(31) ^ 0x3c)
445 .collect();
446 (key, nonce, message)
447 }
448
449 #[test]
454 fn test_open_easy_inplace_failures_leave_buffer_untouched() {
455 let (key, nonce, message) = fixture(100);
456 let mut sealed = message.clone();
457 sealed.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
458 crypto_secretbox_easy_inplace(&mut sealed, &nonce, &key).expect("encrypt failed");
459
460 let mut cases = Vec::new();
461 for (name, index) in [
462 ("first tag byte", 0),
463 ("last tag byte", CRYPTO_SECRETBOX_MACBYTES - 1),
464 ("first ciphertext byte", CRYPTO_SECRETBOX_MACBYTES),
465 ("last ciphertext byte", sealed.len() - 1),
466 ] {
467 let mut tampered = sealed.clone();
468 tampered[index] ^= 1;
469 cases.push((name, tampered));
470 }
471 for (name, tampered) in cases {
472 let mut buffer = tampered.clone();
473 assert!(
474 matches!(
475 crypto_secretbox_open_easy_inplace(&mut buffer, &nonce, &key),
476 Err(Error::AuthenticationFailed)
477 ),
478 "{name}: in place"
479 );
480 assert_eq!(buffer, tampered, "{name}: in place buffer");
481
482 let mut output = vec![0xa5u8; message.len()];
483 assert!(
484 matches!(
485 crypto_secretbox_open_easy(&mut output, &tampered, &nonce, &key),
486 Err(Error::AuthenticationFailed)
487 ),
488 "{name}: b2b"
489 );
490 assert_eq!(output, vec![0xa5u8; message.len()], "{name}: b2b output");
491 }
492
493 let mut short = sealed[..CRYPTO_SECRETBOX_MACBYTES - 1].to_vec();
494 let original = short.clone();
495 assert!(matches!(
496 crypto_secretbox_open_easy_inplace(&mut short, &nonce, &key),
497 Err(Error::InvalidLength { .. })
498 ));
499 assert_eq!(short, original);
500 let mut output = [0xa5u8];
501 assert!(matches!(
502 crypto_secretbox_open_easy(&mut output, &original, &nonce, &key),
503 Err(Error::InvalidLength { .. })
504 ));
505 assert_eq!(output, [0xa5]);
506
507 let mut buffer = sealed.clone();
508 crypto_secretbox_open_easy_inplace(&mut buffer, &nonce, &key).expect("decrypt failed");
509 assert_eq!(&buffer[..message.len()], message);
510 }
511
512 #[cfg(dryoc_native_tests)]
517 fn boundary_lens() -> Vec<usize> {
518 let mut lens = vec![0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65];
519 for chunk in [5 * 64, 8 * 64, 16 * 64] {
520 lens.extend([
521 chunk - 33,
522 chunk - 32,
523 chunk - 31,
524 chunk - 1,
525 chunk,
526 chunk + 1,
527 2 * chunk - 33,
528 2 * chunk - 32,
529 2 * chunk - 31,
530 ]);
531 }
532 lens.sort_unstable();
533 lens.dedup();
534 lens
535 }
536
537 #[cfg(dryoc_native_tests)]
541 #[test]
542 fn test_matches_libsodium_at_boundary_lengths() {
543 use libc::c_ulonglong;
544
545 crate::native_test_util::init();
546
547 for len in boundary_lens() {
548 let (key, nonce, message) = fixture(len);
549 let mut expected = vec![0u8; len];
550 let mut expected_mac = Mac::default();
551 let mut expected_easy = vec![0u8; len + CRYPTO_SECRETBOX_MACBYTES];
552 unsafe {
555 assert_eq!(
556 libsodium_sys::crypto_secretbox_detached(
557 expected.as_mut_ptr(),
558 expected_mac.as_mut_ptr(),
559 message.as_ptr(),
560 len as c_ulonglong,
561 nonce.as_ptr(),
562 key.as_ptr(),
563 ),
564 0
565 );
566 assert_eq!(
567 libsodium_sys::crypto_secretbox_easy(
568 expected_easy.as_mut_ptr(),
569 message.as_ptr(),
570 len as c_ulonglong,
571 nonce.as_ptr(),
572 key.as_ptr(),
573 ),
574 0
575 );
576 }
577 assert_eq!(expected_easy[..CRYPTO_SECRETBOX_MACBYTES], expected_mac);
578 assert_eq!(expected_easy[CRYPTO_SECRETBOX_MACBYTES..], expected);
579
580 let mut ciphertext = vec![0u8; len];
581 let mut mac = Mac::default();
582 crypto_secretbox_detached(&mut ciphertext, &mut mac, &message, &nonce, &key)
583 .expect("detached");
584 assert_eq!(
585 (&ciphertext, mac),
586 (&expected, expected_mac),
587 "len {len}: detached"
588 );
589
590 let mut data = message.clone();
591 let mut mac = Mac::default();
592 crypto_secretbox_detached_inplace(&mut data, &mut mac, &nonce, &key);
593 assert_eq!(
594 (&data, mac),
595 (&expected, expected_mac),
596 "len {len}: detached in place"
597 );
598
599 let mut easy = vec![0u8; len + CRYPTO_SECRETBOX_MACBYTES];
600 crypto_secretbox_easy(&mut easy, &message, &nonce, &key).expect("easy");
601 assert_eq!(easy, expected_easy, "len {len}: easy");
602
603 let mut data = message.clone();
604 data.resize(len + CRYPTO_SECRETBOX_MACBYTES, 0);
605 crypto_secretbox_easy_inplace(&mut data, &nonce, &key).expect("easy in place");
606 assert_eq!(data, expected_easy, "len {len}: easy in place");
607
608 let mut output = vec![0u8; len];
609 crypto_secretbox_open_detached(&mut output, &expected, &expected_mac, &nonce, &key)
610 .expect("open detached");
611 assert_eq!(output, message, "len {len}: open detached");
612
613 let mut data = expected.clone();
614 crypto_secretbox_open_detached_inplace(&mut data, &expected_mac, &nonce, &key)
615 .expect("open detached in place");
616 assert_eq!(data, message, "len {len}: open detached in place");
617
618 let mut output = vec![0u8; len];
619 crypto_secretbox_open_easy(&mut output, &expected_easy, &nonce, &key)
620 .expect("open easy");
621 assert_eq!(output, message, "len {len}: open easy");
622
623 let mut data = expected_easy.clone();
624 crypto_secretbox_open_easy_inplace(&mut data, &nonce, &key)
625 .expect("open easy in place");
626 assert_eq!(&data[..len], message, "len {len}: open easy in place");
627 }
628 }
629
630 #[cfg(all(feature = "nightly", dryoc_native_tests))]
631 fn bench_crypto_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
632 let key: Key = crypto_secretbox_keygen();
633 let nonce = Nonce::generate();
634 let mut message = vec![0u8; message_len];
635 crate::rng::copy_randombytes(&mut message);
636 let mut ciphertext = vec![0u8; message_len];
637 let mut mac = Mac::default();
638
639 b.bytes = message_len as u64;
640 b.iter(|| {
641 crypto_secretbox_detached(
642 test::black_box(&mut ciphertext),
643 test::black_box(&mut mac),
644 test::black_box(&message),
645 test::black_box(&nonce),
646 test::black_box(&key),
647 )
648 .expect("encrypt failed");
649 });
650 }
651
652 #[cfg(all(feature = "nightly", dryoc_native_tests))]
653 #[bench]
654 fn crypto_secretbox_detached_64b_bench(b: &mut test::Bencher) {
655 bench_crypto_secretbox_detached(b, 64);
656 }
657
658 #[cfg(all(feature = "nightly", dryoc_native_tests))]
659 #[bench]
660 fn crypto_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
661 bench_crypto_secretbox_detached(b, 1024);
662 }
663
664 #[cfg(all(feature = "nightly", dryoc_native_tests))]
665 #[bench]
666 fn crypto_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
667 bench_crypto_secretbox_detached(b, 16 * 1024);
668 }
669
670 #[cfg(all(feature = "nightly", dryoc_native_tests))]
671 #[bench]
672 fn crypto_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
673 bench_crypto_secretbox_detached(b, 1024 * 1024);
674 }
675
676 #[cfg(all(feature = "nightly", dryoc_native_tests))]
680 fn bench_libsodium_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
681 crate::native_test_util::init();
682
683 let key: Key = crypto_secretbox_keygen();
684 let nonce = Nonce::generate();
685 let mut message = vec![0u8; message_len];
686 crate::rng::copy_randombytes(&mut message);
687 let mut ciphertext = vec![0u8; message_len];
688 let mut mac = Mac::default();
689
690 b.bytes = message_len as u64;
691 b.iter(|| {
692 let rc = unsafe {
695 libsodium_sys::crypto_secretbox_detached(
696 ciphertext.as_mut_ptr(),
697 mac.as_mut_ptr(),
698 test::black_box(message.as_ptr()),
699 message_len as u64,
700 nonce.as_ptr(),
701 key.as_ptr(),
702 )
703 };
704 assert_eq!(rc, 0);
705 test::black_box((&ciphertext, &mac));
706 });
707 }
708
709 #[cfg(all(feature = "nightly", dryoc_native_tests))]
710 #[bench]
711 fn libsodium_secretbox_detached_64b_bench(b: &mut test::Bencher) {
712 bench_libsodium_secretbox_detached(b, 64);
713 }
714
715 #[cfg(all(feature = "nightly", dryoc_native_tests))]
716 #[bench]
717 fn libsodium_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
718 bench_libsodium_secretbox_detached(b, 1024);
719 }
720
721 #[cfg(all(feature = "nightly", dryoc_native_tests))]
722 #[bench]
723 fn libsodium_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
724 bench_libsodium_secretbox_detached(b, 16 * 1024);
725 }
726
727 #[cfg(all(feature = "nightly", dryoc_native_tests))]
728 #[bench]
729 fn libsodium_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
730 bench_libsodium_secretbox_detached(b, 1024 * 1024);
731 }
732}