1#[cfg(feature = "alloc")]
67use alloc::vec::Vec;
68
69#[cfg(feature = "serde")]
70use serde::{Deserialize, Serialize};
71use zeroize::Zeroize;
72
73use crate::constants::{
74 CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_NONCEBYTES,
75};
76use crate::error::{Error, ErrorContext};
77use crate::types::*;
78use crate::utils::{ct_eq_bytes, split_prefix};
79
80pub type Key = StackByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
82pub type Nonce = StackByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
84pub type Mac = StackByteArray<CRYPTO_SECRETBOX_MACBYTES>;
86
87#[cfg(any(
88 all(feature = "protected", any(unix, windows)),
89 all(doc, not(doctest), feature = "std")
90))]
91#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
92pub mod protected {
93 use super::*;
126 pub use crate::protected::*;
127
128 pub type Key = HeapByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
131 pub type Nonce = HeapByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
134 pub type Mac = HeapByteArray<CRYPTO_SECRETBOX_MACBYTES>;
137
138 pub type LockedBox = DryocSecretBox<Locked<Mac>, LockedBytes>;
140}
141
142#[derive(Zeroize, Clone, Debug)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub struct DryocSecretBox<
149 Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
150 Data: Bytes + Zeroize,
151> {
152 tag: Mac,
153 data: Data,
154}
155
156#[cfg(feature = "alloc")]
158pub type VecBox = DryocSecretBox<Mac, Vec<u8>>;
159
160#[cfg(feature = "wincode_0_6")]
161impl_wincode_schema!(VecBox {
162 tag: [u8; CRYPTO_SECRETBOX_MACBYTES] = (src => src.tag.as_array(), tag => tag.into()),
163 data: Vec<u8> = (src => &src.data, data => data),
164});
165
166impl<
167 Mac: NewByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
168 Data: NewBytes + ResizableBytes + Zeroize,
169> DryocSecretBox<Mac, Data>
170{
171 pub fn encrypt<
187 Message: Bytes + ?Sized,
188 Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
189 SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
190 >(
191 message: &Message,
192 nonce: &Nonce,
193 secret_key: &SecretKey,
194 ) -> Result<Self, Error> {
195 use crate::classic::crypto_secretbox::crypto_secretbox_detached;
196
197 let mut new = Self {
198 tag: Mac::new_byte_array(),
199 data: Data::new_bytes(),
200 };
201 new.data.resize(message.len(), 0);
202
203 crypto_secretbox_detached(
204 new.data.as_mut_slice(),
205 new.tag.as_mut_array(),
206 message.as_slice(),
207 nonce.as_array(),
208 secret_key.as_array(),
209 )?;
210
211 Ok(new)
212 }
213}
214
215impl<
216 'a,
217 Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + core::convert::TryFrom<&'a [u8]> + Zeroize,
218 Data: Bytes + From<&'a [u8]> + Zeroize,
219> DryocSecretBox<Mac, Data>
220{
221 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
231 let (tag, data) = split_prefix(bytes, CRYPTO_SECRETBOX_MACBYTES, ErrorContext::SecretBox)?;
232 Ok(Self {
233 tag: Mac::try_from(tag)
234 .map_err(|_| Error::invalid_encoding(ErrorContext::AuthenticationTag))?,
235 data: Data::from(data),
236 })
237 }
238}
239
240impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
241 DryocSecretBox<Mac, Data>
242{
243 #[must_use]
245 pub fn from_parts(tag: Mac, data: Data) -> Self {
246 Self { tag, data }
247 }
248
249 pub fn tag(&self) -> &Mac {
251 &self.tag
252 }
253
254 pub fn data(&self) -> &Data {
256 &self.data
257 }
258
259 #[cfg(feature = "alloc")]
261 #[must_use]
262 pub fn to_vec(&self) -> Vec<u8> {
263 self.to_bytes()
264 }
265
266 #[must_use]
268 pub fn into_parts(self) -> (Mac, Data) {
269 (self.tag, self.data)
270 }
271}
272
273impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
274 DryocSecretBox<Mac, Data>
275{
276 pub fn decrypt<
284 Output: ResizableBytes + NewBytes,
285 Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
286 SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
287 >(
288 &self,
289 nonce: &Nonce,
290 secret_key: &SecretKey,
291 ) -> Result<Output, Error> {
292 use crate::classic::crypto_secretbox::crypto_secretbox_open_detached;
293
294 let mut message = Output::new_bytes();
295 message.resize(self.data.as_slice().len(), 0);
296
297 crypto_secretbox_open_detached(
298 message.as_mut_slice(),
299 self.data.as_slice(),
300 self.tag.as_array(),
301 nonce.as_array(),
302 secret_key.as_array(),
303 )?;
304
305 Ok(message)
306 }
307
308 #[must_use]
310 pub fn to_bytes<Bytes: NewBytes + ResizableBytes>(&self) -> Bytes {
311 concat_bytes(self.tag.as_array(), self.data.as_slice())
312 }
313}
314
315#[cfg(feature = "alloc")]
316impl DryocSecretBox<Mac, Vec<u8>> {
317 pub fn encrypt_to_vecbox<
327 Message: Bytes + ?Sized,
328 Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
329 SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
330 >(
331 message: &Message,
332 nonce: &Nonce,
333 secret_key: &SecretKey,
334 ) -> Result<Self, Error> {
335 Self::encrypt(message, nonce, secret_key)
336 }
337
338 pub fn decrypt_to_vec<
345 Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
346 SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
347 >(
348 &self,
349 nonce: &Nonce,
350 secret_key: &SecretKey,
351 ) -> Result<Vec<u8>, Error> {
352 self.decrypt(nonce, secret_key)
353 }
354
355 #[must_use]
357 pub fn into_vec(mut self) -> Vec<u8> {
358 self.data
359 .resize(self.data.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
360 self.data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
361 self.data[0..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(self.tag.as_array());
362 self.data
363 }
364}
365
366impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
367 PartialEq<DryocSecretBox<Mac, Data>> for DryocSecretBox<Mac, Data>
368{
369 fn eq(&self, other: &Self) -> bool {
370 ct_eq_bytes(self.tag.as_slice(), other.tag.as_slice())
371 && ct_eq_bytes(self.data.as_slice(), other.data.as_slice())
372 }
373}
374
375#[cfg(all(test, feature = "alloc"))]
376mod tests {
377 use super::*;
378 use crate::LengthConstraint;
379 use crate::test_prelude::*;
380
381 const NACL_KEY: &str = "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389";
384 const NACL_NONCE: &str = "69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37";
385 const NACL_MESSAGE: &str = concat!(
386 "be075fc53c81f2d5cf141316ebeb0c7b5228c52a4c62cbd44b66849b64244ffce5ecbaaf33bd751a1ac728d4",
387 "5e6c61296cdc3c01233561f41db66cce314adb310e3be8250c46f06dceea3a7fa1348057e2f6556ad6b1318a",
388 "024a838f21af1fde048977eb48f59ffd4924ca1c60902e52f0a089bc76897040e082f937763848645e0705",
389 );
390 const NACL_BOXED: &str = concat!(
391 "f3ffc7703f9400e52a7dfb4b3d3305d98e993b9f48681273c29650ba32fc76ce48332ea7164d96a4476fb8c5",
392 "31a1186ac0dfc17c98dce87b4da7f011ec48c97271d2c20f9b928fe2270d6fb863d51738b48eeee314a7cc8a",
393 "b932164548e526ae90224368517acfeabd6bb3732bc0e9da99832b61ca01b6de56244a9e88d5f9b37973f622",
394 "a43d14a6599b1f654cb45a74e355a5",
395 );
396
397 fn nacl_vector() -> (Key, Nonce, Vec<u8>, Vec<u8>) {
398 let key = Key::try_from(hex::decode(NACL_KEY).expect("key hex").as_slice()).expect("key");
399 let nonce =
400 Nonce::try_from(hex::decode(NACL_NONCE).expect("nonce hex").as_slice()).expect("nonce");
401 (
402 key,
403 nonce,
404 hex::decode(NACL_MESSAGE).expect("message hex"),
405 hex::decode(NACL_BOXED).expect("boxed hex"),
406 )
407 }
408
409 #[test]
410 fn nacl_vector_encrypts_to_known_bytes_and_parses_back() {
411 let (key, nonce, message, boxed) = nacl_vector();
412
413 let dryocsecretbox =
414 DryocSecretBox::encrypt_to_vecbox(&message, &nonce, &key).expect("encrypt failed");
415 assert_eq!(dryocsecretbox.to_vec(), boxed);
416 assert_eq!(dryocsecretbox.clone().into_vec(), boxed);
417 assert_eq!(
418 dryocsecretbox.tag.as_slice(),
419 &boxed[..CRYPTO_SECRETBOX_MACBYTES]
420 );
421 assert_eq!(dryocsecretbox.data, boxed[CRYPTO_SECRETBOX_MACBYTES..]);
422
423 let parsed = VecBox::from_bytes(&boxed).expect("known-good box should parse");
424 assert_eq!(parsed, dryocsecretbox);
425 assert_eq!(
426 parsed.decrypt_to_vec(&nonce, &key).expect("decrypt failed"),
427 message
428 );
429
430 let (tag, data) = boxed.split_at(CRYPTO_SECRETBOX_MACBYTES);
431 let rebuilt: VecBox =
432 DryocSecretBox::from_parts(Mac::try_from(tag).expect("mac"), data.to_vec());
433 assert_eq!(rebuilt, dryocsecretbox);
434 let (rebuilt_tag, rebuilt_data) = rebuilt.into_parts();
435 assert_eq!(
436 VecBox::from_parts(rebuilt_tag, rebuilt_data).to_vec(),
437 boxed
438 );
439 }
440
441 #[test]
442 fn from_bytes_requires_a_full_tag() {
443 for len in 0..CRYPTO_SECRETBOX_MACBYTES {
444 assert!(matches!(
445 VecBox::from_bytes(&vec![0u8; len]),
446 Err(Error::InvalidLength {
447 context: ErrorContext::SecretBox,
448 actual,
449 ..
450 }) if actual == len
451 ));
452 }
453 let empty = VecBox::from_bytes(&[0xa5u8; CRYPTO_SECRETBOX_MACBYTES])
454 .expect("a lone tag is an empty box");
455 assert!(empty.data.is_empty());
456 assert_eq!(empty.tag.as_slice(), &[0xa5u8; CRYPTO_SECRETBOX_MACBYTES]);
457 }
458
459 #[test]
460 fn tampering_is_rejected_and_the_box_stays_usable() {
461 let (key, nonce, message, boxed) = nacl_vector();
462 let dryocsecretbox = VecBox::from_bytes(&boxed).expect("parse");
463
464 let mut wrong_key = key.clone();
465 wrong_key[0] ^= 1;
466 assert!(matches!(
467 dryocsecretbox.decrypt_to_vec(&nonce, &wrong_key),
468 Err(Error::AuthenticationFailed)
469 ));
470
471 let mut wrong_nonce = nonce.clone();
472 wrong_nonce[CRYPTO_SECRETBOX_NONCEBYTES - 1] ^= 1;
473 assert!(matches!(
474 dryocsecretbox.decrypt_to_vec(&wrong_nonce, &key),
475 Err(Error::AuthenticationFailed)
476 ));
477
478 for index in [
479 0,
480 CRYPTO_SECRETBOX_MACBYTES - 1,
481 CRYPTO_SECRETBOX_MACBYTES,
482 boxed.len() - 1,
483 ] {
484 let mut tampered = boxed.clone();
485 tampered[index] ^= 0x80;
486 let tampered = VecBox::from_bytes(&tampered).expect("parse");
487 assert!(matches!(
488 tampered.decrypt_to_vec(&nonce, &key),
489 Err(Error::AuthenticationFailed)
490 ));
491 }
492
493 let truncated = VecBox::from_bytes(&boxed[..boxed.len() - 1]).expect("parse");
494 assert!(truncated.decrypt_to_vec(&nonce, &key).is_err());
495
496 assert_eq!(dryocsecretbox.to_vec(), boxed);
498 assert_eq!(
499 dryocsecretbox
500 .decrypt_to_vec(&nonce, &key)
501 .expect("decrypt"),
502 message
503 );
504 }
505
506 #[test]
507 fn empty_message_produces_a_bare_tag_that_authenticates() {
508 let (key, nonce, _, _) = nacl_vector();
509 let empty = DryocSecretBox::encrypt_to_vecbox(&[], &nonce, &key).expect("encrypt failed");
510 let bytes = empty.to_vec();
511 assert_eq!(bytes.len(), CRYPTO_SECRETBOX_MACBYTES);
512
513 let parsed = VecBox::from_bytes(&bytes).expect("parse");
514 assert!(
515 parsed
516 .decrypt_to_vec(&nonce, &key)
517 .expect("decrypt")
518 .is_empty()
519 );
520 let mut wrong_key = key.clone();
521 wrong_key[0] ^= 1;
522 assert!(parsed.decrypt_to_vec(&nonce, &wrong_key).is_err());
523 }
524
525 #[derive(Zeroize)]
528 struct FixedData([u8; 4]);
529
530 impl Bytes for FixedData {
531 fn as_slice(&self) -> &[u8] {
532 &self.0
533 }
534
535 fn len(&self) -> usize {
536 self.0.len()
537 }
538
539 fn is_empty(&self) -> bool {
540 self.0.is_empty()
541 }
542 }
543
544 impl MutBytes for FixedData {
545 fn as_mut_slice(&mut self) -> &mut [u8] {
546 &mut self.0
547 }
548
549 fn copy_from_slice(&mut self, other: &[u8]) {
550 self.0.copy_from_slice(other)
551 }
552 }
553
554 impl NewBytes for FixedData {
555 fn new_bytes() -> Self {
556 Self([0; 4])
557 }
558 }
559
560 impl ResizableBytes for FixedData {
561 fn resize(&mut self, _new_len: usize, _value: u8) {}
562 }
563
564 #[test]
565 fn encrypt_returns_an_error_when_storage_does_not_grow() {
566 let (key, nonce, message, _) = nacl_vector();
567
568 assert!(matches!(
569 DryocSecretBox::<Mac, FixedData>::encrypt(&message[..5], &nonce, &key),
570 Err(Error::InvalidLength {
571 context: ErrorContext::Ciphertext,
572 actual: 4,
573 constraint: LengthConstraint::AtLeast(5),
574 })
575 ));
576
577 let fits = DryocSecretBox::<Mac, FixedData>::encrypt(&message[..4], &nonce, &key)
579 .expect("encrypt failed");
580 let expected =
581 DryocSecretBox::encrypt_to_vecbox(&message[..4], &nonce, &key).expect("encrypt failed");
582 assert_eq!(fits.tag, expected.tag);
583 assert_eq!(fits.data.as_slice(), expected.data.as_slice());
584 }
585
586 #[cfg(all(feature = "protected", any(unix, windows)))]
587 #[test]
588 fn locked_box_matches_stack_box_bytes() {
589 use crate::protected::*;
590
591 let (key, nonce, message, boxed) = nacl_vector();
592 let locked_key =
593 protected::Key::from_slice_into_readonly_locked(key.as_slice()).expect("lock key");
594 let locked_nonce = protected::Nonce::from_slice_into_readonly_locked(nonce.as_slice())
595 .expect("lock nonce");
596 let locked_message =
597 HeapBytes::from_slice_into_readonly_locked(&message).expect("lock message");
598
599 let locked: protected::LockedBox =
600 DryocSecretBox::encrypt(&locked_message, &locked_nonce, &locked_key)
601 .expect("encrypt failed");
602 assert_eq!(locked.to_vec(), boxed);
603
604 let decrypted: LockedBytes = locked
605 .decrypt(&locked_nonce, &locked_key)
606 .expect("decrypt failed");
607 assert_eq!(decrypted.as_slice(), message.as_slice());
608
609 let parsed: protected::LockedBox = DryocSecretBox::from_parts(
610 protected::Mac::from_slice_into_locked(&boxed[..CRYPTO_SECRETBOX_MACBYTES])
611 .expect("lock tag"),
612 HeapBytes::from_slice_into_locked(&boxed[CRYPTO_SECRETBOX_MACBYTES..])
613 .expect("lock data"),
614 );
615 let decrypted: Vec<u8> = parsed.decrypt(&nonce, &key).expect("decrypt failed");
616 assert_eq!(decrypted, message);
617 }
618
619 #[cfg(dryoc_native_tests)]
620 mod native_tests {
621 use super::*;
622 use crate::native_test_util as sodium;
623
624 #[test]
625 fn nacl_vector_matches_libsodium() {
626 let (key, nonce, message, boxed) = nacl_vector();
627 let so_ciphertext = sodium::secretbox_easy(&message, &nonce, &key);
628 assert_eq!(so_ciphertext, boxed);
629 }
630
631 #[test]
632 fn libsodium_ciphertext_parses_and_decrypts() {
633 let (key, nonce, message, _) = nacl_vector();
634
635 for len in [0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, message.len()] {
636 let plaintext = &message[..len];
637 let so_ciphertext = sodium::secretbox_easy(plaintext, &nonce, &key);
638
639 let dryocsecretbox =
640 VecBox::from_bytes(&so_ciphertext).expect("sodium box should parse");
641 assert_eq!(
642 dryocsecretbox
643 .decrypt_to_vec(&nonce, &key)
644 .expect("decrypt failed"),
645 plaintext
646 );
647 assert_eq!(dryocsecretbox.to_vec(), so_ciphertext);
648
649 let mut wrong_key = key.clone();
650 wrong_key[len % CRYPTO_SECRETBOX_KEYBYTES] ^= 1;
651 assert!(dryocsecretbox.decrypt_to_vec(&nonce, &wrong_key).is_err());
652 }
653 }
654
655 #[cfg(all(feature = "protected", any(unix, windows)))]
656 #[test]
657 fn libsodium_ciphertext_decrypts_into_locked_box() {
658 use crate::protected::*;
659
660 let (key, nonce, message, boxed) = nacl_vector();
661 let locked: protected::LockedBox = DryocSecretBox::from_parts(
662 protected::Mac::from_slice_into_locked(&boxed[..CRYPTO_SECRETBOX_MACBYTES])
663 .expect("lock tag"),
664 HeapBytes::from_slice_into_locked(&boxed[CRYPTO_SECRETBOX_MACBYTES..])
665 .expect("lock data"),
666 );
667 let decrypted: LockedBytes = locked.decrypt(&nonce, &key).expect("decrypt failed");
668 assert_eq!(decrypted.as_slice(), message.as_slice());
669
670 let so_decrypted = sodium::secretbox_open_easy(&locked.to_vec(), &nonce, &key)
671 .expect("sodium open failed");
672 assert_eq!(so_decrypted, message);
673 }
674
675 #[test]
676 fn test_dryocbox() {
677 for i in 0..20 {
678 use base64::Engine as _;
679 use base64::engine::general_purpose;
680
681 use crate::dryocsecretbox::*;
682
683 let secret_key = Key::generate();
684 let nonce = Nonce::generate();
685 let words = vec!["hello1".to_string(); i];
686 let message = words.join(" :D ").into_bytes();
687 let message_copy = message.clone();
688 let dryocsecretbox: VecBox =
689 DryocSecretBox::encrypt(&message, &nonce, &secret_key).expect("encrypt failed");
690
691 let ciphertext = dryocsecretbox.clone().into_vec();
692 assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
693
694 let ciphertext_copy = ciphertext.clone();
695
696 let so_ciphertext = sodium::secretbox_easy(&message_copy, &nonce, &secret_key);
697 assert_eq!(
698 general_purpose::STANDARD.encode(&ciphertext),
699 general_purpose::STANDARD.encode(&so_ciphertext)
700 );
701
702 let so_decrypted =
703 sodium::secretbox_open_easy(&ciphertext_copy, &nonce, &secret_key)
704 .expect("decrypt failed");
705
706 let m = DryocSecretBox::decrypt::<Vec<u8>, Nonce, Key>(
707 &dryocsecretbox,
708 &nonce,
709 &secret_key,
710 )
711 .expect("decrypt failed");
712 assert_eq!(m, message_copy);
713 assert_eq!(m, so_decrypted);
714 }
715 }
716
717 #[test]
718 fn test_dryocbox_vec() {
719 for i in 0..20 {
720 use base64::Engine as _;
721 use base64::engine::general_purpose;
722
723 use crate::dryocsecretbox::*;
724
725 let secret_key = Key::generate();
726 let nonce = Nonce::generate();
727 let words = vec!["hello1".to_string(); i];
728 let message = words.join(" :D ").into_bytes();
729 let message_copy = message.clone();
730 let dryocsecretbox =
731 DryocSecretBox::encrypt_to_vecbox(&message, &nonce, &secret_key)
732 .expect("encrypt failed");
733
734 let ciphertext = dryocsecretbox.clone().into_vec();
735 assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
736
737 let ciphertext_copy = ciphertext.clone();
738
739 let so_ciphertext = sodium::secretbox_easy(&message_copy, &nonce, &secret_key);
740 assert_eq!(
741 general_purpose::STANDARD.encode(&ciphertext),
742 general_purpose::STANDARD.encode(&so_ciphertext)
743 );
744
745 let so_decrypted =
746 sodium::secretbox_open_easy(&ciphertext_copy, &nonce, &secret_key)
747 .expect("decrypt failed");
748
749 let m = dryocsecretbox
750 .decrypt_to_vec(&nonce, &secret_key)
751 .expect("decrypt failed");
752 assert_eq!(m, message_copy);
753 assert_eq!(m, so_decrypted);
754 }
755 }
756
757 #[cfg(all(feature = "protected", any(unix, windows)))]
758 #[test]
759 fn test_dryocbox_locked() {
760 for i in 0..20 {
761 use base64::Engine as _;
762 use base64::engine::general_purpose;
763
764 use crate::dryocsecretbox::*;
765 use crate::protected::*;
766
767 let secret_key = protected::Key::generate_locked().expect("generate failed");
768 let nonce = protected::Nonce::generate_locked().expect("generate failed");
769 let words = vec!["hello1".to_string(); i];
770 let message = words.join(" :D ");
771 let message_copy = message.clone();
772 let dryocsecretbox: protected::LockedBox =
773 DryocSecretBox::encrypt(message.as_bytes(), &nonce, &secret_key)
774 .expect("encrypt failed");
775
776 let ciphertext = dryocsecretbox.to_vec();
777
778 let ciphertext_copy = ciphertext.clone();
779
780 let so_ciphertext = sodium::secretbox_easy(
781 message_copy.as_bytes(),
782 nonce.as_slice(),
783 secret_key.as_slice(),
784 );
785 assert_eq!(
786 general_purpose::STANDARD.encode(&ciphertext),
787 general_purpose::STANDARD.encode(&so_ciphertext)
788 );
789
790 let so_decrypted = sodium::secretbox_open_easy(
791 &ciphertext_copy,
792 nonce.as_slice(),
793 secret_key.as_slice(),
794 )
795 .expect("decrypt failed");
796
797 let m: LockedBytes = dryocsecretbox
798 .decrypt(&nonce, &secret_key)
799 .expect("decrypt failed");
800
801 assert_eq!(m.as_slice(), message_copy.as_bytes());
802 assert_eq!(m.as_slice(), so_decrypted);
803 }
804 }
805 }
806}