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::gen();
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_NONCEBYTES,
37};
38use crate::error::Error;
39use crate::rng::copy_randombytes;
40use crate::types::*;
41
42/// Secret box message authentication code.
43pub type Mac = [u8; CRYPTO_SECRETBOX_MACBYTES];
44/// Nonce for secret key authenticated boxes.
45pub type Nonce = [u8; CRYPTO_SECRETBOX_NONCEBYTES];
46/// Key (or secret) for secret key authenticated boxes.
47pub type Key = [u8; CRYPTO_SECRETBOX_KEYBYTES];
48
49/// In-place variant of [`crypto_secretbox_keygen`]
50pub fn crypto_secretbox_keygen_inplace(key: &mut Key) {
51    copy_randombytes(key)
52}
53
54/// Generates a random key using
55/// [`copy_randombytes`].
56pub fn crypto_secretbox_keygen() -> Key {
57    Key::gen()
58}
59
60/// Detached version of [`crypto_secretbox_easy`].
61///
62/// Compatible with libsodium's `crypto_secretbox_detached`.
63pub fn crypto_secretbox_detached(
64    ciphertext: &mut [u8],
65    mac: &mut Mac,
66    message: &[u8],
67    nonce: &Nonce,
68    key: &Key,
69) {
70    ciphertext[..message.len()].copy_from_slice(message);
71    crypto_secretbox_detached_inplace(ciphertext, mac, nonce, key);
72}
73
74/// Detached version of [`crypto_secretbox_open_easy`].
75///
76/// Compatible with libsodium's `crypto_secretbox_open_detached`.
77pub fn crypto_secretbox_open_detached(
78    message: &mut [u8],
79    mac: &Mac,
80    ciphertext: &[u8],
81    nonce: &Nonce,
82    key: &Key,
83) -> Result<(), Error> {
84    let c_len = ciphertext.len();
85    message[..c_len].copy_from_slice(ciphertext);
86    crypto_secretbox_open_detached_inplace(message, mac, nonce, key)
87}
88
89/// Encrypts `message` with `nonce` and `key`.
90///
91/// Compatible with libsodium's `crypto_secretbox_easy`.
92pub fn crypto_secretbox_easy(
93    ciphertext: &mut [u8],
94    message: &[u8],
95    nonce: &Nonce,
96    key: &Key,
97) -> Result<(), Error> {
98    let mut mac = Mac::default();
99    crypto_secretbox_detached(
100        &mut ciphertext[CRYPTO_SECRETBOX_MACBYTES..],
101        &mut mac,
102        message,
103        nonce,
104        key,
105    );
106
107    ciphertext[..CRYPTO_SECRETBOX_MACBYTES].copy_from_slice(&mac);
108
109    Ok(())
110}
111
112/// Decrypts `ciphertext` with `nonce` and `key`.
113///
114/// Compatible with libsodium's `crypto_secretbox_open_easy`.
115pub fn crypto_secretbox_open_easy(
116    message: &mut [u8],
117    ciphertext: &[u8],
118    nonce: &Nonce,
119    key: &Key,
120) -> Result<(), Error> {
121    if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
122        Err(dryoc_error!(format!(
123            "Impossibly small box ({} < {}",
124            ciphertext.len(),
125            CRYPTO_SECRETBOX_MACBYTES
126        )))
127    } else {
128        let (mac, ciphertext) = ciphertext.split_at(CRYPTO_SECRETBOX_MACBYTES);
129        let mac = ByteArray::as_array(mac);
130        crypto_secretbox_open_detached(message, mac, ciphertext, nonce, key)
131    }
132}
133
134/// Encrypts `message` with `nonce` and `key` in-place, without allocating
135/// additional memory for the ciphertext.
136pub fn crypto_secretbox_easy_inplace(
137    data: &mut [u8],
138    nonce: &Nonce,
139    key: &Key,
140) -> Result<(), Error> {
141    data.rotate_right(CRYPTO_SECRETBOX_MACBYTES);
142    let (mac, data) = data.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
143    let mac = MutByteArray::as_mut_array(mac);
144
145    crypto_secretbox_detached_inplace(data, mac, nonce, key);
146
147    Ok(())
148}
149
150/// Decrypts `ciphertext` with `nonce` and `key` in-place, without allocating
151/// additional memory for the message.
152pub fn crypto_secretbox_open_easy_inplace(
153    ciphertext: &mut [u8],
154    nonce: &Nonce,
155    key: &Key,
156) -> Result<(), Error> {
157    if ciphertext.len() < CRYPTO_SECRETBOX_MACBYTES {
158        Err(dryoc_error!(format!(
159            "Impossibly small box ({} < {}",
160            ciphertext.len(),
161            CRYPTO_SECRETBOX_MACBYTES
162        )))
163    } else {
164        let (mac, data) = ciphertext.split_at_mut(CRYPTO_SECRETBOX_MACBYTES);
165        let mac = ByteArray::as_array(mac);
166
167        crypto_secretbox_open_detached_inplace(data, mac, nonce, key)?;
168
169        ciphertext.rotate_left(CRYPTO_SECRETBOX_MACBYTES);
170
171        Ok(())
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_crypto_secretbox_easy() {
181        for i in 0..20 {
182            use base64::Engine as _;
183            use base64::engine::general_purpose;
184            use sodiumoxide::crypto::secretbox;
185            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
186
187            let key: Key = crypto_secretbox_keygen();
188            let nonce = Nonce::gen();
189
190            let words = vec!["love Doge".to_string(); i];
191            let message = words.join(" <3 ");
192
193            let mut ciphertext = vec![0u8; message.len() + CRYPTO_SECRETBOX_MACBYTES];
194            crypto_secretbox_easy(&mut ciphertext, message.as_bytes(), &nonce, &key)
195                .expect("encrypt failed");
196            let so_ciphertext = secretbox::seal(
197                message.as_bytes(),
198                &SONonce::from_slice(&nonce).unwrap(),
199                &SOKey::from_slice(&key).unwrap(),
200            );
201            assert_eq!(
202                general_purpose::STANDARD.encode(&ciphertext),
203                general_purpose::STANDARD.encode(&so_ciphertext)
204            );
205
206            let mut decrypted = vec![0u8; message.len()];
207            crypto_secretbox_open_easy(&mut decrypted, &ciphertext, &nonce, &key)
208                .expect("decrypt failed");
209            let so_decrypted = secretbox::open(
210                &ciphertext,
211                &SONonce::from_slice(&nonce).unwrap(),
212                &SOKey::from_slice(&key).unwrap(),
213            )
214            .unwrap();
215
216            assert_eq!(decrypted, message.as_bytes());
217            assert_eq!(decrypted, so_decrypted);
218        }
219    }
220
221    #[test]
222    fn test_crypto_secretbox_easy_inplace() {
223        for i in 0..20 {
224            use base64::Engine as _;
225            use base64::engine::general_purpose;
226            use sodiumoxide::crypto::secretbox;
227            use sodiumoxide::crypto::secretbox::{Key as SOKey, Nonce as SONonce};
228
229            let key = crypto_secretbox_keygen();
230            let nonce = Nonce::gen();
231
232            let words = vec!["love Doge".to_string(); i];
233            let message: Vec<u8> = words.join(" <3 ").into();
234            let message_copy = message.clone();
235
236            let mut ciphertext = message.clone();
237            ciphertext.resize(message.len() + CRYPTO_SECRETBOX_MACBYTES, 0);
238            crypto_secretbox_easy_inplace(&mut ciphertext, &nonce, &key).expect("encrypt failed");
239            let so_ciphertext = secretbox::seal(
240                &message_copy,
241                &SONonce::from_slice(&nonce).unwrap(),
242                &SOKey::from_slice(&key).unwrap(),
243            );
244            assert_eq!(
245                general_purpose::STANDARD.encode(&ciphertext),
246                general_purpose::STANDARD.encode(&so_ciphertext)
247            );
248
249            let mut decrypted = ciphertext.clone();
250            crypto_secretbox_open_easy_inplace(&mut decrypted, &nonce, &key)
251                .expect("decrypt failed");
252            decrypted.resize(ciphertext.len() - CRYPTO_SECRETBOX_MACBYTES, 0);
253            let so_decrypted = secretbox::open(
254                &ciphertext,
255                &SONonce::from_slice(&nonce).unwrap(),
256                &SOKey::from_slice(&key).unwrap(),
257            )
258            .expect("decrypt failed");
259
260            assert_eq!(&decrypted, &message_copy);
261            assert_eq!(decrypted, so_decrypted);
262        }
263    }
264}