Skip to main content

dryoc/classic/
crypto_secretbox.rs

1//! # Authenticated encryption functions
2//!
3//! Implements libsodium's secret-key authenticated crypto boxes.
4//!
5//! For details, refer to [libsodium docs](https://libsodium.gitbook.io/doc/secret-key_cryptography/secretbox).
6//!
7//! ## Classic API example
8//!
9//! ```
10//! use dryoc::classic::crypto_secretbox::{
11//!     Key, Nonce, crypto_secretbox_easy, crypto_secretbox_keygen, crypto_secretbox_open_easy,
12//! };
13//! use dryoc::constants::{CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_NONCEBYTES};
14//! use dryoc::rng::randombytes_buf;
15//! use dryoc::types::*;
16//!
17//! let key: Key = crypto_secretbox_keygen();
18//! let nonce = Nonce::generate();
19//!
20//! let message = "I Love Doge!";
21//!
22//! // Encrypt
23//! let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
24//! crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
25//!     .expect("encrypt failed");
26//!
27//! // Decrypt
28//! let mut decrypted = vec![0u8; ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES];
29//! crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key).expect("decrypt failed");
30//!
31//! assert_eq!(decrypted, message.as_bytes());
32//! ```
33
34use crate::classic::crypto_secretbox_impl::*;
35use crate::constants::{
36    CRYPTO_SECRETBOX_KEYBYTES, CRYPTO_SECRETBOX_MACBYTES, CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
37    CRYPTO_SECRETBOX_NONCEBYTES,
38};
39use crate::error::Error;
40use crate::rng::copy_randombytes;
41use crate::types::*;
42
43/// Secret box message authentication code.
44pub type Mac = [u8; CRYPTO_SECRETBOX_MACBYTES];
45/// Nonce for secret key authenticated boxes.
46pub type Nonce = [u8; CRYPTO_SECRETBOX_NONCEBYTES];
47/// Key (or secret) for secret key authenticated boxes.
48pub type Key = [u8; CRYPTO_SECRETBOX_KEYBYTES];
49
50fn validate_message_len(message_len: usize, context: crate::ErrorContext) -> Result<(), Error> {
51    if message_len > CRYPTO_SECRETBOX_MESSAGEBYTES_MAX {
52        Err(length_error!(
53            context,
54            message_len,
55            max CRYPTO_SECRETBOX_MESSAGEBYTES_MAX
56        ))
57    } else {
58        Ok(())
59    }
60}
61
62/// In-place variant of [`crypto_secretbox_keygen`]
63pub fn crypto_secretbox_keygen_inplace(key: &mut Key) {
64    copy_randombytes(key)
65}
66
67/// Generates a random key using
68/// [`copy_randombytes`].
69pub fn crypto_secretbox_keygen() -> Key {
70    Key::generate()
71}
72
73/// Detached version of [`crypto_secretbox_easy`].
74///
75/// Compatible with libsodium's `crypto_secretbox_detached`.
76///
77/// # Errors
78///
79/// Returns an error if `message` is too long or `ciphertext` is shorter than
80/// `message`.
81pub fn crypto_secretbox_detached(
82    ciphertext: &mut [u8],
83    mac: &mut Mac,
84    message: &[u8],
85    nonce: &Nonce,
86    key: &Key,
87) -> Result<(), Error> {
88    validate_message_len(message.len(), crate::ErrorContext::Message)?;
89
90    if ciphertext.len() < message.len() {
91        return Err(
92            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min message.len()),
93        );
94    }
95
96    crypto_secretbox_detached_b2b(&mut ciphertext[..message.len()], mac, message, nonce, key);
97    Ok(())
98}
99
100/// Detached version of [`crypto_secretbox_open_easy`].
101///
102/// Compatible with libsodium's `crypto_secretbox_open_detached`.
103///
104/// # Errors
105///
106/// Returns an error if `ciphertext` is too long, `message` is shorter than
107/// `ciphertext`, or authentication fails.
108pub fn crypto_secretbox_open_detached(
109    message: &mut [u8],
110    mac: &Mac,
111    ciphertext: &[u8],
112    nonce: &Nonce,
113    key: &Key,
114) -> Result<(), Error> {
115    let c_len = ciphertext.len();
116    validate_message_len(c_len, crate::ErrorContext::Ciphertext)?;
117
118    if message.len() < c_len {
119        return Err(length_error!(crate::ErrorContext::Message, message.len(), min c_len));
120    }
121
122    crypto_secretbox_open_detached_b2b(&mut message[..c_len], mac, ciphertext, nonce, key)
123}
124
125/// Encrypts `message` with `nonce` and `key`.
126///
127/// Compatible with libsodium's `crypto_secretbox_easy`.
128///
129/// # Errors
130///
131/// Returns an error if `message` is too long or `ciphertext` is not exactly one
132/// authentication tag longer than `message`.
133pub fn crypto_secretbox_easy(
134    ciphertext: &mut [u8],
135    message: &[u8],
136    nonce: &Nonce,
137    key: &Key,
138) -> Result<(), Error> {
139    validate_message_len(message.len(), crate::ErrorContext::Message)?;
140
141    let expected_len = message.len() + CRYPTO_SECRETBOX_MACBYTES;
142    if ciphertext.len() != expected_len {
143        return Err(
144            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), exact expected_len),
145        );
146    }
147
148    let mut mac = Mac::default();
149    crypto_secretbox_detached(
150        &mut ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
151        &mut mac,
152        message,
153        nonce,
154        key,
155    )?;
156
157    ciphertext[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(&mac);
158
159    Ok(())
160}
161
162/// Decrypts `ciphertext` with `nonce` and `key`.
163///
164/// Compatible with libsodium's `crypto_secretbox_open_easy`.
165///
166/// # Errors
167///
168/// Returns an error if `ciphertext` is shorter than an authentication tag,
169/// `message` has the wrong length, or authentication fails.
170pub fn crypto_secretbox_open_easy(
171    message: &mut [u8],
172    ciphertext: &[u8],
173    nonce: &Nonce,
174    key: &Key,
175) -> Result<(), Error> {
176    if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
177        Err(
178            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_SECRETBOX_MACBYTES),
179        )
180    } else if message.len() != ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES {
181        Err(length_error!(
182            crate::ErrorContext::Message,
183            message.len(),
184            exact ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES
185        ))
186    } else {
187        let (mac, ciphertext) = ciphertext.split_at(CRYPTO_SECRETBOX_MACBYTES);
188        let mac = ByteArray::as_array(mac);
189        crypto_secretbox_open_detached(message, mac, ciphertext, nonce, key)
190    }
191}
192
193/// Encrypts `message` with `nonce` and `key` in-place, without allocating
194/// additional memory for ciphertext.
195///
196/// # Errors
197///
198/// Returns an error if `data` is shorter than an authentication tag.
199pub fn crypto_secretbox_easy_inplace(
200    data: &mut [u8],
201    nonce: &Nonce,
202    key: &Key,
203) -> Result<(), Error> {
204    if data.len() < CRYPTO_SECRETBOX_MACBYTES {
205        return Err(
206            length_error!(crate::ErrorContext::Data, data.len(), min CRYPTO_SECRETBOX_MACBYTES),
207        );
208    }
209    data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
210    let (mac, data) = data.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
211    let mac = MutByteArray::as_mut_array(mac);
212
213    crypto_secretbox_detached_inplace(data, mac, nonce, key);
214
215    Ok(())
216}
217
218/// Decrypts `ciphertext` with `nonce` and `key` in-place, without allocating
219/// additional memory for the message.
220///
221/// # Errors
222///
223/// Returns an error if `ciphertext` is shorter than an authentication tag or
224/// authentication fails.
225pub fn crypto_secretbox_open_easy_inplace(
226    ciphertext: &mut [u8],
227    nonce: &Nonce,
228    key: &Key,
229) -> Result<(), Error> {
230    if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
231        Err(
232            length_error!(crate::ErrorContext::Ciphertext, ciphertext.len(), min CRYPTO_SECRETBOX_MACBYTES),
233        )
234    } else {
235        let (mac, data) = ciphertext.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
236        let mac = ByteArray::as_array(mac);
237
238        crypto_secretbox_open_detached_inplace(data, mac, nonce, key)?;
239
240        ciphertext.rotate_left(CRYPTO_SECRETBOX_MACBYTES);
241
242        Ok(())
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    #[cfg(all(feature = "nightly", dryoc_native_tests))]
249    extern crate test;
250
251    use super::*;
252
253    #[test]
254    fn rejects_lengths_above_the_libsodium_limit() {
255        let too_long = CRYPTO_SECRETBOX_MESSAGEBYTES_MAX + 1;
256
257        assert!(matches!(
258            validate_message_len(too_long, crate::ErrorContext::Message),
259            Err(Error::InvalidLength {
260                context: crate::ErrorContext::Message,
261                actual,
262                constraint: crate::LengthConstraint::AtMost(CRYPTO_SECRETBOX_MESSAGEBYTES_MAX),
263            }) if actual == too_long
264        ));
265    }
266
267    #[test]
268    fn test_crypto_secretbox_rejects_invalid_buffer_lengths_without_mutation() {
269        let key = Key::default();
270        let nonce = Nonce::default();
271        let message = b"buffer length validation";
272
273        let mut short_detached = vec![0xa5; message.len() - 1];
274        let original_short_detached = short_detached.clone();
275        let mut mac = [0x5a; CRYPTO_SECRETBOX_MACBYTES];
276        let original_mac = mac;
277        assert!(
278            crypto_secretbox_detached(&mut short_detached, &mut mac, message, &nonce, &key)
279                .is_err()
280        );
281        assert_eq!(short_detached, original_short_detached);
282        assert_eq!(mac, original_mac);
283
284        for output_len in [
285            message.len() + CRYPTO_SECRETBOX_MACBYTES - 1,
286            message.len() + CRYPTO_SECRETBOX_MACBYTES + 1,
287        ] {
288            let mut output = vec![0xa5; output_len];
289            let original = output.clone();
290            assert!(crypto_secretbox_easy(&mut output, message, &nonce, &key).is_err());
291            assert_eq!(output, original);
292        }
293
294        let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
295        crypto_secretbox_easy(&mut ciphertext, message, &nonce, &key).expect("encrypt failed");
296
297        for output_len in [message.len() - 1, message.len() + 1] {
298            let mut output = vec![0xa5; output_len];
299            let original = output.clone();
300            assert!(crypto_secretbox_open_easy(&mut output, &ciphertext, &nonce, &key).is_err());
301            assert_eq!(output, original);
302        }
303
304        let mut short_open = vec![0xa5; message.len() - 1];
305        let original_short_open = short_open.clone();
306        assert!(
307            crypto_secretbox_open_detached(
308                &mut short_open,
309                ByteArray::as_array(&ciphertext[..CRYPTO_SECRETBOX_MACBYTES]),
310                &ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
311                &nonce,
312                &key,
313            )
314            .is_err()
315        );
316        assert_eq!(short_open, original_short_open);
317
318        let mut too_short_inplace = vec![0xa5; CRYPTO_SECRETBOX_MACBYTES - 1];
319        let original_too_short_inplace = too_short_inplace.clone();
320        assert!(crypto_secretbox_easy_inplace(&mut too_short_inplace, &nonce, &key).is_err());
321        assert_eq!(too_short_inplace, original_too_short_inplace);
322    }
323
324    #[cfg(dryoc_native_tests)]
325    #[test]
326    fn test_crypto_secretbox_easy() {
327        for i in 0..20 {
328            use base64::Engine as _;
329            use base64::engine::general_purpose;
330            use sodiumoxide::crypto::secretbox;
331            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
332
333            let key: Key = crypto_secretbox_keygen();
334            let nonce = Nonce::generate();
335
336            let words = vec!["love Doge".to_string(); i];
337            let message = words.join(" <3 ");
338
339            let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
340            crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
341                .expect("encrypt failed");
342            let so_ciphertext = secretbox::seal(
343                message.as_bytes(),
344                &SONonce::from_slice(&nonce).unwrap(),
345                &SOKey::from_slice(&key).unwrap(),
346            );
347            assert_eq!(
348                general_purpose::STANDARD.encode(&ciphertext),
349                general_purpose::STANDARD.encode(&so_ciphertext)
350            );
351
352            let mut decrypted = vec![0u8; message.len()];
353            crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key)
354                .expect("decrypt failed");
355            let so_decrypted = secretbox::open(
356                &ciphertext,
357                &SONonce::from_slice(&nonce).unwrap(),
358                &SOKey::from_slice(&key).unwrap(),
359            )
360            .unwrap();
361
362            assert_eq!(decrypted, message.as_bytes());
363            assert_eq!(decrypted, so_decrypted);
364        }
365    }
366
367    #[cfg(dryoc_native_tests)]
368    #[test]
369    fn test_crypto_secretbox_easy_inplace() {
370        for i in 0..20 {
371            use base64::Engine as _;
372            use base64::engine::general_purpose;
373            use sodiumoxide::crypto::secretbox;
374            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
375
376            let key = crypto_secretbox_keygen();
377            let nonce = Nonce::generate();
378
379            let words = vec!["love Doge".to_string(); i];
380            let message: Vec<u8> = words.join(" <3 ").into();
381            let message_copy = message.clone();
382
383            let mut ciphertext = message.clone();
384            ciphertext.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
385            crypto_secretbox_easy_inplace(&mut ciphertext, &nonce, &key).expect("encrypt failed");
386            let so_ciphertext = secretbox::seal(
387                &message_copy,
388                &SONonce::from_slice(&nonce).unwrap(),
389                &SOKey::from_slice(&key).unwrap(),
390            );
391            assert_eq!(
392                general_purpose::STANDARD.encode(&ciphertext),
393                general_purpose::STANDARD.encode(&so_ciphertext)
394            );
395
396            let mut decrypted = ciphertext.clone();
397            crypto_secretbox_open_easy_inplace(&mut decrypted, &nonce, &key)
398                .expect("decrypt failed");
399            decrypted.resize(ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES, 0);
400            let so_decrypted = secretbox::open(
401                &ciphertext,
402                &SONonce::from_slice(&nonce).unwrap(),
403                &SOKey::from_slice(&key).unwrap(),
404            )
405            .expect("decrypt failed");
406
407            assert_eq!(&decrypted, &message_copy);
408            assert_eq!(decrypted, so_decrypted);
409        }
410    }
411
412    #[test]
413    fn test_crypto_secretbox_detached_only_touches_message_len() {
414        let key = crypto_secretbox_keygen();
415        let nonce = Nonce::generate();
416        let message = b"detached secretbox buffer prefix";
417        let mut ciphertext = vec![0xa5; message.len() + 8];
418        let mut mac = Mac::default();
419
420        crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
421            .expect("encrypt failed");
422
423        assert_eq!(&ciphertext[message.len()..], &[0xa5; 8]);
424
425        let mut decrypted = vec![0x5a; message.len() + 8];
426        crypto_secretbox_open_detached(
427            &mut decrypted,
428            &mac,
429            &ciphertext[..message.len()],
430            &nonce,
431            &key,
432        )
433        .expect("decrypt failed");
434
435        assert_eq!(&decrypted[..message.len()], message);
436        assert_eq!(&decrypted[message.len()..], &[0x5a; 8]);
437    }
438
439    #[test]
440    fn test_crypto_secretbox_open_failure_keeps_output() {
441        let key = crypto_secretbox_keygen();
442        let nonce = Nonce::generate();
443        let message = b"authenticated plaintext";
444        let mut ciphertext = vec![0u8; message.len()];
445        let mut mac = Mac::default();
446
447        crypto_secretbox_detached(&mut ciphertext, &mut mac, message, &nonce, &key)
448            .expect("encrypt failed");
449        mac[0] ^= 1;
450
451        let mut decrypted = vec![0x5a; message.len()];
452        let original_decrypted = decrypted.clone();
453        assert!(
454            crypto_secretbox_open_detached(&mut decrypted, &mac, &ciphertext, &nonce, &key)
455                .is_err()
456        );
457        assert_eq!(decrypted, original_decrypted);
458
459        let mut inplace = ciphertext.clone();
460        assert!(crypto_secretbox_open_detached_inplace(&mut inplace, &mac, &nonce, &key).is_err());
461        assert_eq!(inplace, ciphertext);
462    }
463
464    #[cfg(all(feature = "nightly", dryoc_native_tests))]
465    fn bench_crypto_secretbox_detached(b: &mut test::Bencher, message_len: usize) {
466        let key: Key = crypto_secretbox_keygen();
467        let nonce = Nonce::generate();
468        let mut message = vec![0u8; message_len];
469        crate::rng::copy_randombytes(&mut message);
470        let mut ciphertext = vec![0u8; message_len];
471        let mut mac = Mac::default();
472
473        b.bytes = message_len as u64;
474        b.iter(|| {
475            crypto_secretbox_detached(
476                test::black_box(&mut ciphertext),
477                test::black_box(&mut mac),
478                test::black_box(&message),
479                test::black_box(&nonce),
480                test::black_box(&key),
481            )
482            .expect("encrypt failed");
483        });
484    }
485
486    #[cfg(all(feature = "nightly", dryoc_native_tests))]
487    #[bench]
488    fn crypto_secretbox_detached_64b_bench(b: &mut test::Bencher) {
489        bench_crypto_secretbox_detached(b, 64);
490    }
491
492    #[cfg(all(feature = "nightly", dryoc_native_tests))]
493    #[bench]
494    fn crypto_secretbox_detached_1kib_bench(b: &mut test::Bencher) {
495        bench_crypto_secretbox_detached(b, 1024);
496    }
497
498    #[cfg(all(feature = "nightly", dryoc_native_tests))]
499    #[bench]
500    fn crypto_secretbox_detached_16kib_bench(b: &mut test::Bencher) {
501        bench_crypto_secretbox_detached(b, 16 * 1024);
502    }
503
504    #[cfg(all(feature = "nightly", dryoc_native_tests))]
505    #[bench]
506    fn crypto_secretbox_detached_1mib_bench(b: &mut test::Bencher) {
507        bench_crypto_secretbox_detached(b, 1024 * 1024);
508    }
509}