crypto/chacha/
chacha8_poly1305.rs1use crate::{
2 Aead, AeadError, Bytes, Hash, StreamCipher,
3 chacha::{ChaCha, chacha20_poly1305::update_poly1305_padded},
4 poly1305::Poly1305,
5};
6
7#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
35pub struct ChaCha8Poly1305 {
36 key: [u8; 32],
37}
38
39impl ChaCha8Poly1305 {
40 pub fn new(key: &[u8; 32]) -> ChaCha8Poly1305 {
42 return ChaCha8Poly1305 {
43 key: *key,
44 };
45 }
46
47 #[inline]
49 fn poly1305_key_gen(&self, nonce: &[u8; 12]) -> ([u8; 32], ChaCha<8, true>) {
50 let mut cipher = ChaCha::<8, true>::new(&self.key, nonce);
51 cipher.set_counter(0);
52 let mut block = [0u8; 64];
53 cipher.xor_keystream(&mut block);
54 let mut key = [0u8; 32];
55 key.copy_from_slice(&block[..32]);
56 return (key, cipher);
57 }
58}
59
60impl Aead for ChaCha8Poly1305 {
61 const TAG_SIZE: usize = 16;
62 const NONCE_SIZE: usize = 12;
63
64 fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
65 let nonce: &[u8; 12] = nonce.try_into().expect("nonce must be 12 bytes");
66 let (poly1305key, mut cipher) = self.poly1305_key_gen(nonce);
67
68 cipher.set_counter(1);
69 cipher.xor_keystream(in_out);
70
71 let mut mac = Poly1305::new(&poly1305key);
72 update_poly1305_padded(&mut mac, aad);
73 update_poly1305_padded(&mut mac, in_out);
74 mac.update(&(aad.len() as u64).to_le_bytes());
75 mac.update(&(in_out.len() as u64).to_le_bytes());
76 let tag_bytes = mac.finalize();
77
78 let mut tag = Hash(Bytes::<64>::with_length(16));
79 tag.as_mut().copy_from_slice(&tag_bytes);
80 return tag;
81 }
82
83 fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
84 if tag.len() != Self::TAG_SIZE {
85 return Err(AeadError::InvalidCiphertext);
86 }
87 let nonce: &[u8; 12] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
88 let (poly1305key, mut cipher) = self.poly1305_key_gen(nonce);
89
90 let mut mac = Poly1305::new(&poly1305key);
91 update_poly1305_padded(&mut mac, aad);
92 update_poly1305_padded(&mut mac, in_out);
93 mac.update(&(aad.len() as u64).to_le_bytes());
94 mac.update(&(in_out.len() as u64).to_le_bytes());
95 let computed = mac.finalize();
96
97 if !constant_time_eq::constant_time_eq(&computed, tag) {
98 return Err(AeadError::InvalidCiphertext);
99 }
100
101 cipher.set_counter(1);
102 cipher.xor_keystream(in_out);
103
104 return Ok(());
105 }
106}
107
108#[cfg(test)]
109mod test {
110 use super::*;
111
112 #[test]
113 fn aead_roundtrip() {
114 let key = [0x55; 32];
115 let nonce = [0xaa; 12];
116 let aad = b"authenticated but not encrypted";
117 let aead = ChaCha8Poly1305::new(&key);
118 let plaintext = b"hello, world!";
119 let mut buf = plaintext.to_vec();
120 let tag = aead.encrypt_in_place(&mut buf, &nonce, aad);
121 let result = aead.decrypt_in_place(&mut buf, &nonce, aad, tag.as_ref());
122 assert!(result.is_ok());
123 assert_eq!(&buf, plaintext);
124 }
125}