dup_crypto/
private_message.rs

1//  Copyright (C) 2020  Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Private message encryption/decryption
17//!
18//! ## Encrypt a private message (sender side)
19//!
20//! **Warning**: Take the time to study which is the authentication policy adapted to **your specific use case**.
21//! Choosing an unsuitable authentication policy can be **dramatic for your end users**.
22//! Read the documentation of [AuthenticationPolicy](./enum.AuthenticationPolicy.html).
23//!
24//! ```;
25//! use dup_crypto::keys::{
26//!     KeyPair, PublicKey,
27//!     ed25519::{KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey}
28//! };
29//! use dup_crypto::private_message::{ChaChaRounds, AuthenticationPolicy, METADATA_LEN};
30//! use dup_crypto::seeds::Seed32;
31//!
32//! // Take the time to study which is the authentication policy adapted
33//! // to your specific use case.
34//! // Read `dup_crypto::private_message::AuthenticationPolicy` documentation.
35//! let authentication_policy = AuthenticationPolicy::PrivateAuthentication;
36//!
37//! // Regardless of the authentication policy chosen, the sender's key-pair is required.
38//! let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::new([42u8; 32]));
39//!
40//! // Choose number of chacha rounds.
41//! let chacha_rounds = ChaChaRounds::ChaCha20;
42//!
43//! // Aad value must be known by the software that will decipher the message, it can be the
44//! // name of the service followed by the name of the network (name of the currency for example).
45//! // This field is only used to ensure that there is no interference between different services
46//! // and/or networks.
47//! let aad = b"service name - currency name";
48//!
49//! // Define receiver and message content
50//! // The message must be mutable because for performance reasons the encryption is applied
51//! // directly to the bytes of the message (the message is never copied).
52//! let receiver_public_key = Ed25519PublicKey::from_base58(
53//!     "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7"
54//! ).expect("invalid public key");
55//! let message = b"This is a secret message, which can only be read by the recipient.";
56//!
57//! // It is up to you to create the buffer that will contain the encrypted message.
58//! // This gives you the freedom to choose how to allocate the memory space and in
59//! // which type of "container" to store the bytes of the encrypted message.
60//! // Metadata needed for decryption and authentication will be added to your message,
61//! // you must make sure that your buffer has enough capacity to hold this metadata.
62//! let mut buffer: Vec<u8> = Vec::with_capacity(message.len() + METADATA_LEN);
63//! buffer.extend(&message[..]);
64//!
65//! // Finally, authenticate and encrypt the message.
66//! dup_crypto::private_message::encrypt_private_message(
67//!     aad,
68//!     authentication_policy,
69//!     chacha_rounds,
70//!     &mut buffer,
71//!     &receiver_public_key,
72//!     &sender_key_pair,
73//! )?;
74//!
75//! // Send message to the recipient by any way..
76//!
77//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(())
78//! ```
79//!
80//! ## Decrypt a private message (receiver side)
81//!
82//! ```
83//! use dup_crypto::keys::{KeyPair, ed25519::KeyPairFromSeed32Generator};
84//! use dup_crypto::private_message::{ChaChaRounds, DecryptedMessage};
85//! use dup_crypto::seeds::Seed32;
86//!
87//! let receiver_key_pair = KeyPairFromSeed32Generator::generate(
88//!     Seed32::from_base58("7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ").expect("invalid seed")
89//! );
90//!
91//! let mut encrypted_message = vec![3, 81, 192, 79, 234, // ... several bytes hidden
92//! # 127, 151, 145, 237, 209, 209, 213, 219, 29, 249, 21, 217, 231, 216, 147, 105, 39, 180, 181, 92, 97, 215, 153, 65, 104, 221, 236, 96, 151, 136, 3, 100, 109, 170, 117, 137, 66, 225, 189, 200, 38, 151, 219, 60, 78, 17, 146, 69, 35, 92, 186, 192, 69, 187, 44, 201, 163, 53, 16, 151, 212, 172, 120, 151, 241, 42, 79, 11, 77, 54, 21, 30, 206, 105, 94, 195, 177, 80, 58, 96, 28, 27, 99, 164, 39, 87, 49, 143, 185, 7, 137, 138, 189, 60, 98, 208, 169, 168, 236, 13, 86, 74, 177, 60, 197, 45, 222, 135, 193, 130, 161, 192, 56, 168, 169, 97, 8, 33, 101, 202, 180, 239, 178, 42, 139, 226, 59, 22, 228, 43, 245, 236, 204, 106, 86, 218, 88, 238, 215, 219, 4, 38, 88, 90, 42, 250, 27, 236, 204, 73, 53, 179, 39, 7, 124, 187, 126, 81, 4, 117, 244, 114, 88, 52, 214, 86, 168, 213, 201, 114, 248, 145, 212, 164, 189, 78, 8, 201, 178, 85, 12, 25, 248, 193, 247, 13, 103, 15, 50, 197, 17, 41, 93, 164, 36, 87, 97, 215, 216, 207, 183, 21, 236, 114, 227, 88, 235, 86, 72, 183, 49, 69, 176];
93//!
94//! let DecryptedMessage { message, sender_public_key, signature_opt } =
95//!     dup_crypto::private_message::decrypt_private_message(
96//!         b"service name - currency name",
97//!         ChaChaRounds::ChaCha20,
98//!         &mut encrypted_message,
99//!         &receiver_key_pair,
100//! )?;
101//!
102//! assert_eq!(
103//!     message,
104//!     b"Hello, this is a secret message, which can only be read by the recipient.",
105//! );
106//! assert_eq!{
107//!     "5pFCsihCTDbFaysD6jDhvv7wUcZsSKoGWQ3Lm1QU5Z9t",
108//!     &sender_public_key.to_string(),
109//! }
110//! assert_eq!(
111//!     signature_opt,
112//!     None
113//! );
114//!
115//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(())
116//! ```
117//!
118
119mod authentication;
120
121pub use self::authentication::AuthenticationPolicy;
122
123use self::authentication::{
124    generate_authentication_proof, verify_authentication_proof, write_anthentication_datas,
125};
126use crate::keys::ed25519::{
127    Ed25519KeyPair, KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey, Signature,
128};
129use crate::keys::x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey};
130use crate::keys::{KeyPair, PubKeyFromBytesError};
131use crate::rand::UnspecifiedRandError;
132use crate::seeds::Seed32;
133use chacha20poly1305::aead::{AeadInPlace as _, NewAead};
134use chacha20poly1305::{ChaCha12Poly1305, ChaCha20Poly1305, ChaCha8Poly1305};
135use std::num::NonZeroU32;
136use std::{convert::TryFrom, hint::unreachable_unchecked};
137use zeroize::Zeroize;
138
139type Key = chacha20poly1305::aead::Key<ChaCha20Poly1305>;
140type Nonce =
141    chacha20poly1305::aead::Nonce<chacha20poly1305::aead::generic_array::typenum::consts::U12>;
142type Tag = chacha20poly1305::aead::Tag<chacha20poly1305::aead::generic_array::typenum::consts::U16>;
143
144/// Metadata length
145pub const METADATA_LEN: usize = CLEAR_FOOTER_LEN + AUTHENTICATION_DATAS_LEN;
146
147const AUTHENTICATION_DATAS_LEN: usize = 97;
148const CLEAR_FOOTER_LEN: usize = EPHEMERAL_PUBLIC_KEY_LEN + TAG_LEN;
149const EPHEMERAL_PUBLIC_KEY_LEN: usize = 32;
150const PBKDF2_ITERATIONS: u32 = 3;
151const SENDER_PUBLIC_KEY_LEN: usize = 32;
152const TAG_LEN: usize = 16;
153
154/// Number of ChaCha rounds
155#[derive(Clone, Copy, Debug)]
156pub enum ChaChaRounds {
157    /// 8 rounds
158    ChaCha8,
159    /// 12 rounds
160    ChaCha12,
161    /// 20 rounds
162    ChaCha20,
163}
164
165/// Error at encryption/decryption of a private message
166#[derive(Debug)]
167pub enum PrivateMessageError {
168    /// I/O error
169    IoError(std::io::Error),
170    /// Invalid ephemeral pubkey
171    InvalidEphemeralPubKey(PubKeyFromBytesError),
172    /// Invalid sender pubkey
173    InvalidSenderPubKey(PubKeyFromBytesError),
174    /// Invalid authentication proof : invalid signature
175    InvalidAuthenticationProof,
176    /// Unspecified aead error
177    UnspecifiedAeadError,
178    /// Unspecified rand error
179    UnspecifiedRandError,
180}
181
182impl From<std::io::Error> for PrivateMessageError {
183    fn from(e: std::io::Error) -> Self {
184        PrivateMessageError::IoError(e)
185    }
186}
187
188impl From<UnspecifiedRandError> for PrivateMessageError {
189    fn from(_: UnspecifiedRandError) -> Self {
190        PrivateMessageError::UnspecifiedRandError
191    }
192}
193
194#[derive(Zeroize)]
195#[zeroize(drop)]
196struct SharedSecret([u8; 44]);
197
198impl Default for SharedSecret {
199    fn default() -> Self {
200        SharedSecret([0u8; 44])
201    }
202}
203
204impl AsRef<[u8]> for SharedSecret {
205    fn as_ref(&self) -> &[u8] {
206        &self.0
207    }
208}
209
210impl AsMut<[u8]> for SharedSecret {
211    fn as_mut(&mut self) -> &mut [u8] {
212        &mut self.0
213    }
214}
215
216/// Encrypt private message
217pub fn encrypt_private_message<M>(
218    additionally_authenticated_data: &[u8],
219    authentication_policy: AuthenticationPolicy,
220    chacha_rounds: ChaChaRounds,
221    message: &mut M,
222    receiver_public_key: &Ed25519PublicKey,
223    sender_keypair: &Ed25519KeyPair,
224) -> Result<(), PrivateMessageError>
225where
226    M: AsRef<[u8]> + AsMut<[u8]> + Extend<u8>,
227{
228    // Generate ephemeral ed25519 keypair
229    let ephemeral_keypair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
230
231    // Compute DH exchange (ephemeral_secret_key, receiver_public_key)
232    // and derive symmetric_key and nonce from shared secret
233    let shared_secret = generate_shared_secret(
234        ephemeral_keypair.public_key().datas.as_ref(),
235        ephemeral_keypair.seed(),
236        &receiver_public_key,
237    );
238
239    // Write encrypted footer (=authentication datas)
240    let encrypted_footer = write_anthentication_datas(
241        &sender_keypair.public_key(),
242        generate_authentication_proof(
243            authentication_policy,
244            sender_keypair,
245            receiver_public_key,
246            message.as_ref(),
247        ),
248        authentication_policy,
249    );
250    message.extend(encrypted_footer);
251
252    // Encrypt message
253    let tag = encrypt(
254        additionally_authenticated_data,
255        chacha_rounds,
256        message.as_mut(),
257        shared_secret,
258    )?;
259
260    // write clear footer (tag and ephemeral_public_key)
261    let mut clear_footer = arrayvec::ArrayVec::<u8, 48>::new();
262    clear_footer
263        .try_extend_from_slice(tag.as_ref())
264        .unwrap_or_else(|_| unsafe { unreachable_unchecked() }); // It's safe because the tag is 16 bytes long.
265    clear_footer
266        .try_extend_from_slice(ephemeral_keypair.public_key().datas.as_ref())
267        .unwrap_or_else(|_| unsafe { unreachable_unchecked() }); // It's safe because the public key is 32 bytes long.
268    message.extend(clear_footer.into_iter());
269
270    Ok(())
271}
272
273/// Decrypted message
274pub struct DecryptedMessage<'m> {
275    /// decrypted message content
276    pub message: &'m [u8],
277    /// Sender public key
278    pub sender_public_key: Ed25519PublicKey,
279    /// Optional signature
280    pub signature_opt: Option<Signature>,
281}
282
283/// Decrypt private message.
284/// Return a reference to decrypted bytes and an optional signature.
285/// If the authentication method chosen by the sender is `Signature`,
286/// then the signature is necessarily returned. The signature is returned
287/// to allow subsequent publication of proof that this particular message was sent by the sender.
288pub fn decrypt_private_message<'m>(
289    additionally_authenticated_data: &[u8],
290    chacha_rounds: ChaChaRounds,
291    encrypted_message: &'m mut [u8],
292    receiver_key_pair: &Ed25519KeyPair,
293) -> Result<DecryptedMessage<'m>, PrivateMessageError> {
294    // Read clear footer (tag and ephemeral public key)
295    let len = encrypted_message.len();
296    let clear_footer_begin = len - EPHEMERAL_PUBLIC_KEY_LEN - TAG_LEN;
297    let tag_end = len - EPHEMERAL_PUBLIC_KEY_LEN;
298    let tag = Tag::from_slice(&encrypted_message[clear_footer_begin..tag_end]).to_owned();
299    let sender_ephemeral_public_key =
300        Ed25519PublicKey::try_from(&encrypted_message[(len - EPHEMERAL_PUBLIC_KEY_LEN)..])
301            .map_err(PrivateMessageError::InvalidEphemeralPubKey)?;
302
303    // Compute DH exchange (receiver_secret_key, ephemeral_public_key)
304    // and derive symmetric_key and nonce from shared secret
305    let shared_secret = generate_shared_secret(
306        &sender_ephemeral_public_key.datas.as_ref(),
307        &receiver_key_pair.seed(),
308        &sender_ephemeral_public_key,
309    );
310
311    // Decrypt message
312    decrypt(
313        additionally_authenticated_data,
314        chacha_rounds,
315        &mut encrypted_message[..(len - CLEAR_FOOTER_LEN)],
316        shared_secret,
317        &tag,
318    )?;
319
320    // Verify authentication proof
321    let authent_end = clear_footer_begin;
322    let authent_begin = authent_end - AUTHENTICATION_DATAS_LEN;
323    let (sender_public_key, sig_opt) = verify_authentication_proof(
324        receiver_key_pair,
325        &encrypted_message[..authent_begin],
326        &encrypted_message[authent_begin..authent_end],
327    )?;
328
329    Ok(DecryptedMessage {
330        message: &encrypted_message[..authent_begin],
331        sender_public_key,
332        signature_opt: sig_opt,
333    })
334}
335
336fn generate_shared_secret(
337    ephemeral_public_key: &[u8],
338    exchange_secret_key: &Seed32,
339    exchange_public_key: &Ed25519PublicKey,
340) -> SharedSecret {
341    diffie_hellman(
342        X25519SecretKey::from(exchange_secret_key),
343        X25519PublicKey::from(exchange_public_key),
344        |key_material| derive(key_material, ephemeral_public_key),
345    )
346}
347
348#[cfg(target_arch = "wasm32")]
349#[cfg(not(tarpaulin_include))]
350fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret {
351    let mut shared_secret = SharedSecret::default();
352    let mut hmac = cryptoxide::hmac::Hmac::new(cryptoxide::sha2::Sha512::new(), seed);
353    cryptoxide::pbkdf2::pbkdf2(&mut hmac, salt, PBKDF2_ITERATIONS, shared_secret.as_mut());
354    shared_secret
355}
356#[cfg(not(target_arch = "wasm32"))]
357fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret {
358    let mut shared_secret = SharedSecret::default();
359    ring::pbkdf2::derive(
360        ring::pbkdf2::PBKDF2_HMAC_SHA512,
361        unsafe { NonZeroU32::new_unchecked(PBKDF2_ITERATIONS) },
362        salt,
363        seed,
364        shared_secret.as_mut(),
365    );
366    shared_secret
367}
368
369fn encrypt(
370    associated_data: &[u8],
371    chacha_rounds: ChaChaRounds,
372    message: &mut [u8],
373    shared_secret: SharedSecret,
374) -> Result<Tag, PrivateMessageError> {
375    let symmetric_key = Key::from_slice(&shared_secret.as_ref()[..32]);
376    let nonce = Nonce::from_slice(&shared_secret.as_ref()[32..44]);
377    match chacha_rounds {
378        ChaChaRounds::ChaCha8 => ChaCha8Poly1305::new(symmetric_key)
379            .encrypt_in_place_detached(&nonce, associated_data, message)
380            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
381        ChaChaRounds::ChaCha12 => ChaCha12Poly1305::new(symmetric_key)
382            .encrypt_in_place_detached(&nonce, associated_data, message)
383            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
384        ChaChaRounds::ChaCha20 => ChaCha20Poly1305::new(symmetric_key)
385            .encrypt_in_place_detached(&nonce, associated_data, message)
386            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
387    }
388}
389
390fn decrypt(
391    associated_data: &[u8],
392    chacha_rounds: ChaChaRounds,
393    encrypted_message: &mut [u8],
394    shared_secret: SharedSecret,
395    tag: &Tag,
396) -> Result<(), PrivateMessageError> {
397    let symmetric_key = Key::from_slice(&shared_secret.as_ref()[..32]);
398    let nonce = Nonce::from_slice(&shared_secret.as_ref()[32..44]);
399
400    match chacha_rounds {
401        ChaChaRounds::ChaCha8 => ChaCha8Poly1305::new(symmetric_key)
402            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
403            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
404        ChaChaRounds::ChaCha12 => ChaCha12Poly1305::new(symmetric_key)
405            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
406            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
407        ChaChaRounds::ChaCha20 => ChaCha20Poly1305::new(symmetric_key)
408            .decrypt_in_place_detached(&nonce, associated_data, encrypted_message, tag)
409            .map_err(|_| PrivateMessageError::UnspecifiedAeadError),
410    }
411}
412
413#[cfg(test)]
414mod tests {
415
416    use super::*;
417    use crate::keys::ed25519::KeyPairFromSeed32Generator;
418    use crate::keys::KeyPair;
419    use unwrap::unwrap;
420
421    const AAD: &[u8] = b"service name - currency name";
422    const MESSAGE: &[u8] =
423        b"Hello, this is a secret message, which can only be read by the recipient.";
424
425    #[test]
426    fn encrypt_same_message_must_be_different() -> Result<(), PrivateMessageError> {
427        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
428        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
429
430        let message = MESSAGE;
431
432        let encrypted_message1 =
433            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
434
435        let encrypted_message2 =
436            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
437
438        assert_ne!(encrypted_message1, encrypted_message2);
439        assert_ne!(encrypted_message1[32..37], encrypted_message2[32..37]);
440
441        Ok(())
442    }
443
444    #[test]
445    fn encrypt_then_decrypt_with_invalid_aad() -> Result<(), PrivateMessageError> {
446        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
447        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
448
449        let message = MESSAGE;
450
451        let mut encrypted_message =
452            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
453
454        println!("encrypted message={:?}", encrypted_message);
455
456        match decrypt_private_message(
457            b"invalid aad",
458            ChaChaRounds::ChaCha20,
459            &mut encrypted_message,
460            &receiver_key_pair,
461        ) {
462            Ok(_) => {
463                panic!("Expected error PrivateMessageError::UnspecifiedAeadError, found: Ok(()).")
464            }
465            Err(PrivateMessageError::UnspecifiedAeadError) => Ok(()),
466            Err(e) => panic!(
467                "Expected error PrivateMessageError::UnspecifiedAeadError, found: {:?}.",
468                e
469            ),
470        }
471    }
472
473    #[test]
474    fn encrypt_then_decrypt_with_invalid_algorithm() -> Result<(), PrivateMessageError> {
475        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
476        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
477
478        let message = MESSAGE;
479
480        let mut encrypted_message =
481            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
482
483        println!("encrypted message={:?}", encrypted_message);
484
485        match decrypt_private_message(
486            AAD,
487            ChaChaRounds::ChaCha12,
488            &mut encrypted_message,
489            &receiver_key_pair,
490        ) {
491            Ok(_) => {
492                panic!("Expected error PrivateMessageError::UnspecifiedAeadError, found: Ok(()).")
493            }
494            Err(PrivateMessageError::UnspecifiedAeadError) => Ok(()),
495            Err(e) => panic!(
496                "Expected error PrivateMessageError::UnspecifiedAeadError, found: {:?}.",
497                e
498            ),
499        }
500    }
501
502    #[test]
503    fn encrypt_and_decrypt_ok() -> Result<(), PrivateMessageError> {
504        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
505        let receiver_key_pair = KeyPairFromSeed32Generator::generate(unwrap!(Seed32::from_base58(
506            "7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ"
507        )));
508
509        let message = MESSAGE;
510
511        let mut encrypted_message =
512            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
513
514        println!("encrypted message={:?}", encrypted_message);
515
516        let DecryptedMessage {
517            message: decrypted_message,
518            sender_public_key,
519            signature_opt,
520        } = decrypt_private_message(
521            AAD,
522            ChaChaRounds::ChaCha20,
523            &mut encrypted_message,
524            &receiver_key_pair,
525        )?;
526
527        println!("decrypted message={:?}", decrypted_message);
528
529        assert_eq!(decrypted_message, message);
530        assert_eq!(sender_public_key, sender_key_pair.public_key());
531        assert_eq!(signature_opt, None);
532
533        Ok(())
534    }
535
536    fn test_encrypt(
537        message: &[u8],
538        receiver_public_key: &Ed25519PublicKey,
539        sender_keypair: &Ed25519KeyPair,
540    ) -> Result<Vec<u8>, PrivateMessageError> {
541        let mut encrypted_message = Vec::new();
542        encrypted_message.extend(message);
543
544        encrypt_private_message(
545            AAD,
546            AuthenticationPolicy::PrivateAuthentication,
547            ChaChaRounds::ChaCha20,
548            &mut encrypted_message,
549            receiver_public_key,
550            sender_keypair,
551        )?;
552
553        Ok(encrypted_message)
554    }
555}