vector_core/crypto/gcm/
mod.rs1#[cfg(target_arch = "x86_64")]
6mod x86;
7
8use aes::cipher::{BlockCipherEncrypt, KeyInit, KeyIvInit, StreamCipher};
9use aes::Aes256;
10use ghash::universal_hash::UniversalHash;
11use ghash::GHash;
12
13pub const TAG_LEN: usize = 16;
14
15#[allow(clippy::large_enum_variant)]
18enum Engine {
19 #[cfg(target_arch = "x86_64")]
20 Stitched(x86::Gcm),
21 Generic { ctr: ctr::Ctr32BE<Aes256>, ghash: GHash, tag_mask: [u8; TAG_LEN] },
22}
23
24pub struct Stream {
27 engine: Engine,
28 len: u64,
29 closed: bool,
30}
31
32impl Stream {
33 pub fn new(key: &[u8; 32], nonce: &[u8; 16]) -> Self {
36 #[cfg(target_arch = "x86_64")]
37 if x86::available() {
38 return Self::stitched(key, nonce);
39 }
40 Self::generic(key, nonce)
41 }
42
43 #[cfg(target_arch = "x86_64")]
44 fn stitched(key: &[u8; 32], nonce: &[u8; 16]) -> Self {
45 let gcm = unsafe { x86::Gcm::new(key, nonce) };
47 Stream { engine: Engine::Stitched(gcm), len: 0, closed: false }
48 }
49
50 fn generic(key: &[u8; 32], nonce: &[u8; 16]) -> Self {
51 let cipher = Aes256::new(&(*key).into());
53 let mut h = ghash::Block::default();
54 cipher.encrypt_block(&mut h);
55 let mut j0_hash = GHash::new(&h);
56 j0_hash.update_padded(nonce);
57 let mut len_block = ghash::Block::default();
58 len_block[8..].copy_from_slice(&128u64.to_be_bytes());
59 j0_hash.update(&[len_block]);
60 let j0 = j0_hash.finalize();
61
62 let mut ctr = ctr::Ctr32BE::<Aes256>::new(&(*key).into(), &j0);
64 let mut tag_mask = [0u8; TAG_LEN];
65 ctr.apply_keystream(&mut tag_mask);
66 Stream { engine: Engine::Generic { ctr, ghash: GHash::new(&h), tag_mask }, len: 0, closed: false }
67 }
68
69 fn account(&mut self, chunk: &[u8]) {
70 debug_assert!(!self.closed, "a chunk followed a partial one");
71 self.closed = !chunk.len().is_multiple_of(16);
72 self.len += chunk.len() as u64;
73 }
74
75 pub fn encrypt(&mut self, chunk: &mut [u8]) {
76 self.account(chunk);
77 match &mut self.engine {
78 #[cfg(target_arch = "x86_64")]
80 Engine::Stitched(gcm) => unsafe { gcm.encrypt(chunk) },
81 Engine::Generic { ctr, ghash, .. } => {
82 ctr.apply_keystream(chunk);
83 ghash.update_padded(chunk);
84 }
85 }
86 }
87
88 pub fn decrypt(&mut self, chunk: &mut [u8]) {
89 self.account(chunk);
90 match &mut self.engine {
91 #[cfg(target_arch = "x86_64")]
93 Engine::Stitched(gcm) => unsafe { gcm.decrypt(chunk) },
94 Engine::Generic { ctr, ghash, .. } => {
95 ghash.update_padded(chunk);
96 ctr.apply_keystream(chunk);
97 }
98 }
99 }
100
101 pub fn finish(self) -> [u8; TAG_LEN] {
103 match self.engine {
104 #[cfg(target_arch = "x86_64")]
106 Engine::Stitched(gcm) => unsafe { gcm.finish(self.len) },
107 Engine::Generic { mut ghash, tag_mask, .. } => {
108 let mut lengths = ghash::Block::default();
109 lengths[8..].copy_from_slice(&(self.len * 8).to_be_bytes());
110 ghash.update(&[lengths]);
111 let mut tag = [0u8; TAG_LEN];
112 for (t, (s, m)) in tag.iter_mut().zip(ghash.finalize().iter().zip(tag_mask)) {
113 *t = s ^ m;
114 }
115 tag
116 }
117 }
118 }
119}
120
121pub fn seal(key: &[u8; 32], nonce: &[u8; 16], data: &mut [u8]) -> [u8; TAG_LEN] {
123 let mut s = Stream::new(key, nonce);
124 s.encrypt(data);
125 s.finish()
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub struct TagMismatch;
131
132pub fn open(key: &[u8; 32], nonce: &[u8; 16], data: &mut [u8], tag: &[u8; TAG_LEN]) -> Result<(), TagMismatch> {
135 let mut s = Stream::new(key, nonce);
136 s.decrypt(data);
137 if tags_equal(&s.finish(), tag) {
138 Ok(())
139 } else {
140 super::wipe(data);
141 Err(TagMismatch)
142 }
143}
144
145pub fn tags_equal(a: &[u8; TAG_LEN], b: &[u8; TAG_LEN]) -> bool {
147 let diff = a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y));
148 core::hint::black_box(diff) == 0
149}
150
151#[doc(hidden)]
153pub mod bench {
154 use super::*;
155
156 pub fn stitched_present() -> bool {
158 #[cfg(target_arch = "x86_64")]
159 {
160 x86::instructions_present()
161 }
162 #[cfg(not(target_arch = "x86_64"))]
163 {
164 false
165 }
166 }
167
168 pub fn stitched_in_production() -> bool {
170 #[cfg(target_arch = "x86_64")]
171 {
172 x86::available()
173 }
174 #[cfg(not(target_arch = "x86_64"))]
175 {
176 false
177 }
178 }
179
180 pub fn stream(engine: &str, key: &[u8; 32], nonce: &[u8; 16]) -> Option<Stream> {
182 match engine {
183 #[cfg(target_arch = "x86_64")]
184 "stitched" if x86::instructions_present() => Some(Stream::stitched(key, nonce)),
185 "generic" => Some(Stream::generic(key, nonce)),
186 _ => None,
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests;