ssh_cipher/
chacha20poly1305.rs1pub use chacha20::ChaCha20Legacy as ChaCha20;
4
5use crate::Tag;
6use aead::{
7 AeadCore, Error, KeyInit, KeySizeUser, Result, TagPosition,
8 array::typenum::{U8, U16, U32},
9};
10use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
11use poly1305::Poly1305;
12use subtle::ConstantTimeEq;
13
14#[cfg(feature = "zeroize")]
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17pub type ChaChaKey = chacha20::Key;
19
20pub type ChaChaNonce = chacha20::LegacyNonce;
22
23#[derive(Clone)]
34pub struct ChaCha20Poly1305 {
35 key: ChaChaKey,
36}
37
38impl KeySizeUser for ChaCha20Poly1305 {
39 type KeySize = U32;
40}
41
42impl KeyInit for ChaCha20Poly1305 {
43 #[inline]
44 fn new(key: &ChaChaKey) -> Self {
45 Self { key: *key }
46 }
47}
48
49impl AeadCore for ChaCha20Poly1305 {
50 type NonceSize = U8;
51 type TagSize = U16;
52 const TAG_POSITION: TagPosition = TagPosition::Postfix;
53}
54
55impl ChaCha20Poly1305 {
56 pub fn encrypt(&self, nonce: &ChaChaNonce, buffer: &mut [u8], aad_len: usize) -> Result<Tag> {
71 Cipher::new(&self.key, nonce).encrypt(buffer, aad_len)
72 }
73
74 pub fn decrypt(
88 &self,
89 nonce: &ChaChaNonce,
90 buffer: &mut [u8],
91 tag: Tag,
92 aad_len: usize,
93 ) -> Result<()> {
94 Cipher::new(&self.key, nonce).decrypt(buffer, tag, aad_len)
95 }
96}
97
98impl Drop for ChaCha20Poly1305 {
99 fn drop(&mut self) {
100 #[cfg(feature = "zeroize")]
101 self.key.zeroize();
102 }
103}
104
105#[cfg(feature = "zeroize")]
106impl ZeroizeOnDrop for ChaCha20Poly1305 {}
107
108struct Cipher {
110 cipher: ChaCha20,
111 mac: Poly1305,
112}
113
114impl Cipher {
115 pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self {
117 let mut cipher = ChaCha20::new(key, nonce);
118 let mut poly1305_key = poly1305::Key::default();
119 cipher.apply_keystream(&mut poly1305_key);
120
121 let mac = Poly1305::new(&poly1305_key);
122
123 cipher.seek(64);
125
126 Self { cipher, mac }
127 }
128
129 #[inline]
131 pub fn encrypt(mut self, buffer: &mut [u8], aad_len: usize) -> Result<Tag> {
132 if buffer.len() < aad_len {
133 return Err(Error);
134 }
135
136 self.cipher.apply_keystream(&mut buffer[aad_len..]);
137 Ok(self.mac.compute_unpadded(buffer))
138 }
139
140 #[inline]
143 pub fn decrypt(mut self, buffer: &mut [u8], tag: Tag, aad_len: usize) -> Result<()> {
144 if buffer.len() < aad_len {
145 return Err(Error);
146 }
147
148 let expected_tag = self.mac.compute_unpadded(buffer);
149
150 if expected_tag.ct_eq(&tag).into() {
151 self.cipher.apply_keystream(&mut buffer[aad_len..]);
152 Ok(())
153 } else {
154 Err(Error)
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{ChaCha20Poly1305, KeyInit};
162 use hex_literal::hex;
163
164 #[test]
165 fn test_vector() {
166 let key = hex!("379a8ca9e7e705763633213511e8d92eb148a46f1dd0045ec8164e5d23e456eb");
167 let nonce = hex!("0000000000000003");
168 let aad = hex!("5709db2d");
169 let plaintext = hex!("06050000000c7373682d7573657261757468de5949ab061f");
170 let ciphertext = hex!("6dcfb03be8a55e7f0220465672edd921489ea0171198e8a7");
171 let tag = hex!("3e82fe0a2db7128d58ef8d9047963ca3");
172
173 const AAD_LEN: usize = 4;
174 const PT_LEN: usize = 24;
175 assert_eq!(aad.len(), AAD_LEN);
176 assert_eq!(plaintext.len(), PT_LEN);
177
178 let cipher = ChaCha20Poly1305::new(key.as_ref());
179 let mut buffer = [0u8; AAD_LEN + PT_LEN];
180 let (a, p) = buffer.split_at_mut(AAD_LEN);
181 a.copy_from_slice(&aad);
182 p.copy_from_slice(&plaintext);
183
184 let actual_tag = cipher
185 .encrypt(nonce.as_ref(), &mut buffer, AAD_LEN)
186 .unwrap();
187
188 assert_eq!(&buffer[..AAD_LEN], aad);
189 assert_eq!(&buffer[AAD_LEN..], ciphertext);
190 assert_eq!(actual_tag, tag);
191
192 cipher
193 .decrypt(nonce.as_ref(), &mut buffer, actual_tag, AAD_LEN)
194 .unwrap();
195
196 assert_eq!(&buffer[..AAD_LEN], aad);
197 assert_eq!(&buffer[AAD_LEN..], plaintext);
198 }
199}