crypto_box/lib.rs
1#![no_std]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
7)]
8#![warn(missing_docs, rust_2018_idioms)]
9
10//! ## Usage
11//!
12#![cfg_attr(all(feature = "getrandom", feature = "std"), doc = "```")]
13#![cfg_attr(not(all(feature = "getrandom", feature = "std")), doc = "```ignore")]
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! use crypto_box::{
16//! aead::{Aead, AeadCore, OsRng},
17//! SalsaBox, PublicKey, SecretKey
18//! };
19//!
20//! //
21//! // Encryption
22//! //
23//!
24//! // Generate a random secret key.
25//! // NOTE: The secret key bytes can be accessed by calling `secret_key.as_bytes()`
26//! let alice_secret_key = SecretKey::generate(&mut OsRng);
27//!
28//! // Get the public key for the secret key we just generated
29//! let alice_public_key_bytes = alice_secret_key.public_key().as_bytes().clone();
30//!
31//! // Obtain your recipient's public key.
32//! let bob_public_key = PublicKey::from([
33//! 0xe8, 0x98, 0xc, 0x86, 0xe0, 0x32, 0xf1, 0xeb,
34//! 0x29, 0x75, 0x5, 0x2e, 0x8d, 0x65, 0xbd, 0xdd,
35//! 0x15, 0xc3, 0xb5, 0x96, 0x41, 0x17, 0x4e, 0xc9,
36//! 0x67, 0x8a, 0x53, 0x78, 0x9d, 0x92, 0xc7, 0x54,
37//! ]);
38//!
39//! // Create a `SalsaBox` by performing Diffie-Hellman key agreement between
40//! // the two keys.
41//! let alice_box = SalsaBox::new(&bob_public_key, &alice_secret_key);
42//!
43//! // Get a random nonce to encrypt the message under
44//! let nonce = SalsaBox::generate_nonce(&mut OsRng);
45//!
46//! // Message to encrypt
47//! let plaintext = b"Top secret message we're encrypting";
48//!
49//! // Encrypt the message using the box
50//! let ciphertext = alice_box.encrypt(&nonce, &plaintext[..])?;
51//!
52//! //
53//! // Decryption
54//! //
55//!
56//! // Either side can encrypt or decrypt messages under the Diffie-Hellman key
57//! // they agree upon. The example below shows Bob's side.
58//! let bob_secret_key = SecretKey::from([
59//! 0xb5, 0x81, 0xfb, 0x5a, 0xe1, 0x82, 0xa1, 0x6f,
60//! 0x60, 0x3f, 0x39, 0x27, 0xd, 0x4e, 0x3b, 0x95,
61//! 0xbc, 0x0, 0x83, 0x10, 0xb7, 0x27, 0xa1, 0x1d,
62//! 0xd4, 0xe7, 0x84, 0xa0, 0x4, 0x4d, 0x46, 0x1b
63//! ]);
64//!
65//! // Deserialize Alice's public key from bytes
66//! let alice_public_key = PublicKey::from(alice_public_key_bytes);
67//!
68//! // Bob can compute the same `SalsaBox` as Alice by performing the
69//! // key agreement operation.
70//! let bob_box = SalsaBox::new(&alice_public_key, &bob_secret_key);
71//!
72//! // Decrypt the message, using the same randomly generated nonce
73//! let decrypted_plaintext = bob_box.decrypt(&nonce, &ciphertext[..])?;
74//!
75//! assert_eq!(&plaintext[..], &decrypted_plaintext[..]);
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! ## Choosing [`ChaChaBox`] vs [`SalsaBox`]
81//!
82//! The `crypto_box` construction was originally specified using [`SalsaBox`].
83//!
84//! However, the newer [`ChaChaBox`] construction is also available, which
85//! provides better security and performance.
86//!
87//! To use it, enable the `chacha20` feature.
88//!
89#![cfg_attr(
90 all(feature = "chacha20", feature = "getrandom", feature = "std"),
91 doc = "```"
92)]
93#![cfg_attr(
94 not(all(feature = "chacha20", feature = "getrandom", feature = "std")),
95 doc = "```ignore"
96)]
97//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
98//! use crypto_box::{
99//! aead::{Aead, AeadCore, Payload, OsRng},
100//! ChaChaBox, PublicKey, SecretKey
101//! };
102//!
103//! let alice_secret_key = SecretKey::generate(&mut OsRng);
104//! let alice_public_key_bytes = alice_secret_key.public_key().as_bytes().clone();
105//! let bob_public_key = PublicKey::from([
106//! 0xe8, 0x98, 0xc, 0x86, 0xe0, 0x32, 0xf1, 0xeb,
107//! 0x29, 0x75, 0x5, 0x2e, 0x8d, 0x65, 0xbd, 0xdd,
108//! 0x15, 0xc3, 0xb5, 0x96, 0x41, 0x17, 0x4e, 0xc9,
109//! 0x67, 0x8a, 0x53, 0x78, 0x9d, 0x92, 0xc7, 0x54,
110//! ]);
111//! let alice_box = ChaChaBox::new(&bob_public_key, &alice_secret_key);
112//! let nonce = ChaChaBox::generate_nonce(&mut OsRng);
113//!
114//! // Message to encrypt
115//! let plaintext = b"Top secret message we're encrypting".as_ref();
116//!
117//! // Encrypt the message using the box
118//! let ciphertext = alice_box.encrypt(&nonce, plaintext).unwrap();
119//!
120//! //
121//! // Decryption
122//! //
123//!
124//! let bob_secret_key = SecretKey::from([
125//! 0xb5, 0x81, 0xfb, 0x5a, 0xe1, 0x82, 0xa1, 0x6f,
126//! 0x60, 0x3f, 0x39, 0x27, 0xd, 0x4e, 0x3b, 0x95,
127//! 0xbc, 0x0, 0x83, 0x10, 0xb7, 0x27, 0xa1, 0x1d,
128//! 0xd4, 0xe7, 0x84, 0xa0, 0x4, 0x4d, 0x46, 0x1b
129//! ]);
130//! let alice_public_key = PublicKey::from(alice_public_key_bytes);
131//! let bob_box = ChaChaBox::new(&alice_public_key, &bob_secret_key);
132//!
133//! // Decrypt the message, using the same randomly generated nonce
134//! let decrypted_plaintext = bob_box.decrypt(&nonce, ciphertext.as_slice()).unwrap();
135//!
136//! assert_eq!(&plaintext[..], &decrypted_plaintext[..]);
137//! # Ok(())
138//! # }
139//! ```
140//!
141//! ## In-place Usage (eliminates `alloc` requirement)
142//!
143//! This crate has an optional `alloc` feature which can be disabled in e.g.
144//! microcontroller environments that don't have a heap.
145//!
146//! The [`AeadInPlace::encrypt_in_place`] and [`AeadInPlace::decrypt_in_place`]
147//! methods accept any type that impls the [`aead::Buffer`] trait which
148//! contains the plaintext for encryption or ciphertext for decryption.
149//!
150//! Note that if you enable the `heapless` feature of this crate,
151//! you will receive an impl of `aead::Buffer` for [`heapless::Vec`]
152//! (re-exported from the `aead` crate as `aead::heapless::Vec`),
153//! which can then be passed as the `buffer` parameter to the in-place encrypt
154//! and decrypt methods.
155//!
156//! A `heapless` usage example can be found in the documentation for the
157//! `xsalsa20poly1305` crate:
158//!
159//! <https://docs.rs/xsalsa20poly1305/latest/xsalsa20poly1305/#in-place-usage-eliminates-alloc-requirement>
160//!
161//! [NaCl]: https://nacl.cr.yp.to/
162//! [`crypto_box`]: https://nacl.cr.yp.to/box.html
163//! [X25519]: https://cr.yp.to/ecdh.html
164//! [XSalsa20Poly1305]: https://nacl.cr.yp.to/secretbox.html
165//! [ECIES]: https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme
166//! [`heapless::Vec`]: https://docs.rs/heapless/latest/heapless/struct.Vec.html
167
168#[cfg(feature = "seal")]
169extern crate alloc;
170
171mod public_key;
172mod secret_key;
173
174pub use crate::{public_key::PublicKey, secret_key::SecretKey};
175pub use aead;
176pub use crypto_secretbox::Nonce;
177
178use aead::{
179 consts::{U0, U16, U24, U32, U8},
180 generic_array::GenericArray,
181 AeadCore, AeadInPlace, Buffer, Error, KeyInit,
182};
183use crypto_secretbox::{
184 cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher},
185 Kdf, SecretBox,
186};
187use zeroize::Zeroizing;
188
189#[cfg(feature = "chacha20")]
190use chacha20::ChaCha20Legacy as ChaCha20;
191
192#[cfg(feature = "salsa20")]
193use salsa20::Salsa20;
194
195/// Size of a `crypto_box` public or secret key in bytes.
196pub const KEY_SIZE: usize = 32;
197
198/// Poly1305 tag.
199///
200/// Implemented as an alias for [`GenericArray`].
201pub type Tag = GenericArray<u8, U16>;
202
203/// Size of a Poly1305 tag in bytes.
204#[cfg(feature = "seal")]
205const TAG_SIZE: usize = 16;
206
207#[cfg(feature = "seal")]
208/// Extra bytes for the ciphertext of a `crypto_box_seal` compared to the plaintext
209pub const SEALBYTES: usize = KEY_SIZE + TAG_SIZE;
210
211/// [`CryptoBox`] instantiated with the ChaCha20 stream cipher.
212#[cfg(feature = "chacha20")]
213pub type ChaChaBox = CryptoBox<ChaCha20>;
214
215/// [`CryptoBox`] instantiated with with the Salsa20 stream cipher.
216#[cfg(feature = "salsa20")]
217pub type SalsaBox = CryptoBox<Salsa20>;
218
219/// Public-key encryption scheme based on the [X25519] Elliptic Curve
220/// Diffie-Hellman function and the [crypto_secretbox] authenticated encryption
221/// cipher.
222///
223/// This type impls the [`aead::Aead`] trait, and otherwise functions as a
224/// symmetric Authenticated Encryption with Associated Data (AEAD) cipher
225/// once instantiated.
226///
227/// Note that additional associated data (AAD) is not supported and encryption
228/// operations will return [`aead::Error`] if it is provided as an argument.
229///
230/// [X25519]: https://cr.yp.to/ecdh.html
231/// [crypto_secretbox]: https://github.com/RustCrypto/nacl-compat/tree/master/crypto_secretbox
232#[derive(Clone)]
233pub struct CryptoBox<C> {
234 secretbox: SecretBox<C>,
235}
236
237impl<C> CryptoBox<C> {
238 /// Create a new [`CryptoBox`], performing X25519 Diffie-Hellman to derive
239 /// a shared secret from the provided public and secret keys.
240 pub fn new(public_key: &PublicKey, secret_key: &SecretKey) -> Self
241 where
242 C: Kdf,
243 {
244 let shared_secret = Zeroizing::new(secret_key.scalar * public_key.0);
245
246 // Use HChaCha20 to create a uniformly random key from the shared secret
247 let key = Zeroizing::new(C::kdf(
248 GenericArray::from_slice(&shared_secret.0),
249 &GenericArray::default(),
250 ));
251
252 Self {
253 secretbox: SecretBox::<C>::new(&*key),
254 }
255 }
256}
257
258impl<C> AeadCore for CryptoBox<C> {
259 type NonceSize = U24;
260 type TagSize = U16;
261 type CiphertextOverhead = U0;
262}
263
264impl<C> AeadInPlace for CryptoBox<C>
265where
266 C: Kdf + KeyIvInit + KeySizeUser<KeySize = U32> + IvSizeUser<IvSize = U8> + StreamCipher,
267{
268 fn encrypt_in_place(
269 &self,
270 nonce: &GenericArray<u8, Self::NonceSize>,
271 associated_data: &[u8],
272 buffer: &mut dyn Buffer,
273 ) -> Result<(), Error> {
274 self.secretbox
275 .encrypt_in_place(nonce, associated_data, buffer)
276 }
277
278 fn encrypt_in_place_detached(
279 &self,
280 nonce: &GenericArray<u8, Self::NonceSize>,
281 associated_data: &[u8],
282 buffer: &mut [u8],
283 ) -> Result<Tag, Error> {
284 self.secretbox
285 .encrypt_in_place_detached(nonce, associated_data, buffer)
286 }
287
288 fn decrypt_in_place(
289 &self,
290 nonce: &GenericArray<u8, Self::NonceSize>,
291 associated_data: &[u8],
292 buffer: &mut dyn Buffer,
293 ) -> Result<(), Error> {
294 self.secretbox
295 .decrypt_in_place(nonce, associated_data, buffer)
296 }
297
298 fn decrypt_in_place_detached(
299 &self,
300 nonce: &GenericArray<u8, Self::NonceSize>,
301 associated_data: &[u8],
302 buffer: &mut [u8],
303 tag: &Tag,
304 ) -> Result<(), Error> {
305 self.secretbox
306 .decrypt_in_place_detached(nonce, associated_data, buffer, tag)
307 }
308}
309
310#[cfg(feature = "seal")]
311fn get_seal_nonce(ephemeral_pk: &PublicKey, recipient_pk: &PublicKey) -> Nonce {
312 use blake2::{Blake2b, Digest};
313 let mut hasher = Blake2b::<U24>::new();
314 hasher.update(ephemeral_pk.as_bytes());
315 hasher.update(recipient_pk.as_bytes());
316 hasher.finalize()
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[cfg(feature = "serde")]
324 #[test]
325 fn test_public_key_serialization() {
326 use aead::rand_core::RngCore;
327
328 // Random PK bytes
329 let mut public_key_bytes = [0; 32];
330 let mut rng = rand::thread_rng();
331 rng.fill_bytes(&mut public_key_bytes);
332
333 // Create public key
334 let public_key = PublicKey::from(public_key_bytes);
335
336 // Round-trip serialize with bincode
337 let serialized = bincode::serialize(&public_key).unwrap();
338 let deserialized: PublicKey = bincode::deserialize(&serialized).unwrap();
339 assert_eq!(deserialized, public_key,);
340
341 // Round-trip serialize with rmp (msgpack)
342 let serialized = rmp_serde::to_vec_named(&public_key).unwrap();
343 let deserialized: PublicKey = rmp_serde::from_slice(&serialized).unwrap();
344 assert_eq!(deserialized, public_key,);
345 }
346
347 #[cfg(feature = "serde")]
348 #[test]
349 fn test_secret_key_serialization() {
350 use aead::rand_core::RngCore;
351
352 // Random SK bytes
353 let mut secret_key_bytes = [0; 32];
354 let mut rng = rand::thread_rng();
355 rng.fill_bytes(&mut secret_key_bytes);
356
357 // Create secret key
358 let secret_key = SecretKey::from(secret_key_bytes);
359
360 // Round-trip serialize with bincode
361 let serialized = bincode::serialize(&secret_key).unwrap();
362 let deserialized: SecretKey = bincode::deserialize(&serialized).unwrap();
363 assert_eq!(deserialized.to_bytes(), secret_key.to_bytes());
364
365 // Round-trip serialize with rmp (msgpack)
366 let serialized = rmp_serde::to_vec_named(&secret_key).unwrap();
367 let deserialized: SecretKey = rmp_serde::from_slice(&serialized).unwrap();
368 assert_eq!(deserialized.to_bytes(), secret_key.to_bytes());
369 }
370
371 #[test]
372 fn test_public_key_from_slice() {
373 let array = [0; 40];
374
375 // Returns None for empty array
376 assert!(PublicKey::from_slice(&[]).is_err());
377
378 // Returns None for length <32
379 for i in 1..=31 {
380 assert!(PublicKey::from_slice(&array[..i]).is_err());
381 }
382
383 // Succeeds for length 32
384 assert!(PublicKey::from_slice(&array[..32]).is_ok());
385
386 // Returns None for length >32
387 for i in 33..=40 {
388 assert!(PublicKey::from_slice(&array[..i]).is_err());
389 }
390 }
391}