p2panda_encryption/crypto/
aead.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! ChaCha20Poly1305 authenticated encryption with additional data (AEAD).
4//!
5//! <https://www.rfc-editor.org/rfc/rfc7905>
6use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, Key, KeyInit, Nonce};
7use thiserror::Error;
8
9/// 96-bit nonce.
10pub type AeadNonce = [u8; 12];
11
12/// 256-bit secret key.
13pub type AeadKey = [u8; 32];
14
15/// ChaCha20Poly1305 AEAD encryption function.
16#[allow(deprecated, reason = "generic_array < v1.0 is deprecated")]
17pub fn aead_encrypt(
18    key: &AeadKey,
19    plaintext: &[u8],
20    nonce: AeadNonce,
21    aad: Option<&[u8]>,
22) -> Result<Vec<u8>, AeadError> {
23    // Implementation attaches authenticated tag (16 bytes) automatically to the end of ciphertext.
24    let key = Key::from_slice(key);
25    let nonce = Nonce::from_slice(&nonce);
26    let mut ciphertext_with_tag: Vec<u8> = Vec::from(plaintext);
27    let cipher = ChaCha20Poly1305::new(key);
28    cipher
29        .encrypt_in_place(nonce, aad.unwrap_or_default(), &mut ciphertext_with_tag)
30        .map_err(AeadError::Encrypt)?;
31    Ok(ciphertext_with_tag)
32}
33
34/// ChaCha20Poly1305 AEAD decryption function.
35#[allow(deprecated, reason = "generic_array < v1.0 is deprecated")]
36pub fn aead_decrypt(
37    key: &AeadKey,
38    ciphertext_with_tag: &[u8],
39    nonce: AeadNonce,
40    aad: Option<&[u8]>,
41) -> Result<Vec<u8>, AeadError> {
42    let key = Key::from_slice(key);
43    let nonce = Nonce::from_slice(&nonce);
44    let mut plaintext: Vec<u8> = Vec::from(ciphertext_with_tag);
45    let cipher = ChaCha20Poly1305::new(key);
46    cipher
47        .decrypt_in_place(nonce, aad.unwrap_or_default(), &mut plaintext)
48        .map_err(AeadError::Decrypt)?;
49    Ok(plaintext)
50}
51
52#[derive(Debug, Error)]
53pub enum AeadError {
54    #[error("plaintext could not be encrypted with aead: {0}")]
55    Encrypt(chacha20poly1305::Error),
56
57    #[error("ciphertext could not be decrypted with aead: {0}")]
58    Decrypt(chacha20poly1305::Error),
59}
60
61#[cfg(test)]
62mod tests {
63    use crate::crypto::Rng;
64
65    use super::{AeadError, AeadKey, AeadNonce, aead_decrypt, aead_encrypt};
66
67    #[test]
68    fn encrypt_decrypt() {
69        let rng = Rng::from_seed([1; 32]);
70
71        let key: AeadKey = rng.random_array().unwrap();
72        let nonce: AeadNonce = rng.random_array().unwrap();
73
74        let ciphertext = aead_encrypt(&key, b"Hello, Panda!", nonce, None).unwrap();
75        let plaintext = aead_decrypt(&key, &ciphertext, nonce, None).unwrap();
76
77        assert_eq!(plaintext, b"Hello, Panda!");
78    }
79
80    #[test]
81    fn decryption_failed() {
82        let rng = Rng::from_seed([1; 32]);
83
84        let key: AeadKey = rng.random_array().unwrap();
85        let nonce: AeadNonce = rng.random_array().unwrap();
86
87        let ciphertext = aead_encrypt(&key, b"Hello, Panda!", nonce, None).unwrap();
88
89        let invalid_key: AeadKey = rng.random_array().unwrap();
90        let invalid_nonce: AeadNonce = rng.random_array().unwrap();
91
92        // Invalid key.
93        assert!(matches!(
94            aead_decrypt(&invalid_key, &ciphertext, nonce, None),
95            Err(AeadError::Decrypt(chacha20poly1305::Error))
96        ));
97
98        // Invalid nonce.
99        assert!(matches!(
100            aead_decrypt(&key, &ciphertext, invalid_nonce, None),
101            Err(AeadError::Decrypt(chacha20poly1305::Error))
102        ));
103
104        // Invalid additional data.
105        assert!(matches!(
106            aead_decrypt(&key, &ciphertext, nonce, Some(b"invalid aad")),
107            Err(AeadError::Decrypt(chacha20poly1305::Error))
108        ));
109    }
110}