chacha12_blake3/
chacha12_blake3.rs1#![no_std]
2#![doc = include_str!("README.md")]
3
4use chacha20::{
5 ChaCha12,
6 cipher::{KeyIvInit, StreamCipher},
7};
8use constant_time_eq::constant_time_eq_32;
9
10#[cfg(feature = "zeroize")]
11use zeroize::{Zeroize, ZeroizeOnDrop};
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15#[cfg(feature = "alloc")]
16use alloc::vec::Vec;
17
18pub const KEY_SIZE: usize = 32;
19pub const NONCE_SIZE: usize = 32;
20pub const TAG_SIZE: usize = 32;
21
22const ENCRYPTION_KDF_CONTEXT: &str = "ChaCha12-BLAKE3 encryption key";
23const AUTHENTICATION_KDF_CONTEXT: &str = "ChaCha12-BLAKE3 authentication key";
24
25#[derive(Clone, Copy, Debug)]
26pub struct Error {}
27
28#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
29pub struct ChaCha12Blake3 {
30 key: [u8; 32],
31 authentication_key: [u8; 32],
32}
33
34impl ChaCha12Blake3 {
35 pub fn new(key: [u8; 32]) -> Self {
36 let authentication_key: [u8; 32] = blake3::derive_key(AUTHENTICATION_KDF_CONTEXT, &key);
37 return ChaCha12Blake3 {
38 key,
39 authentication_key,
40 };
41 }
42
43 #[cfg(feature = "alloc")]
44 pub fn encrypt(&self, nonce: &[u8; 32], plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
45 let mut ciphertext = alloc::vec![0u8; plaintext.len() + TAG_SIZE];
46 ciphertext[..plaintext.len()].copy_from_slice(&plaintext);
47
48 let tag = self.encrypt_in_place_detached(nonce, &mut ciphertext[..plaintext.len()], aad);
49 ciphertext[plaintext.len()..].copy_from_slice(&tag);
50
51 return ciphertext;
52 }
53
54 #[cfg(feature = "alloc")]
55 pub fn decrypt(&self, nonce: &[u8; 32], ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Error> {
56 if ciphertext.len() < TAG_SIZE {
57 return Err(Error {});
58 }
59
60 let mut plaintext = alloc::vec![0u8; ciphertext.len() - TAG_SIZE];
61 plaintext.copy_from_slice(&ciphertext[..ciphertext.len() - TAG_SIZE]);
62
63 self.decrypt_in_place_detached(
64 nonce,
65 &mut plaintext,
66 &ciphertext[ciphertext.len() - TAG_SIZE..].try_into().unwrap(),
67 aad,
68 )?;
69
70 return Ok(plaintext);
71 }
72
73 pub fn encrypt_in_place_detached(&self, nonce: &[u8; 32], plaintext: &mut [u8], aad: &[u8]) -> [u8; 32] {
74 let mut encryption_key: [u8; 32] = blake3::Hasher::new_derive_key(ENCRYPTION_KDF_CONTEXT)
76 .update(&self.key)
77 .update(nonce)
78 .finalize()
79 .into();
80
81 let mut chacha_ietf_nonce = [0u8; 12];
86 chacha_ietf_nonce[4..].copy_from_slice(&nonce[..8]);
87
88 assert!(
94 plaintext.len() < ((64 * (1 << 32)) - 64),
95 "ChaCha12-BLAKE3 currently can't encrypt more than 256 GiB of data with a single (key, nonce) pair"
96 );
97
98 ChaCha12::new(&encryption_key.into(), &chacha_ietf_nonce.into()).apply_keystream(plaintext);
99
100 let mut mac_hasher = blake3::Hasher::new_keyed(&self.authentication_key);
101 mac_hasher.update(nonce);
102 mac_hasher.update(aad);
103 mac_hasher.update(&(aad.len() as u64).to_le_bytes());
104 mac_hasher.update(&plaintext);
105 mac_hasher.update(&(plaintext.len() as u64).to_le_bytes());
106 let tag = mac_hasher.finalize();
107
108 #[cfg(feature = "zeroize")]
109 encryption_key.zeroize();
110
111 return tag.into();
112 }
113
114 pub fn decrypt_in_place_detached(
115 &self,
116 nonce: &[u8; 32],
117 ciphertext: &mut [u8],
118 tag: &[u8; 32],
119 aad: &[u8],
120 ) -> Result<(), Error> {
121 let mut mac_hasher = blake3::Hasher::new_keyed(&self.authentication_key);
122 mac_hasher.update(nonce);
123 mac_hasher.update(aad);
124 mac_hasher.update(&(aad.len() as u64).to_le_bytes());
125 mac_hasher.update(&ciphertext);
126 mac_hasher.update(&(ciphertext.len() as u64).to_le_bytes());
127 let mac = mac_hasher.finalize();
128
129 if !constant_time_eq_32(mac.as_bytes(), tag) {
130 return Err(Error {});
131 }
132
133 let mut encryption_key: [u8; 32] = blake3::Hasher::new_derive_key(ENCRYPTION_KDF_CONTEXT)
135 .update(&self.key)
136 .update(nonce)
137 .finalize()
138 .into();
139
140 let mut chacha_ietf_nonce = [0u8; 12];
143 chacha_ietf_nonce[4..].copy_from_slice(&nonce[..8]);
144
145 ChaCha12::new(&encryption_key.into(), &chacha_ietf_nonce.into()).apply_keystream(ciphertext);
146
147 #[cfg(feature = "zeroize")]
148 encryption_key.zeroize();
149
150 return Ok(());
151 }
152}