1use ctr::Ctr32BE;
18use ctr::CtrCore;
19use ctr::cipher::{Block, BlockEncrypt, InnerIvInit, KeyInit, StreamCipher};
20use ghash::GHash;
21use ghash::universal_hash::UniversalHash;
22use rand_core::{OsRng, RngCore};
23use sm4::Sm4;
24
25use crate::contract::{Identified, SymmetricCipher};
26use crate::error::{Error, Result};
27use crate::key::Key;
28
29const LABEL: &str = "SM4-GCM";
30const PREFIX: &[u8] = b"v1";
31const NONCE_LEN: usize = 12;
32const TAG_LEN: usize = 16;
33
34#[derive(Debug)]
40pub struct Sm4GcmEncryptor {
41 cipher: Sm4,
42}
43
44impl Sm4GcmEncryptor {
45 pub const IDENTIFIER: &'static str = "sm4-gcm";
46
47 pub fn new(key: [u8; 16]) -> Self {
48 Self {
49 cipher: sm4_cipher(&key),
50 }
51 }
52
53 pub fn from_slice(key: &[u8]) -> Result<Self> {
54 let key = Key::<16>::from_slice(key, LABEL)?;
56 Ok(Self {
57 cipher: sm4_cipher(key.as_bytes()),
58 })
59 }
60
61 #[cfg(test)]
63 pub(crate) fn encrypt_with_nonce(&self, nonce: &[u8; NONCE_LEN], plaintext: &[u8]) -> Vec<u8> {
64 let (ciphertext, tag) = seal(&self.cipher, nonce, b"", plaintext);
65 pack(nonce, &tag, &ciphertext)
66 }
67}
68
69impl Identified for Sm4GcmEncryptor {
70 fn identifier(&self) -> &str {
71 Self::IDENTIFIER
72 }
73}
74
75impl SymmetricCipher for Sm4GcmEncryptor {
76 fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
77 let mut nonce = [0u8; NONCE_LEN];
78 OsRng.fill_bytes(&mut nonce);
79
80 let (ciphertext, tag) = seal(&self.cipher, &nonce, b"", plaintext);
81 Ok(pack(&nonce, &tag, &ciphertext))
82 }
83
84 fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
85 let body = ciphertext
86 .strip_prefix(PREFIX)
87 .ok_or(Error::InvalidPrefix { label: LABEL })?;
88 if body.len() < NONCE_LEN + TAG_LEN {
89 return Err(Error::TooShort { label: LABEL });
90 }
91
92 let (nonce, rest) = body.split_at(NONCE_LEN);
93 let (tag, ct) = rest.split_at(TAG_LEN);
94 let nonce: &[u8; NONCE_LEN] = nonce.try_into().expect("length was verified");
95 let tag: &[u8; TAG_LEN] = tag.try_into().expect("length was verified");
96
97 open(&self.cipher, nonce, b"", ct, tag).ok_or(Error::DecryptionFailed { label: LABEL })
98 }
99}
100
101fn pack(nonce: &[u8; NONCE_LEN], tag: &[u8; TAG_LEN], ciphertext: &[u8]) -> Vec<u8> {
103 let mut blob = Vec::with_capacity(PREFIX.len() + NONCE_LEN + TAG_LEN + ciphertext.len());
104 blob.extend_from_slice(PREFIX);
105 blob.extend_from_slice(nonce);
106 blob.extend_from_slice(tag);
107 blob.extend_from_slice(ciphertext);
108 blob
109}
110
111fn sm4_cipher(key: &[u8; 16]) -> Sm4 {
113 Sm4::new_from_slice(key).expect("SM4 key length is fixed at 16")
114}
115
116fn j0_mask(cipher: &Sm4, nonce: &[u8; NONCE_LEN]) -> [u8; 16] {
118 let mut j0 = [0u8; 16];
119 j0[..NONCE_LEN].copy_from_slice(nonce);
120 j0[15] = 1;
121 cipher.encrypt_block(Block::<Sm4>::from_mut_slice(&mut j0));
122 j0
123}
124
125fn ctr_start_block(nonce: &[u8; NONCE_LEN]) -> [u8; 16] {
127 let mut block = [0u8; 16];
128 block[..NONCE_LEN].copy_from_slice(nonce);
129 block[15] = 2; block
131}
132
133fn ctr_keystream(cipher: &Sm4, nonce: &[u8; NONCE_LEN]) -> Ctr32BE<Sm4> {
136 let counter = CtrCore::inner_iv_slice_init(cipher.clone(), &ctr_start_block(nonce))
137 .expect("counter block length is fixed at 16");
138 Ctr32BE::from_core(counter)
139}
140
141fn ghash_state(cipher: &Sm4, aad: &[u8], ciphertext: &[u8]) -> GHash {
144 let mut h = ghash::Key::default();
145 cipher.encrypt_block(&mut h);
146
147 let mut state = GHash::new(&h);
148 state.update_padded(aad);
149 state.update_padded(ciphertext);
150
151 let mut lengths = [0u8; 16];
152 lengths[..8].copy_from_slice(&(8 * aad.len() as u64).to_be_bytes());
153 lengths[8..].copy_from_slice(&(8 * ciphertext.len() as u64).to_be_bytes());
154 state.update(&[lengths.into()]);
155 state
156}
157
158fn seal(
160 cipher: &Sm4,
161 nonce: &[u8; NONCE_LEN],
162 aad: &[u8],
163 plaintext: &[u8],
164) -> (Vec<u8>, [u8; TAG_LEN]) {
165 let mut ciphertext = plaintext.to_vec();
168 ctr_keystream(cipher, nonce).apply_keystream(&mut ciphertext);
169
170 let mask = j0_mask(cipher, nonce);
171 let s = ghash_state(cipher, aad, &ciphertext).finalize();
172 let mut tag = [0u8; TAG_LEN];
173 for ((byte, m), s) in tag.iter_mut().zip(mask.iter()).zip(s.iter()) {
174 *byte = m ^ s;
175 }
176 (ciphertext, tag)
177}
178
179fn open(
182 cipher: &Sm4,
183 nonce: &[u8; NONCE_LEN],
184 aad: &[u8],
185 ciphertext: &[u8],
186 tag: &[u8; TAG_LEN],
187) -> Option<Vec<u8>> {
188 let mask = j0_mask(cipher, nonce);
190 let mut expected = [0u8; TAG_LEN];
191 for ((byte, t), m) in expected.iter_mut().zip(tag.iter()).zip(mask.iter()) {
192 *byte = t ^ m;
193 }
194 ghash_state(cipher, aad, ciphertext)
195 .verify(Block::<GHash>::from_slice(&expected))
196 .ok()?;
197
198 let mut plaintext = ciphertext.to_vec();
199 ctr_keystream(cipher, nonce).apply_keystream(&mut plaintext);
200 Some(plaintext)
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn hex_array<const N: usize>(hex: &str) -> [u8; N] {
209 crate::internal::hex_decode(hex)
210 .unwrap()
211 .try_into()
212 .unwrap()
213 }
214
215 const KAT_KEY: &str = "0123456789abcdeffedcba9876543210";
219 const KAT_NONCE: &str = "00001234567800000000abcd";
220 const KAT_PLAINTEXT: &str = concat!(
221 "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB",
222 "CCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD",
223 "EEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF",
224 "EEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA"
225 );
226 const KAT_AAD: &str = "FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2";
227 const KAT_CIPHERTEXT: &str = concat!(
228 "17F399F08C67D5EE19D0DC9969C4BB7D",
229 "5FD46FD3756489069157B282BB200735",
230 "D82710CA5C22F0CCFA7CBF93D496AC15",
231 "A56834CBCF98C397B4024A2691233B8D"
232 );
233 const KAT_TAG: &str = "83DE3541E4C2B58177E065A9BF7B62EC";
234
235 #[test]
236 fn rfc8998_kat_with_aad_is_byte_exact() {
237 let key: [u8; 16] = hex_array(KAT_KEY);
239 let nonce: [u8; 12] = hex_array(KAT_NONCE);
240 let plaintext: [u8; 64] = hex_array(KAT_PLAINTEXT);
241 let aad: [u8; 20] = hex_array(KAT_AAD);
242 let expected_ciphertext: [u8; 64] = hex_array(KAT_CIPHERTEXT);
243 let expected_tag: [u8; 16] = hex_array(KAT_TAG);
244 let cipher = sm4_cipher(&key);
246
247 let (ciphertext, tag) = seal(&cipher, &nonce, &aad, &plaintext);
248 assert_eq!(ciphertext, expected_ciphertext);
249 assert_eq!(tag, expected_tag);
250
251 let plaintext_back = open(&cipher, &nonce, &aad, &ciphertext, &tag).unwrap();
253 assert_eq!(plaintext_back, plaintext);
254
255 let mut tampered = expected_tag;
256 tampered[0] ^= 0x01;
257 assert_eq!(open(&cipher, &nonce, &aad, &ciphertext, &tampered), None);
258 }
259
260 #[test]
261 fn rfc8998_ciphertext_matches_with_empty_aad() {
262 let encryptor = Sm4GcmEncryptor::new(hex_array(KAT_KEY));
266 let nonce: [u8; 12] = hex_array(KAT_NONCE);
267 let plaintext: [u8; 64] = hex_array(KAT_PLAINTEXT);
268 let expected_ciphertext: [u8; 64] = hex_array(KAT_CIPHERTEXT);
269
270 let frame = encryptor.encrypt_with_nonce(&nonce, &plaintext);
271 assert_eq!(&frame[..2], PREFIX);
272 assert_eq!(&frame[2..14], &nonce);
273 assert_eq!(&frame[30..], &expected_ciphertext[..]);
274 assert_eq!(encryptor.decrypt(&frame).unwrap(), plaintext);
275 }
276
277 #[test]
278 fn roundtrip_and_tamper_detection() {
279 let encryptor = Sm4GcmEncryptor::new([7u8; 16]);
280 for plaintext in [
281 b"".as_slice(),
282 b"plain",
283 b"0123456789abcdef",
284 &[0u8; 64][..],
285 ] {
286 let blob = encryptor.encrypt(plaintext).unwrap();
287 assert_eq!(blob.len(), 2 + NONCE_LEN + TAG_LEN + plaintext.len());
288 assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
289 }
290
291 let mut blob = encryptor.encrypt(b"data").unwrap();
293 let last = blob.len() - 1;
294 blob[last] ^= 0x01;
295 assert_eq!(
296 encryptor.decrypt(&blob).unwrap_err(),
297 Error::DecryptionFailed { label: LABEL }
298 );
299
300 let mut blob = encryptor.encrypt(b"data").unwrap();
302 blob[2 + NONCE_LEN] ^= 0x01;
303 assert_eq!(
304 encryptor.decrypt(&blob).unwrap_err(),
305 Error::DecryptionFailed { label: LABEL }
306 );
307 }
308
309 #[test]
310 fn every_encrypt_uses_a_fresh_nonce() {
311 let encryptor = Sm4GcmEncryptor::new([7u8; 16]);
312 let first = encryptor.encrypt(b"same").unwrap();
313 let second = encryptor.encrypt(b"same").unwrap();
314 assert_ne!(first, second);
315 assert_ne!(first[2..14], second[2..14]);
316 }
317
318 #[test]
319 fn wrong_key_and_bad_frame_fail() {
320 let blob = Sm4GcmEncryptor::new([7u8; 16]).encrypt(b"x").unwrap();
321 let other = Sm4GcmEncryptor::new([8u8; 16]);
322 assert_eq!(
323 other.decrypt(&blob).unwrap_err(),
324 Error::DecryptionFailed { label: LABEL }
325 );
326 assert_eq!(
327 other.decrypt(b"v9....").unwrap_err(),
328 Error::InvalidPrefix { label: LABEL }
329 );
330 assert_eq!(
331 other.decrypt(b"v1short").unwrap_err(),
332 Error::TooShort { label: LABEL }
333 );
334 }
335
336 #[test]
337 fn key_length_is_checked() {
338 assert_eq!(
339 Sm4GcmEncryptor::from_slice(&[0u8; 32]).unwrap_err(),
340 Error::InvalidKeyLength {
341 label: LABEL,
342 expected: 16,
343 got: 32
344 }
345 );
346 }
347
348 #[test]
349 fn identifier_matches_registered_name() {
350 assert_eq!(Sm4GcmEncryptor::new([0u8; 16]).identifier(), "sm4-gcm");
351 }
352}