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//! If the `serde` feature is enabled, the [`serde::Deserialize`] and
18//! [`serde::Serialize`] traits will be implemented for [`DryocSecretBox`].
19//!
20//! ## Rustaceous API example
21//!
22//! ```
23//! use dryoc::dryocsecretbox::*;
24//!
25//! // Generate a random secret key and nonce
26//! let secret_key = Key::gen();
27//! let nonce = Nonce::gen();
28//! let message = b"Why hello there, fren";
29//!
30//! // Encrypt `message`, into a Vec-based box
31//! let dryocsecretbox = DryocSecretBox::encrypt_to_vecbox(message, &nonce, &secret_key);
32//!
33//! // Convert into a libsodium-compatible box
34//! let sodium_box = dryocsecretbox.to_vec();
35//!
36//! // Read the same box we just made into a new DryocBox
37//! let dryocsecretbox = DryocSecretBox::from_bytes(&sodium_box).expect("unable to load box");
38//!
39//! // Decrypt the box we previously encrypted,
40//! let decrypted = dryocsecretbox
41//!     .decrypt_to_vec(&nonce, &secret_key)
42//!     .expect("unable to decrypt");
43//!
44//! assert_eq!(message, decrypted.as_slice());
45//! ```
46//!
47//! ## Additional resources
48//!
49//! * See <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretbox>
50//!   for additional details on secret boxes
51//! * For public-key based encryption, see [`DryocBox`](crate::dryocbox)
52//! * For stream encryption, see [`DryocStream`](crate::dryocstream)
53//! * See the [protected] mod for an example using the protected memory features
54//!   with [`DryocSecretBox`]
55
56#[cfg(feature = "serde")]
57use serde::{Deserialize, Serialize};
58use subtle::ConstantTimeEq;
59use zeroize::Zeroize;
60
61use crate::constants::{
62    CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_NONCEBYTES,
63};
64use crate::error::Error;
65pub use crate::types::*;
66
67/// Stack-allocated secret for authenticated secret box.
68pub type Key = StackByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
69/// Stack-allocated nonce for authenticated secret box.
70pub type Nonce = StackByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
71/// Stack-allocated secret box message authentication code.
72pub type Mac = StackByteArray<CRYPTO_SECRETBOX_MACBYTES>;
73
74#[cfg(any(feature = "nightly", all(doc, not(doctest))))]
75#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "nightly")))]
76pub mod protected {
77    //! #  Protected memory type aliases for [`DryocSecretBox`]
78    //!
79    //! This mod provides re-exports of type aliases for protected memory usage
80    //! with [`DryocSecretBox`]. These type aliases are provided for
81    //! convenience.
82    //!
83    //! ## Example
84    //!
85    //! ```
86    //! use dryoc::dryocsecretbox::DryocSecretBox;
87    //! use dryoc::dryocsecretbox::protected::*;
88    //!
89    //! // Generate a random secret key, lock it, protect memory as read-only
90    //! let secret_key = Key::gen_readonly_locked().expect("key failed");
91    //!
92    //! // Generate a random secret key, lock it, protect memory as read-only
93    //! let nonce = Nonce::gen_readonly_locked().expect("nonce failed");
94    //!
95    //! // Load a message, lock it, protect memory as read-only
96    //! let message =
97    //!     HeapBytes::from_slice_into_readonly_locked(b"Secret message from the tooth fairy")
98    //!         .expect("message failed");
99    //!
100    //! // Encrypt the message, placing the result into locked memory
101    //! let dryocsecretbox: LockedBox = DryocSecretBox::encrypt(&message, &nonce, &secret_key);
102    //!
103    //! // Decrypt the message, placing the result into locked memory
104    //! let decrypted: LockedBytes = dryocsecretbox
105    //!     .decrypt(&nonce, &secret_key)
106    //!     .expect("decrypt failed");
107    //!
108    //! assert_eq!(message.as_slice(), decrypted.as_slice());
109    //! ```
110    use super::*;
111    pub use crate::protected::*;
112
113    /// Heap-allocated, page-aligned secret for authenticated secret box, for
114    /// use with protected memory.
115    pub type Key = HeapByteArray<CRYPTO_SECRETBOX_KEYBYTES>;
116    /// Heap-allocated, page-aligned nonce for authenticated secret box, for use
117    /// with protected memory.
118    pub type Nonce = HeapByteArray<CRYPTO_SECRETBOX_NONCEBYTES>;
119    /// Heap-allocated, page-aligned secret box message authentication code, for
120    /// use with protected memory.
121    pub type Mac = HeapByteArray<CRYPTO_SECRETBOX_MACBYTES>;
122
123    /// Locked [`DryocSecretBox`], provided as a type alias for convenience.
124    pub type LockedBox = DryocSecretBox<Locked<Mac>, LockedBytes>;
125}
126
127#[cfg_attr(
128    feature = "serde",
129    derive(Zeroize, Clone, Debug, Serialize, Deserialize)
130)]
131#[cfg_attr(not(feature = "serde"), derive(Zeroize, Clone, Debug))]
132/// An authenticated secret-key encrypted box, compatible with a libsodium box.
133/// Use with either [`VecBox`] or [`protected::LockedBox`] type aliases.
134///
135/// Refer to [crate::dryocsecretbox] for sample usage.
136pub struct DryocSecretBox<
137    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
138    Data: Bytes + Zeroize,
139> {
140    tag: Mac,
141    data: Data,
142}
143
144/// [Vec]-based authenticated secret box.
145pub type VecBox = DryocSecretBox<Mac, Vec<u8>>;
146
147impl<
148    Mac: NewByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
149    Data: NewBytes + ResizableBytes + Zeroize,
150> DryocSecretBox<Mac, Data>
151{
152    /// Encrypts a message using `secret_key`, and returns a new
153    /// [DryocSecretBox] with ciphertext and tag
154    pub fn encrypt<
155        Message: Bytes + ?Sized,
156        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
157        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
158    >(
159        message: &Message,
160        nonce: &Nonce,
161        secret_key: &SecretKey,
162    ) -> Self {
163        use crate::classic::crypto_secretbox::crypto_secretbox_detached;
164
165        let mut new = Self {
166            tag: Mac::new_byte_array(),
167            data: Data::new_bytes(),
168        };
169        new.data.resize(message.len(), 0);
170
171        crypto_secretbox_detached(
172            new.data.as_mut_slice(),
173            new.tag.as_mut_array(),
174            message.as_slice(),
175            nonce.as_array(),
176            secret_key.as_array(),
177        );
178
179        new
180    }
181}
182
183impl<
184    'a,
185    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + std::convert::TryFrom<&'a [u8]> + Zeroize,
186    Data: Bytes + From<&'a [u8]> + Zeroize,
187> DryocSecretBox<Mac, Data>
188{
189    /// Initializes a [`DryocSecretBox`] from a slice. Expects the first
190    /// [`CRYPTO_SECRETBOX_MACBYTES`] bytes to contain the message
191    /// authentication tag, with the remaining bytes containing the
192    /// encrypted message.
193    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
194        if bytes.len() < CRYPTO_SECRETBOX_MACBYTES {
195            Err(dryoc_error!(format!(
196                "bytes of len {} less than expected minimum of {}",
197                bytes.len(),
198                CRYPTO_SECRETBOX_MACBYTES
199            )))
200        } else {
201            let (tag, data) = bytes.split_at(CRYPTO_SECRETBOX_MACBYTES);
202            Ok(Self {
203                tag: Mac::try_from(tag).map_err(|_e| dryoc_error!("invalid tag"))?,
204                data: Data::from(data),
205            })
206        }
207    }
208}
209
210impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
211    DryocSecretBox<Mac, Data>
212{
213    /// Returns a new box with `tag` and `data`, consuming both
214    pub fn from_parts(tag: Mac, data: Data) -> Self {
215        Self { tag, data }
216    }
217
218    /// Copies `self` into a new [`Vec`]
219    pub fn to_vec(&self) -> Vec<u8> {
220        self.to_bytes()
221    }
222
223    /// Moves the tag and data out of this instance, returning them as a tuple.
224    pub fn into_parts(self) -> (Mac, Data) {
225        (self.tag, self.data)
226    }
227}
228
229impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
230    DryocSecretBox<Mac, Data>
231{
232    /// Decrypts `ciphertext` using `secret_key`, returning a new
233    /// [DryocSecretBox] with decrypted message
234    pub fn decrypt<
235        Output: ResizableBytes + NewBytes,
236        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
237        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
238    >(
239        &self,
240        nonce: &Nonce,
241        secret_key: &SecretKey,
242    ) -> Result<Output, Error> {
243        use crate::classic::crypto_secretbox::crypto_secretbox_open_detached;
244
245        let mut message = Output::new_bytes();
246        message.resize(self.data.as_slice().len(), 0);
247
248        crypto_secretbox_open_detached(
249            message.as_mut_slice(),
250            self.tag.as_array(),
251            self.data.as_slice(),
252            nonce.as_array(),
253            secret_key.as_array(),
254        )?;
255
256        Ok(message)
257    }
258
259    /// Copies `self` into the target. Can be used with protected memory.
260    pub fn to_bytes<Bytes: NewBytes + ResizableBytes>(&self) -> Bytes {
261        let mut data = Bytes::new_bytes();
262        data.resize(self.tag.len() + self.data.len(), 0);
263        let s = data.as_mut_slice();
264        s[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(self.tag.as_slice());
265        s[CRYPTO_SECRETBOX_MACBYTES..].copy_from_slice(self.data.as_slice());
266        data
267    }
268}
269
270impl DryocSecretBox<Mac, Vec<u8>> {
271    /// Encrypts a message using `secret_key`, and returns a new
272    /// [DryocSecretBox] with ciphertext and tag
273    pub fn encrypt_to_vecbox<
274        Message: Bytes + ?Sized,
275        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
276        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
277    >(
278        message: &Message,
279        nonce: &Nonce,
280        secret_key: &SecretKey,
281    ) -> Self {
282        Self::encrypt(message, nonce, secret_key)
283    }
284
285    /// Decrypts `ciphertext` using `secret_key`, returning a new
286    /// [DryocSecretBox] with decrypted message
287    pub fn decrypt_to_vec<
288        Nonce: ByteArray<CRYPTO_SECRETBOX_NONCEBYTES>,
289        SecretKey: ByteArray<CRYPTO_SECRETBOX_KEYBYTES>,
290    >(
291        &self,
292        nonce: &Nonce,
293        secret_key: &SecretKey,
294    ) -> Result<Vec<u8>, Error> {
295        self.decrypt(nonce, secret_key)
296    }
297
298    /// Consumes this box and returns it as a Vec
299    pub fn into_vec(mut self) -> Vec<u8> {
300        self.data
301            .resize(self.data.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
302        self.data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
303        self.data[0..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(self.tag.as_array());
304        self.data
305    }
306}
307
308impl<
309    'a,
310    Mac: NewByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
311    Data: NewBytes + ResizableBytes + From<&'a [u8]> + Zeroize,
312> DryocSecretBox<Mac, Data>
313{
314    /// Returns a box with `data` copied from slice `input`.
315    pub fn with_data(input: &'a [u8]) -> Self {
316        Self {
317            tag: Mac::new_byte_array(),
318            data: input.into(),
319        }
320    }
321}
322
323impl<
324    'a,
325    Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize,
326    Data: Bytes + ResizableBytes + From<&'a [u8]> + Zeroize,
327> DryocSecretBox<Mac, Data>
328{
329    /// Returns a new box with `data` and `tag`, with data copied from `input`
330    /// and `tag` consumed.
331    pub fn with_data_and_mac(tag: Mac, input: &'a [u8]) -> Self {
332        Self {
333            tag,
334            data: input.into(),
335        }
336    }
337}
338
339impl<Mac: ByteArray<CRYPTO_SECRETBOX_MACBYTES> + Zeroize, Data: Bytes + Zeroize>
340    PartialEq<DryocSecretBox<Mac, Data>> for DryocSecretBox<Mac, Data>
341{
342    fn eq(&self, other: &Self) -> bool {
343        self.tag.as_slice().ct_eq(other.tag.as_slice()).unwrap_u8() == 1
344            && self
345                .data
346                .as_slice()
347                .ct_eq(other.data.as_slice())
348                .unwrap_u8()
349                == 1
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn test_dryocbox() {
359        for i in 0..20 {
360            use base64::Engine as _;
361            use base64::engine::general_purpose;
362            use sodiumoxide::crypto::secretbox;
363            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
364
365            use crate::dryocsecretbox::*;
366
367            let secret_key = Key::gen();
368            let nonce = Nonce::gen();
369            let words = vec!["hello1".to_string(); i];
370            let message = words.join(" :D ").into_bytes();
371            let message_copy = message.clone();
372            let dryocsecretbox: VecBox = DryocSecretBox::encrypt(&message, &nonce, &secret_key);
373
374            let ciphertext = dryocsecretbox.clone().into_vec();
375            assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
376
377            let ciphertext_copy = ciphertext.clone();
378
379            let so_ciphertext = secretbox::seal(
380                &message_copy,
381                &SONonce::from_slice(&nonce).unwrap(),
382                &SOKey::from_slice(&secret_key).unwrap(),
383            );
384            assert_eq!(
385                general_purpose::STANDARD.encode(&ciphertext),
386                general_purpose::STANDARD.encode(&so_ciphertext)
387            );
388
389            let so_decrypted = secretbox::open(
390                &ciphertext_copy,
391                &SONonce::from_slice(&nonce).unwrap(),
392                &SOKey::from_slice(&secret_key).unwrap(),
393            )
394            .expect("decrypt failed");
395
396            let m = DryocSecretBox::decrypt::<Vec<u8>, Nonce, Key>(
397                &dryocsecretbox,
398                &nonce,
399                &secret_key,
400            )
401            .expect("decrypt failed");
402            assert_eq!(m, message_copy);
403            assert_eq!(m, so_decrypted);
404        }
405    }
406
407    #[test]
408    fn test_dryocbox_vec() {
409        for i in 0..20 {
410            use base64::Engine as _;
411            use base64::engine::general_purpose;
412            use sodiumoxide::crypto::secretbox;
413            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
414
415            use crate::dryocsecretbox::*;
416
417            let secret_key = Key::gen();
418            let nonce = Nonce::gen();
419            let words = vec!["hello1".to_string(); i];
420            let message = words.join(" :D ").into_bytes();
421            let message_copy = message.clone();
422            let dryocsecretbox = DryocSecretBox::encrypt_to_vecbox(&message, &nonce, &secret_key);
423
424            let ciphertext = dryocsecretbox.clone().into_vec();
425            assert_eq!(&ciphertext, &dryocsecretbox.to_vec());
426
427            let ciphertext_copy = ciphertext.clone();
428
429            let so_ciphertext = secretbox::seal(
430                &message_copy,
431                &SONonce::from_slice(&nonce).unwrap(),
432                &SOKey::from_slice(&secret_key).unwrap(),
433            );
434            assert_eq!(
435                general_purpose::STANDARD.encode(&ciphertext),
436                general_purpose::STANDARD.encode(&so_ciphertext)
437            );
438
439            let so_decrypted = secretbox::open(
440                &ciphertext_copy,
441                &SONonce::from_slice(&nonce).unwrap(),
442                &SOKey::from_slice(&secret_key).unwrap(),
443            )
444            .expect("decrypt failed");
445
446            let m = dryocsecretbox
447                .decrypt_to_vec(&nonce, &secret_key)
448                .expect("decrypt failed");
449            assert_eq!(m, message_copy);
450            assert_eq!(m, so_decrypted);
451        }
452    }
453
454    #[test]
455    fn test_copy() {
456        for _ in 0..20 {
457            use std::convert::TryFrom;
458
459            use crate::rng::*;
460
461            let mut data1: Vec<u8> = vec![0u8; 1024];
462            copy_randombytes(data1.as_mut_slice());
463            let data1_copy = data1.clone();
464
465            let dryocsecretbox: VecBox = DryocSecretBox::from_bytes(&data1).expect("ok");
466            assert_eq!(
467                dryocsecretbox.data.as_slice(),
468                &data1_copy[CRYPTO_SECRETBOX_MACBYTES..]
469            );
470            assert_eq!(
471                dryocsecretbox.tag.as_slice(),
472                &data1_copy[..CRYPTO_SECRETBOX_MACBYTES]
473            );
474
475            let data1 = data1_copy.clone();
476            let dryocsecretbox: VecBox = DryocSecretBox::with_data(&data1);
477            assert_eq!(&dryocsecretbox.data, &data1_copy);
478
479            let data1 = data1_copy.clone();
480            let (tag, data) = data1.split_at(CRYPTO_SECRETBOX_MACBYTES);
481            let dryocsecretbox: VecBox =
482                DryocSecretBox::with_data_and_mac(Mac::try_from(tag).expect("mac"), data);
483            assert_eq!(
484                dryocsecretbox.data.as_slice(),
485                &data1_copy[CRYPTO_SECRETBOX_MACBYTES..]
486            );
487            assert_eq!(
488                dryocsecretbox.tag.as_array(),
489                &data1_copy[..CRYPTO_SECRETBOX_MACBYTES]
490            );
491        }
492    }
493
494    #[cfg(any(feature = "nightly", all(doc, not(doctest))))]
495    #[cfg(feature = "nightly")]
496    #[test]
497    fn test_dryocbox_locked() {
498        for i in 0..20 {
499            use base64::Engine as _;
500            use base64::engine::general_purpose;
501            use sodiumoxide::crypto::secretbox;
502            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
503
504            use crate::dryocsecretbox::*;
505            use crate::protected::*;
506
507            let secret_key = protected::Key::gen_locked().expect("gen failed");
508            let nonce = protected::Nonce::gen_locked().expect("gen failed");
509            let words = vec!["hello1".to_string(); i];
510            let message = words.join(" :D ");
511            let message_copy = message.clone();
512            let dryocsecretbox: protected::LockedBox =
513                DryocSecretBox::encrypt(message.as_bytes(), &nonce, &secret_key);
514
515            let ciphertext = dryocsecretbox.to_vec();
516
517            let ciphertext_copy = ciphertext.clone();
518
519            let so_ciphertext = secretbox::seal(
520                message_copy.as_bytes(),
521                &SONonce::from_slice(nonce.as_slice()).unwrap(),
522                &SOKey::from_slice(secret_key.as_slice()).unwrap(),
523            );
524            assert_eq!(
525                general_purpose::STANDARD.encode(&ciphertext),
526                general_purpose::STANDARD.encode(&so_ciphertext)
527            );
528
529            let so_decrypted = secretbox::open(
530                &ciphertext_copy,
531                &SONonce::from_slice(nonce.as_slice()).unwrap(),
532                &SOKey::from_slice(secret_key.as_slice()).unwrap(),
533            )
534            .expect("decrypt failed");
535
536            let m: LockedBytes = dryocsecretbox
537                .decrypt(&nonce, &secret_key)
538                .expect("decrypt failed");
539
540            assert_eq!(m.as_slice(), message_copy.as_bytes());
541            assert_eq!(m.as_slice(), so_decrypted);
542        }
543    }
544}