Skip to main content

chuchi_crypto/cipher/
key.rs

1use super::{Mac, MacNotEqual};
2use crate::xor;
3
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use std::fmt;
7
8use zeroize::Zeroize;
9
10use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
11use chacha20::{Rounds, XChaCha20, hchacha};
12
13use poly1305::Poly1305;
14use universal_hash::{KeyInit, UniversalHash};
15
16// KEY
17
18const BLOCK_SIZE: u64 = 64;
19
20/// Not sure this is the right aproach or we should move to
21/// maybe 12.
22/// The 10 originally came from https://github.com/RustCrypto/nacl-compat/blob/20be967430389e4df9dc0c1c8920eda8681b5dca/crypto_secretstream/src/stream.rs#L50
23#[derive(Debug, Clone, Copy)]
24pub struct R10;
25
26impl Rounds for R10 {
27	const COUNT: usize = 10;
28}
29
30/// A Key that allows to encrypt and decrypt messages.
31pub struct Key {
32	shared_secret: [u8; 32],
33	initial_nonce: [u8; 24],
34	count: u64,
35}
36
37impl Key {
38	/// Creates a new key.  
39	/// And modifying the shared_secret to be a uniformly random key.
40	pub(crate) fn new(
41		shared_secret: [u8; 32],
42		initial_nonce: [u8; 24],
43	) -> Self {
44		// is this really necessary See: https://github.com/RustCrypto/AEADs/pull/295
45		let shared_secret =
46			hchacha::<R10>(&shared_secret.into(), &Default::default()).into();
47
48		Self {
49			shared_secret,
50			initial_nonce,
51			count: 0,
52		}
53	}
54
55	/// Encrypts bytes generating returning the generated Mac-
56	pub fn encrypt(&mut self, msg: &mut [u8]) -> Mac {
57		self.new_cipher().encrypt(msg)
58	}
59
60	/// Decrypts data, returning an Error if the Mac's do not
61	/// match.
62	pub fn decrypt(
63		&mut self,
64		msg: &mut [u8],
65		recv_mac: &Mac,
66	) -> Result<(), MacNotEqual> {
67		self.new_cipher().decrypt(msg, recv_mac)
68	}
69
70	/// the cipher should only be used once
71	fn new_cipher(&mut self) -> Cipher {
72		self.count += 1;
73		Cipher::new(&self.shared_secret, &self.initial_nonce, self.count)
74	}
75
76	pub fn into_sync(self) -> SyncKey {
77		SyncKey::new(self.shared_secret, self.initial_nonce, self.count)
78	}
79
80	/// This should only be used in test.
81	///
82	/// Using the same key can lead to nonce reuse
83	/// which makes the encryption or decryption
84	/// unsecure.
85	pub fn dublicate(&self) -> Self {
86		Self {
87			shared_secret: self.shared_secret,
88			initial_nonce: self.initial_nonce,
89			count: self.count,
90		}
91	}
92}
93
94impl fmt::Debug for Key {
95	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96		f.write_str("Key")
97	}
98}
99
100impl Drop for Key {
101	fn drop(&mut self) {
102		self.shared_secret.zeroize();
103		self.initial_nonce.zeroize();
104	}
105}
106
107/// A Key that allows to encrypt and decrypt messages.  
108/// Without having to borrow mutably.
109pub struct SyncKey {
110	shared_secret: [u8; 32],
111	initial_nonce: [u8; 24],
112	count: AtomicU64,
113}
114
115impl SyncKey {
116	/// Creates a new key.  
117	/// And modifying the shared_secret to be a uniformly random key.
118	fn new(
119		shared_secret: [u8; 32],
120		initial_nonce: [u8; 24],
121		count: u64,
122	) -> Self {
123		Self {
124			shared_secret,
125			initial_nonce,
126			// + 1 since the values that will be used are before adding
127			count: AtomicU64::new(count + 1),
128		}
129	}
130
131	/// Encrypts bytes generating returning the generated Mac-
132	pub fn encrypt(&self, msg: &mut [u8]) -> Mac {
133		self.new_cipher().encrypt(msg)
134	}
135
136	/// Decrypts data, returning an Error if the Mac's do not
137	/// match.
138	pub fn decrypt(
139		&self,
140		msg: &mut [u8],
141		recv_mac: &Mac,
142	) -> Result<(), MacNotEqual> {
143		self.new_cipher().decrypt(msg, recv_mac)
144	}
145
146	/// the cipher should only be used once
147	fn new_cipher(&self) -> Cipher {
148		Cipher::new(
149			&self.shared_secret,
150			&self.initial_nonce,
151			// relaxed since we only need to guarantee a number get's used once.
152			self.count.fetch_add(1, Ordering::Relaxed),
153		)
154	}
155}
156
157impl fmt::Debug for SyncKey {
158	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159		f.write_str("SyncKey")
160	}
161}
162
163impl Drop for SyncKey {
164	fn drop(&mut self) {
165		self.shared_secret.zeroize();
166		self.initial_nonce.zeroize();
167	}
168}
169
170trait ToMac {
171	fn to_mac(self, msg_len: usize) -> Mac;
172}
173
174impl ToMac for Poly1305 {
175	fn to_mac(self, msg_len: usize) -> Mac {
176		// like https://docs.rs/crate/chacha20poly1305/0.5.1/source/src/cipher.rs
177		let bytes = (msg_len as u64).to_be_bytes();
178
179		// assuming no aad needs to be set
180		Mac::new(self.compute_unpadded(&bytes))
181	}
182}
183
184fn xor_nonce_with_u64(nonce: &mut [u8; 24], count: u64) {
185	let bytes = count.to_be_bytes();
186	xor(&mut nonce[..8], &bytes);
187	xor(&mut nonce[8..16], &bytes);
188	xor(&mut nonce[16..], &bytes);
189}
190
191struct Cipher {
192	cipher: XChaCha20,
193	poly: Poly1305,
194}
195
196impl Cipher {
197	fn new(
198		shared_secret: &[u8; 32],
199		initial_nonce: &[u8; 24],
200		count: u64,
201	) -> Self {
202		// new chacha
203		let mut iv = *initial_nonce;
204		xor_nonce_with_u64(&mut iv, count);
205
206		let mut cipher =
207			<XChaCha20 as KeyIvInit>::new(shared_secret.into(), &iv.into());
208
209		// Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
210		let mut mac_key = [0u8; 32];
211		cipher.apply_keystream(&mut mac_key);
212
213		let poly = Poly1305::new(&mac_key.into());
214
215		mac_key.zeroize();
216
217		// set ChaCha20 counter to 1
218		cipher.seek(BLOCK_SIZE);
219
220		Self { cipher, poly }
221	}
222
223	/// Encrypts bytes generating returning the generated Mac-
224	fn encrypt(mut self, msg: &mut [u8]) -> Mac {
225		self.cipher.apply_keystream(msg);
226		self.poly.update_padded(msg);
227		self.poly.to_mac(msg.len())
228	}
229
230	fn decrypt(
231		mut self,
232		msg: &mut [u8],
233		recv_mac: &Mac,
234	) -> Result<(), MacNotEqual> {
235		self.poly.update_padded(msg);
236		let mac = self.poly.to_mac(msg.len());
237
238		// This performs a constant-time comparison using the `subtle` crate
239		// via Poly1305 `Tag` Struct
240		if recv_mac == &mac {
241			self.cipher.apply_keystream(msg);
242
243			Ok(())
244		} else {
245			Err(MacNotEqual)
246		}
247	}
248}