Skip to main content

dryoc/
dryocsecretbox.rs

1//! # Secret-key authenticated encryption
2//!
3//! [`DryocSecretBox`] implements libsodium's secret-key authenticated
4//! encryption, also known as a _secretbox_. This implementation uses the
5//! XSalsa20 stream cipher, and Poly1305 for message authentication.
6//!
7//! You should use a [`DryocSecretBox`] when you want to:
8//!
9//! * exchange messages between two or more parties
10//! * use a shared secret, which could be pre-shared, or derived using one or
11//!   more of:
12//!   * [`Kdf`](crate::kdf)
13//!   * [`Kx`](crate::kx)
14//!   * a passphrase with a strong password hashing function, such as
15//!     [`crypto_pwhash`](crate::classic::crypto_pwhash)
16//!
17//! Every holder of the shared key can create valid messages. In a group,
18//! secretbox authenticates membership in the group, not which member sent a
19//! message.
20//!
21//! Secretbox nonces are public, but a nonce must never repeat with the same
22//! key. Store each nonce with its ciphertext or use a counter that cannot
23//! repeat for that key.
24//!
25//! With the `serde` feature,
26//! [`serde::Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) and
27//! [`serde::Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) are implemented
28//! for [`DryocSecretBox`]. With `wincode`,
29//! [`wincode::SchemaRead`](https://docs.rs/wincode/latest/wincode/trait.SchemaRead.html) and
30//! [`wincode::SchemaWrite`](https://docs.rs/wincode/latest/wincode/trait.SchemaWrite.html) are
31//! implemented.
32//!
33//! ## Rustaceous API example
34//!
35//! ```
36//! use dryoc::dryocsecretbox::*;
37//!
38//! // Generate a random secret key and nonce
39//! let secret_key = Key::generate();
40//! let nonce = Nonce::generate();
41//! let message = b"A message to encrypt";
42//!
43//! // Encrypt `message`, into a Vec-based box
44//! let dryocsecretbox = DryocSecretBox::encrypt_to_vecbox(message, &nonce, &secret_key);
45//!
46//! // Convert into a libsodium-compatible box
47//! let sodium_box = dryocsecretbox.to_vec();
48//!
49//! // Read the same box we just made into a new DryocBox
50//! let dryocsecretbox = DryocSecretBox::from_bytes(&sodium_box).expect("unable to load box");
51//!
52//! // Decrypt the box we previously encrypted,
53//! let decrypted = dryocsecretbox
54//!     .decrypt_to_vec(&nonce, &secret_key)
55//!     .expect("unable to decrypt");
56//!
57//! assert_eq!(message, decrypted.as_slice());
58//! ```
59//!
60//! ## Additional resources
61//!
62//! * See <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretbox>
63//!   for additional details on secret boxes
64//! * For public-key based encryption, see [`DryocBox`](crate::dryocbox)
65//! * For stream encryption, see [`DryocStream`](crate::dryocstream)
66//! * See the [protected] mod for an example using the protected memory features
67//!   with [`DryocSecretBox`]
68
69#[cfg(feature = "serde")]
70use serde::{Deserialize, Serialize};
71use subtle::ConstantTimeEq;
72use zeroize::Zeroize;
73
74use crate::constants::{
75    CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_NONCEBYTES,
76};
77use crate::error::Error;
78pub use crate::types::*;
79
80/// Stack-allocated secret for authenticated secret box.
81pub type Key = StackByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
82/// Stack-allocated nonce for authenticated secret box.
83pub type Nonce = StackByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
84/// Stack-allocated secret box message authentication code.
85pub type Mac = StackByteArray<CRYPTO_SECRETBOX_MACBYTES>;
86
87#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
88#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
89pub mod protected {
90    //! # Protected memory type aliases for [`DryocSecretBox`]
91    //!
92    //! Type aliases for using [`DryocSecretBox`] with protected memory.
93    //!
94    //! ## Example
95    //!
96    //! ```
97    //! use dryoc::dryocsecretbox::DryocSecretBox;
98    //! use dryoc::dryocsecretbox::protected::*;
99    //!
100    //! // Generate a random secret key, lock it, protect memory as read-only
101    //! let secret_key = Key::generate_readonly_locked().expect("key failed");
102    //!
103    //! // Generate a random secret key, lock it, protect memory as read-only
104    //! let nonce = Nonce::generate_readonly_locked().expect("nonce failed");
105    //!
106    //! // Load a message, lock it, protect memory as read-only
107    //! let message =
108    //!     HeapBytes::from_slice_into_readonly_locked(b"Secret message from the tooth fairy")
109    //!         .expect("message failed");
110    //!
111    //! // Encrypt the message, placing the result into locked memory
112    //! let dryocsecretbox: LockedBox = DryocSecretBox::encrypt(&message, &nonce, &secret_key);
113    //!
114    //! // Decrypt the message, placing the result into locked memory
115    //! let decrypted: LockedBytes = dryocsecretbox
116    //!     .decrypt(&nonce, &secret_key)
117    //!     .expect("decrypt failed");
118    //!
119    //! assert_eq!(message.as_slice(), decrypted.as_slice());
120    //! ```
121    use super::*;
122    pub use crate::protected::*;
123
124    /// Heap-allocated, page-aligned secret for authenticated secret box, for
125    /// use with protected memory.
126    pub type Key = HeapByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
127    /// Heap-allocated, page-aligned nonce for authenticated secret box, for use
128    /// with protected memory.
129    pub type Nonce = HeapByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
130    /// Heap-allocated, page-aligned secret box message authentication code, for
131    /// use with protected memory.
132    pub type Mac = HeapByteArray<CRYPTO_SECRETBOX_MACBYTES>;
133
134    /// Locked [`DryocSecretBox`], provided as a type alias for convenience.
135    pub type LockedBox = DryocSecretBox<Locked<Mac>, LockedBytes>;
136}
137
138#[cfg_attr(
139    feature = "serde",
140    derive(Zeroize, Clone, Debug, Serialize, Deserialize)
141)]
142#[cfg_attr(not(feature = "serde"), derive(Zeroize, Clone, Debug))]
143/// An authenticated secret-key encrypted box, compatible with a libsodium box.
144/// Use with either [`VecBox`] or [`protected::LockedBox`] type aliases.
145///
146/// Refer to [crate::dryocsecretbox] for sample usage.
147pub struct DryocSecretBox<
148    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
149    Data: Bytes + Zeroize,
150> {
151    tag: Mac,
152    data: Data,
153}
154
155/// [Vec]-based authenticated secret box.
156pub type VecBox = DryocSecretBox<Mac, Vec<u8>>;
157
158#[cfg(feature = "wincode")]
159// SAFETY: The implementation writes exactly the fields used to reconstruct
160// `VecBox` below, using `wincode` schema implementations for each initialized
161// field and preserving their order.
162unsafe impl<C: wincode::config::Config> wincode::SchemaWrite<C> for VecBox {
163    type Src = Self;
164
165    fn size_of(src: &Self::Src) -> wincode::WriteResult<usize> {
166        Ok(
167            <[u8; CRYPTO_SECRETBOX_MACBYTES] as wincode::SchemaWrite<C>>::size_of(
168                src.tag.as_array(),
169            )? + <Vec<u8> as wincode::SchemaWrite<C>>::size_of(&src.data)?,
170        )
171    }
172
173    fn write(mut writer: impl wincode::io::Writer, src: &Self::Src) -> wincode::WriteResult<()> {
174        <[u8; CRYPTO_SECRETBOX_MACBYTES] as wincode::SchemaWrite<C>>::write(
175            writer.by_ref(),
176            src.tag.as_array(),
177        )?;
178        <Vec<u8> as wincode::SchemaWrite<C>>::write(writer, &src.data)
179    }
180}
181
182#[cfg(feature = "wincode")]
183// SAFETY: The implementation fully initializes `dst` with a valid `VecBox`
184// after successfully reading each field in the same order as `SchemaWrite`.
185unsafe impl<'de, C: wincode::config::Config> wincode::SchemaRead<'de, C> for VecBox {
186    type Dst = Self;
187
188    fn read(
189        mut reader: impl wincode::io::Reader<'de>,
190        dst: &mut std::mem::MaybeUninit<Self::Dst>,
191    ) -> wincode::ReadResult<()> {
192        let tag =
193            <[u8; CRYPTO_SECRETBOX_MACBYTES] as wincode::SchemaRead<'de, C>>::get(reader.by_ref())?;
194        let data = <Vec<u8> as wincode::SchemaRead<'de, C>>::get(reader)?;
195        dst.write(Self {
196            tag: tag.into(),
197            data,
198        });
199        Ok(())
200    }
201}
202
203impl<
204    Mac: NewByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
205    Data: NewBytes + ResizableBytes + Zeroize,
206> DryocSecretBox<Mac, Data>
207{
208    /// Encrypts a message using `secret_key` and returns a new
209    /// [`DryocSecretBox`] with ciphertext and tag.
210    ///
211    /// # Panics
212    ///
213    /// Panics if allocation or resizing panics, the message exceeds
214    /// [`CRYPTO_SECRETBOX_MESSAGEBYTES_MAX`](crate::constants::CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
215    /// or a custom `Data` implementation leaves its buffer shorter than the
216    /// message.
217    pub fn encrypt<
218        Message: Bytes + ?Sized,
219        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
220        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
221    >(
222        message: &Message,
223        nonce: &Nonce,
224        secret_key: &SecretKey,
225    ) -> Self {
226        use crate::classic::crypto_secretbox::crypto_secretbox_detached;
227
228        let mut new = Self {
229            tag: Mac::new_byte_array(),
230            data: Data::new_bytes(),
231        };
232        new.data.resize(message.len(), 0);
233
234        crypto_secretbox_detached(
235            new.data.as_mut_slice(),
236            new.tag.as_mut_array(),
237            message.as_slice(),
238            nonce.as_array(),
239            secret_key.as_array(),
240        )
241        .expect("allocated ciphertext length matches message length");
242
243        new
244    }
245}
246
247impl<
248    'a,
249    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
250    Data: Bytes + From<&'a [u8]> + Zeroize,
251> DryocSecretBox<Mac, Data>
252{
253    /// Initializes a [`DryocSecretBox`] from a slice. Expects the first
254    /// [`CRYPTO_SECRETBOX_MACBYTES`] bytes to contain the message
255    /// authentication tag, with the remaining bytes containing the
256    /// encrypted message.
257    ///
258    /// # Errors
259    ///
260    /// Returns an error if `bytes` is shorter than one authentication tag or
261    /// the tag cannot be converted to `Mac`.
262    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
263        if bytes.len() < CRYPTO_SECRETBOX_MACBYTES {
264            Err(
265                length_error!(crate::ErrorContext::SecretBox, bytes.len(), min CRYPTO_SECRETBOX_MACBYTES),
266            )
267        } else {
268            let (tag, data) = bytes.split_at(CRYPTO_SECRETBOX_MACBYTES);
269            Ok(Self {
270                tag: Mac::try_from(tag)
271                    .map_err(|_| Error::invalid_encoding(crate::ErrorContext::AuthenticationTag))?,
272                data: Data::from(data),
273            })
274        }
275    }
276}
277
278impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
279    DryocSecretBox<Mac, Data>
280{
281    /// Returns a new box with `tag` and `data`, consuming both.
282    pub fn from_parts(tag: Mac, data: Data) -> Self {
283        Self { tag, data }
284    }
285
286    /// Copies `self` into a new [`Vec`].
287    pub fn to_vec(&self) -> Vec<u8> {
288        self.to_bytes()
289    }
290
291    /// Moves the tag and data out of this instance, returning them as a tuple.
292    pub fn into_parts(self) -> (Mac, Data) {
293        (self.tag, self.data)
294    }
295}
296
297impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
298    DryocSecretBox<Mac, Data>
299{
300    /// Decrypts this box using `secret_key`.
301    ///
302    /// # Errors
303    ///
304    /// Returns an error if the output storage is shorter than the ciphertext
305    /// or authentication fails. Authentication fails when the key, nonce, tag,
306    /// or ciphertext does not match the value used during encryption.
307    pub fn decrypt<
308        Output: ResizableBytes + NewBytes,
309        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
310        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
311    >(
312        &self,
313        nonce: &Nonce,
314        secret_key: &SecretKey,
315    ) -> Result<Output, Error> {
316        use crate::classic::crypto_secretbox::crypto_secretbox_open_detached;
317
318        let mut message = Output::new_bytes();
319        message.resize(self.data.as_slice().len(), 0);
320
321        crypto_secretbox_open_detached(
322            message.as_mut_slice(),
323            self.tag.as_array(),
324            self.data.as_slice(),
325            nonce.as_array(),
326            secret_key.as_array(),
327        )?;
328
329        Ok(message)
330    }
331
332    /// Copies `self` into the target. Can be used with protected memory.
333    pub fn to_bytes<Bytes: NewBytes + ResizableBytes>(&self) -> Bytes {
334        let mut data = Bytes::new_bytes();
335        data.resize(self.tag.len() + self.data.len(), 0);
336        let s = data.as_mut_slice();
337        s[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(self.tag.as_slice());
338        s[CRYPTO_SECRETBOX_MACBYTES..].copy_from_slice(self.data.as_slice());
339        data
340    }
341}
342
343impl DryocSecretBox<Mac, Vec<u8>> {
344    /// Encrypts a message using `secret_key` and returns a new
345    /// [`DryocSecretBox`] with ciphertext and tag.
346    pub fn encrypt_to_vecbox<
347        Message: Bytes + ?Sized,
348        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
349        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
350    >(
351        message: &Message,
352        nonce: &Nonce,
353        secret_key: &SecretKey,
354    ) -> Self {
355        Self::encrypt(message, nonce, secret_key)
356    }
357
358    /// Decrypts this box using `secret_key` and returns the plaintext.
359    ///
360    /// # Errors
361    ///
362    /// Returns an error if authentication fails because the key, nonce, tag,
363    /// or ciphertext does not match.
364    pub fn decrypt_to_vec<
365        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
366        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
367    >(
368        &self,
369        nonce: &Nonce,
370        secret_key: &SecretKey,
371    ) -> Result<Vec<u8>, Error> {
372        self.decrypt(nonce, secret_key)
373    }
374
375    /// Consumes this box and returns `tag || ciphertext` as a [`Vec`].
376    pub fn into_vec(mut self) -> Vec<u8> {
377        self.data
378            .resize(self.data.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
379        self.data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
380        self.data[0..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(self.tag.as_array());
381        self.data
382    }
383}
384
385impl<
386    'a,
387    Mac: NewByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
388    Data: NewBytes + ResizableBytes + From<&'a [u8]> + Zeroize,
389> DryocSecretBox<Mac, Data>
390{
391    /// Returns a box with `data` copied from slice `input`.
392    pub fn with_data(input: &'a [u8]) -> Self {
393        Self {
394            tag: Mac::new_byte_array(),
395            data: input.into(),
396        }
397    }
398}
399
400impl<
401    'a,
402    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
403    Data: Bytes + ResizableBytes + From<&'a [u8]> + Zeroize,
404> DryocSecretBox<Mac, Data>
405{
406    /// Returns a new box with ciphertext copied from `input` and the supplied
407    /// `tag`.
408    pub fn with_data_and_mac(tag: Mac, input: &'a [u8]) -> Self {
409        Self {
410            tag,
411            data: input.into(),
412        }
413    }
414}
415
416impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
417    PartialEq<DryocSecretBox<Mac, Data>> for DryocSecretBox<Mac, Data>
418{
419    fn eq(&self, other: &Self) -> bool {
420        self.tag.as_slice().ct_eq(other.tag.as_slice()).unwrap_u8() == 1
421            && self
422                .data
423                .as_slice()
424                .ct_eq(other.data.as_slice())
425                .unwrap_u8()
426                == 1
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn test_copy() {
436        for _ in 0..20 {
437            use std::convert::TryFrom;
438
439            use crate::rng::*;
440
441            let mut data1: Vec<u8> = vec![0u8; 1024];
442            copy_randombytes(data1.as_mut_slice());
443            let data1_copy = data1.clone();
444
445            let dryocsecretbox: VecBox = DryocSecretBox::from_bytes(&data1).expect("ok");
446            assert_eq!(
447                dryocsecretbox.data.as_slice(),
448                &data1_copy[CRYPTO_SECRETBOX_MACBYTES..]
449            );
450            assert_eq!(
451                dryocsecretbox.tag.as_slice(),
452                &data1_copy[..CRYPTO_SECRETBOX_MACBYTES]
453            );
454
455            let data1 = data1_copy.clone();
456            let dryocsecretbox: VecBox = DryocSecretBox::with_data(&data1);
457            assert_eq!(&dryocsecretbox.data, &data1_copy);
458
459            let data1 = data1_copy.clone();
460            let (tag, data) = data1.split_at(CRYPTO_SECRETBOX_MACBYTES);
461            let dryocsecretbox: VecBox =
462                DryocSecretBox::with_data_and_mac(Mac::try_from(tag).expect("mac"), data);
463            assert_eq!(
464                dryocsecretbox.data.as_slice(),
465                &data1_copy[CRYPTO_SECRETBOX_MACBYTES..]
466            );
467            assert_eq!(
468                dryocsecretbox.tag.as_array(),
469                &data1_copy[..CRYPTO_SECRETBOX_MACBYTES]
470            );
471        }
472    }
473
474    #[cfg(dryoc_native_tests)]
475    mod native_tests {
476        #[test]
477        fn test_dryocbox() {
478            for i in 0..20 {
479                use base64::Engine as _;
480                use base64::engine::general_purpose;
481                use sodiumoxide::crypto::secretbox;
482                use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
483
484                use crate::dryocsecretbox::*;
485
486                let secret_key = Key::generate();
487                let nonce = Nonce::generate();
488                let words = vec!["hello1".to_string(); i];
489                let message = words.join(" :D ").into_bytes();
490                let message_copy = message.clone();
491                let dryocsecretbox: VecBox = DryocSecretBox::encrypt(&message, &nonce, &secret_key);
492
493                let ciphertext = dryocsecretbox.clone().into_vec();
494                assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
495
496                let ciphertext_copy = ciphertext.clone();
497
498                let so_ciphertext = secretbox::seal(
499                    &message_copy,
500                    &SONonce::from_slice(&nonce).unwrap(),
501                    &SOKey::from_slice(&secret_key).unwrap(),
502                );
503                assert_eq!(
504                    general_purpose::STANDARD.encode(&ciphertext),
505                    general_purpose::STANDARD.encode(&so_ciphertext)
506                );
507
508                let so_decrypted = secretbox::open(
509                    &ciphertext_copy,
510                    &SONonce::from_slice(&nonce).unwrap(),
511                    &SOKey::from_slice(&secret_key).unwrap(),
512                )
513                .expect("decrypt failed");
514
515                let m = DryocSecretBox::decrypt::<Vec<u8>, Nonce, Key>(
516                    &dryocsecretbox,
517                    &nonce,
518                    &secret_key,
519                )
520                .expect("decrypt failed");
521                assert_eq!(m, message_copy);
522                assert_eq!(m, so_decrypted);
523            }
524        }
525
526        #[test]
527        fn test_dryocbox_vec() {
528            for i in 0..20 {
529                use base64::Engine as _;
530                use base64::engine::general_purpose;
531                use sodiumoxide::crypto::secretbox;
532                use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
533
534                use crate::dryocsecretbox::*;
535
536                let secret_key = Key::generate();
537                let nonce = Nonce::generate();
538                let words = vec!["hello1".to_string(); i];
539                let message = words.join(" :D ").into_bytes();
540                let message_copy = message.clone();
541                let dryocsecretbox =
542                    DryocSecretBox::encrypt_to_vecbox(&message, &nonce, &secret_key);
543
544                let ciphertext = dryocsecretbox.clone().into_vec();
545                assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
546
547                let ciphertext_copy = ciphertext.clone();
548
549                let so_ciphertext = secretbox::seal(
550                    &message_copy,
551                    &SONonce::from_slice(&nonce).unwrap(),
552                    &SOKey::from_slice(&secret_key).unwrap(),
553                );
554                assert_eq!(
555                    general_purpose::STANDARD.encode(&ciphertext),
556                    general_purpose::STANDARD.encode(&so_ciphertext)
557                );
558
559                let so_decrypted = secretbox::open(
560                    &ciphertext_copy,
561                    &SONonce::from_slice(&nonce).unwrap(),
562                    &SOKey::from_slice(&secret_key).unwrap(),
563                )
564                .expect("decrypt failed");
565
566                let m = dryocsecretbox
567                    .decrypt_to_vec(&nonce, &secret_key)
568                    .expect("decrypt failed");
569                assert_eq!(m, message_copy);
570                assert_eq!(m, so_decrypted);
571            }
572        }
573
574        #[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
575        #[cfg(all(feature = "protected", any(unix, windows)))]
576        #[test]
577        fn test_dryocbox_locked() {
578            for i in 0..20 {
579                use base64::Engine as _;
580                use base64::engine::general_purpose;
581                use sodiumoxide::crypto::secretbox;
582                use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
583
584                use crate::dryocsecretbox::*;
585                use crate::protected::*;
586
587                let secret_key = protected::Key::generate_locked().expect("generate failed");
588                let nonce = protected::Nonce::generate_locked().expect("generate failed");
589                let words = vec!["hello1".to_string(); i];
590                let message = words.join(" :D ");
591                let message_copy = message.clone();
592                let dryocsecretbox: protected::LockedBox =
593                    DryocSecretBox::encrypt(message.as_bytes(), &nonce, &secret_key);
594
595                let ciphertext = dryocsecretbox.to_vec();
596
597                let ciphertext_copy = ciphertext.clone();
598
599                let so_ciphertext = secretbox::seal(
600                    message_copy.as_bytes(),
601                    &SONonce::from_slice(nonce.as_slice()).unwrap(),
602                    &SOKey::from_slice(secret_key.as_slice()).unwrap(),
603                );
604                assert_eq!(
605                    general_purpose::STANDARD.encode(&ciphertext),
606                    general_purpose::STANDARD.encode(&so_ciphertext)
607                );
608
609                let so_decrypted = secretbox::open(
610                    &ciphertext_copy,
611                    &SONonce::from_slice(nonce.as_slice()).unwrap(),
612                    &SOKey::from_slice(secret_key.as_slice()).unwrap(),
613                )
614                .expect("decrypt failed");
615
616                let m: LockedBytes = dryocsecretbox
617                    .decrypt(&nonce, &secret_key)
618                    .expect("decrypt failed");
619
620                assert_eq!(m.as_slice(), message_copy.as_bytes());
621                assert_eq!(m.as_slice(), so_decrypted);
622            }
623        }
624    }
625}