1use chacha20poly1305::{
2 aead::{Aead, KeyInit, Payload},
3 XChaCha20Poly1305, XNonce,
4};
5use rand::rngs::OsRng;
6use rand::RngCore;
7use subtle::ConstantTimeEq;
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10use crate::error::Error;
11
12#[derive(Zeroize, ZeroizeOnDrop)]
13pub struct AeadKey(chacha20poly1305::Key);
14
15impl AeadKey {
16 pub fn generate() -> Self {
17 let mut bytes = chacha20poly1305::Key::default();
18 OsRng.fill_bytes(&mut bytes);
19 Self(bytes)
20 }
21
22 pub fn from_bytes(b: &[u8; 32]) -> Self {
23 Self(*chacha20poly1305::Key::from_slice(b))
24 }
25}
26
27impl PartialEq for AeadKey {
28 fn eq(&self, other: &Self) -> bool {
29 self.0.ct_eq(&other.0).into()
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Nonce([u8; 24]);
35
36impl Nonce {
37 pub fn random() -> Self {
38 let mut b = [0u8; 24];
39 OsRng.fill_bytes(&mut b);
40 Self(b)
41 }
42
43 pub fn from_bytes(b: [u8; 24]) -> Self {
44 Self(b)
45 }
46
47 pub fn as_bytes(&self) -> &[u8; 24] {
48 &self.0
49 }
50}
51
52pub fn encrypt(key: &AeadKey, nonce: &Nonce, plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
54 let cipher = XChaCha20Poly1305::new(&key.0);
55 let xnonce = XNonce::from_slice(&nonce.0);
56 cipher
57 .encrypt(
58 xnonce,
59 Payload {
60 msg: plaintext,
61 aad,
62 },
63 )
64 .expect("XChaCha20Poly1305 encrypt: key or nonce size mismatch, this is a bug")
65}
66
67pub fn decrypt(
68 key: &AeadKey,
69 nonce: &Nonce,
70 ciphertext: &[u8],
71 aad: &[u8],
72) -> Result<Vec<u8>, Error> {
73 let cipher = XChaCha20Poly1305::new(&key.0);
74 let xnonce = XNonce::from_slice(&nonce.0);
75 cipher
76 .decrypt(
77 xnonce,
78 Payload {
79 msg: ciphertext,
80 aad,
81 },
82 )
83 .map_err(|_| Error::Decrypt)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
93 fn xchacha20poly1305_test_vector() {
94 let key_bytes: [u8; 32] = [
95 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
96 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
97 0x9c, 0x9d, 0x9e, 0x9f,
98 ];
99 let nonce_bytes: [u8; 24] = [
100 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
101 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
102 ];
103 let plaintext: &[u8] = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
104 let aad: &[u8] = &[
105 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
106 ];
107 let expected_ct: &[u8] = &[
109 0xbd, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, 0x95, 0x76, 0x57, 0x94, 0x93, 0xc0,
110 0xe9, 0x39, 0x57, 0x2a, 0x17, 0x00, 0x25, 0x2b, 0xfa, 0xcc, 0xbe, 0xd2, 0x90, 0x2c,
111 0x21, 0x39, 0x6c, 0xbb, 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, 0x0b, 0xf3,
112 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, 0xae, 0x64, 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16,
113 0xcb, 0x96, 0xb7, 0x2e, 0x12, 0x13, 0xb4, 0x52, 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5,
114 0xd9, 0x45, 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, 0x3f, 0x3f, 0xac, 0x2b,
115 0xc3, 0x69, 0x48, 0x8f, 0x76, 0xb2, 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, 0x21, 0xf9,
116 0x66, 0x4c, 0x97, 0x63, 0x7d, 0xa9, 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, 0x8b, 0x13,
117 0xb5, 0x2e, 0xc0, 0x87, 0x59, 0x24, 0xc1, 0xc7, 0x98, 0x79, 0x47, 0xde, 0xaf, 0xd8,
118 0x78, 0x0a, 0xcf, 0x49,
119 ];
120
121 let key = AeadKey::from_bytes(&key_bytes);
122 let nonce = Nonce::from_bytes(nonce_bytes);
123 let ct = encrypt(&key, &nonce, plaintext, aad);
124 assert_eq!(ct.as_slice(), expected_ct);
125
126 let pt = decrypt(&key, &nonce, &ct, aad).unwrap();
127 assert_eq!(pt, plaintext);
128 }
129
130 #[test]
131 fn tampered_tag_returns_decrypt_error() {
132 let key = AeadKey::generate();
133 let nonce = Nonce::random();
134 let mut ct = encrypt(&key, &nonce, b"secret", b"");
135 let last = ct.len() - 1;
137 ct[last] ^= 0xff;
138 assert!(matches!(
139 decrypt(&key, &nonce, &ct, b""),
140 Err(Error::Decrypt)
141 ));
142 }
143
144 #[test]
145 fn wrong_aad_returns_decrypt_error() {
146 let key = AeadKey::generate();
147 let nonce = Nonce::random();
148 let ct = encrypt(&key, &nonce, b"msg", b"right-aad");
149 assert!(matches!(
150 decrypt(&key, &nonce, &ct, b"wrong-aad"),
151 Err(Error::Decrypt)
152 ));
153 }
154}