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