Skip to main content

dryoc/
dryocsecretbox.rs

1//! # Secret-key authenticated encryption
2//!
3//! [`DryocSecretBox`] provides libsodium-compatible authenticated encryption
4//! with a shared secret key. It uses XSalsa20 to encrypt the message and
5//! Poly1305 to detect tampering.
6//!
7//! Use a [`DryocSecretBox`] when all parties already share a secret key. The
8//! key can be generated directly or derived with [`Kdf`](crate::kdf),
9//! [`Session`](crate::kx), or a password-hashing function such as
10//! [`crypto_pwhash`](crate::classic::crypto_pwhash).
11//!
12//! Anyone who knows the key can create valid messages. In a group, a secretbox
13//! proves that a member created the message, not which member created it.
14//!
15//! Nonces are public, but a nonce must never repeat with the same key. Store
16//! each nonce with its ciphertext, or use a counter that cannot repeat for that
17//! key.
18//!
19//! With the `serde` feature,
20//! [`serde::Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) and
21//! [`serde::Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) are implemented
22//! for [`DryocSecretBox`]. With `wincode_0_6`,
23//! [`wincode::SchemaRead`](https://docs.rs/wincode/0.6/wincode/trait.SchemaRead.html) and
24//! [`wincode::SchemaWrite`](https://docs.rs/wincode/0.6/wincode/trait.SchemaWrite.html) are
25//! implemented for [`VecBox`].
26//!
27//! ## Rustaceous API example
28//!
29//! ```
30//! # #[cfg(feature = "alloc")]
31//! # {
32//! use dryoc::dryocsecretbox::*;
33//! use dryoc::types::*;
34//!
35//! // Generate a random secret key and nonce
36//! let secret_key = Key::generate();
37//! let nonce = Nonce::generate();
38//! let message = b"A message to encrypt";
39//!
40//! // Encrypt the message into a vector-backed box.
41//! let dryocsecretbox =
42//!     DryocSecretBox::encrypt_to_vecbox(message, &nonce, &secret_key).expect("encrypt failed");
43//!
44//! // Serialize the box in libsodium's wire format, then read it back.
45//! let sodium_box = dryocsecretbox.to_vec();
46//! let dryocsecretbox = DryocSecretBox::from_bytes(&sodium_box).expect("unable to load box");
47//!
48//! // Decrypt the box.
49//! let decrypted = dryocsecretbox
50//!     .decrypt_to_vec(&nonce, &secret_key)
51//!     .expect("unable to decrypt");
52//!
53//! assert_eq!(message, decrypted.as_slice());
54//! # }
55//! ```
56//!
57//! ## Additional resources
58//!
59//! * See the [libsodium documentation](https://doc.libsodium.org/secret-key_cryptography/secretbox)
60//!   for more about secret boxes
61//! * For public-key encryption, see [`DryocBox`](crate::dryocbox)
62//! * For encrypted message streams, see [`DryocStream`](crate::dryocstream)
63//! * See the [`protected`] module for an example that stores keys in protected
64//!   memory
65
66#[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
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(
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    //! # Protected memory type aliases for [`DryocSecretBox`]
94    //!
95    //! Type aliases for using [`DryocSecretBox`] with protected memory.
96    //!
97    //! ## Example
98    //!
99    //! ```
100    //! use dryoc::dryocsecretbox::DryocSecretBox;
101    //! use dryoc::dryocsecretbox::protected::*;
102    //!
103    //! // Generate a random secret key, lock it, protect memory as read-only
104    //! let secret_key = Key::generate_readonly_locked().expect("key failed");
105    //!
106    //! // Generate a random secret key, lock it, protect memory as read-only
107    //! let nonce = Nonce::generate_readonly_locked().expect("nonce failed");
108    //!
109    //! // Load a message, lock it, protect memory as read-only
110    //! let message =
111    //!     HeapBytes::from_slice_into_readonly_locked(b"Secret message from the tooth fairy")
112    //!         .expect("message failed");
113    //!
114    //! // Encrypt the message, placing the result into locked memory
115    //! let dryocsecretbox: LockedBox =
116    //!     DryocSecretBox::encrypt(&message, &nonce, &secret_key).expect("encrypt failed");
117    //!
118    //! // Decrypt the message, placing the result into locked memory
119    //! let decrypted: LockedBytes = dryocsecretbox
120    //!     .decrypt(&nonce, &secret_key)
121    //!     .expect("decrypt failed");
122    //!
123    //! assert_eq!(message.as_slice(), decrypted.as_slice());
124    //! ```
125    use super::*;
126    pub use crate::protected::*;
127
128    /// Heap-allocated, page-aligned secret for authenticated secret box, for
129    /// use with protected memory.
130    pub type Key = HeapByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
131    /// Heap-allocated, page-aligned nonce for authenticated secret box, for use
132    /// with protected memory.
133    pub type Nonce = HeapByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
134    /// Heap-allocated, page-aligned secret box message authentication code, for
135    /// use with protected memory.
136    pub type Mac = HeapByteArray<CRYPTO_SECRETBOX_MACBYTES>;
137
138    /// Locked [`DryocSecretBox`], provided as a type alias for convenience.
139    pub type LockedBox = DryocSecretBox<Locked<Mac>, LockedBytes>;
140}
141
142#[derive(Zeroize, Clone, Debug)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144/// An authenticated secret-key encrypted box, compatible with a libsodium box.
145/// Use with either [`VecBox`] or [`protected::LockedBox`] type aliases.
146///
147/// Refer to [crate::dryocsecretbox] for sample usage.
148pub struct DryocSecretBox<
149    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
150    Data: Bytes + Zeroize,
151> {
152    tag: Mac,
153    data: Data,
154}
155
156/// [Vec]-based authenticated secret box.
157#[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    /// Encrypts a message using `secret_key` and returns a new
172    /// [`DryocSecretBox`] with ciphertext and tag.
173    ///
174    /// # Errors
175    ///
176    /// Returns [`Error::InvalidLength`] with [`ErrorContext::Message`] if
177    /// `message` is longer than
178    /// [`CRYPTO_SECRETBOX_MESSAGEBYTES_MAX`](crate::constants::CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
179    /// or with [`ErrorContext::Ciphertext`] if `Data`'s
180    /// [`ResizableBytes::resize`] leaves it shorter than `message` (storage
181    /// that cannot grow to the message length).
182    ///
183    /// # Panics
184    ///
185    /// Panics if allocation or resizing panics.
186    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    /// Initializes a [`DryocSecretBox`] from a slice. Expects the first
222    /// [`CRYPTO_SECRETBOX_MACBYTES`] bytes to contain the message
223    /// authentication tag, with the remaining bytes containing the
224    /// encrypted message.
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if `bytes` is shorter than one authentication tag or
229    /// the tag cannot be converted to `Mac`.
230    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    /// Returns a new box with `tag` and `data`, consuming both.
244    #[must_use]
245    pub fn from_parts(tag: Mac, data: Data) -> Self {
246        Self { tag, data }
247    }
248
249    /// Returns the authentication tag.
250    pub fn tag(&self) -> &Mac {
251        &self.tag
252    }
253
254    /// Returns the ciphertext.
255    pub fn data(&self) -> &Data {
256        &self.data
257    }
258
259    /// Copies `self` into a new [`Vec`].
260    #[cfg(feature = "alloc")]
261    #[must_use]
262    pub fn to_vec(&self) -> Vec<u8> {
263        self.to_bytes()
264    }
265
266    /// Moves the tag and data out of this instance, returning them as a tuple.
267    #[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    /// Decrypts this box using `secret_key`.
277    ///
278    /// # Errors
279    ///
280    /// Returns an error if the output storage is shorter than the ciphertext
281    /// or authentication fails. Authentication fails when the key, nonce, tag,
282    /// or ciphertext does not match the value used during encryption.
283    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    /// Copies `self` into the target. Can be used with protected memory.
309    #[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    /// Encrypts a message using `secret_key` and returns a new
318    /// [`DryocSecretBox`] with ciphertext and tag.
319    ///
320    /// # Errors
321    ///
322    /// Returns [`Error::InvalidLength`] with [`ErrorContext::Message`] if
323    /// `message` is longer than
324    /// [`CRYPTO_SECRETBOX_MESSAGEBYTES_MAX`](crate::constants::CRYPTO_SECRETBOX_MESSAGEBYTES_MAX).
325    /// The [`Vec`] storage always resizes, so this is the only error.
326    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    /// Decrypts this box using `secret_key` and returns the plaintext.
339    ///
340    /// # Errors
341    ///
342    /// Returns an error if authentication fails because the key, nonce, tag,
343    /// or ciphertext does not match.
344    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    /// Consumes this box and returns `tag || ciphertext` as a [`Vec`].
356    #[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    /// NaCl `tests/secretbox.c` vector: `firstkey`, `nonce`, the 131-byte
382    /// message, and the 147-byte `tag || ciphertext` output.
383    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        // Rejections leave the box untouched and decryptable.
497        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    /// Output storage whose `resize` never changes its length, like a
526    /// fixed-capacity buffer supplied through the generic `Data` parameter.
527    #[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        // A message that fits the fixed storage encrypts as with `Vec`.
578        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}