From f3f26b59a73d9168e2c64e15cf2d6b6fbd81463a Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Mon, 23 Oct 2023 23:55:24 +0800 Subject: [PATCH] support (non-standard) sm4-gcm, sm4-ccm in v1-aead-extra - https://datatracker.ietf.org/doc/html/rfc8998 - shadowsocks/shadowsocks-libev#2424 If anyone interested in trying these non-standard ciphers, please take some time to get some background knowledge about the SM Cipher Suites. --- Cargo.toml | 22 ++- src/kind.rs | 35 ++++- src/v1/aeadcipher/mod.rs | 32 +++++ src/v1/aeadcipher/sm4_ccm.rs | 83 ++++++++++++ src/v1/aeadcipher/sm4_gcm.rs | 46 +++++++ src/v1/aeadcipher/sm4_gcm_cipher.rs | 202 ++++++++++++++++++++++++++++ 6 files changed, 412 insertions(+), 8 deletions(-) create mode 100644 src/v1/aeadcipher/sm4_ccm.rs create mode 100644 src/v1/aeadcipher/sm4_gcm.rs create mode 100644 src/v1/aeadcipher/sm4_gcm_cipher.rs diff --git a/Cargo.toml b/Cargo.toml index 17d37a1..3375d28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-crypto" -version = "0.5.2" +version = "0.5.3" authors = ["luozijun ", "ty "] edition = "2021" license = "MIT" @@ -12,14 +12,20 @@ rust-version = "1.61" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [ - "v1", - "v1-aead", -] +default = ["v1", "v1-aead"] v1 = [] v1-stream = ["v1", "chacha20", "aes", "ctr", "camellia"] v1-aead = ["v1", "aes-gcm", "chacha20poly1305", "hkdf", "sha1"] -v1-aead-extra = ["v1-aead", "aes-gcm-siv", "ccm", "aes"] +v1-aead-extra = [ + "v1-aead", + "aes-gcm-siv", + "ccm", + "aes", + "sm4", + "ghash", + "aead", + "subtle", +] v2 = ["aes", "aes-gcm", "blake3", "chacha20poly1305", "bytes"] v2-extra = ["v2", "chacha20poly1305/reduced-round"] @@ -42,6 +48,10 @@ aes = { version = "0.8", optional = true } ctr = { version = "0.9", optional = true } bytes = { version = "1.3", optional = true } camellia = { version = "0.1", optional = true } +sm4 = { version = "0.5", optional = true } +ghash = { version = "0.5", optional = true } +aead = { version = "0.5", optional = true } +subtle = { version = "2.5", optional = true } #[target.'cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))'.dependencies] #md-5 = { version = "0.10", features = ["asm"] } diff --git a/src/kind.rs b/src/kind.rs index a764b22..e495702 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -1,7 +1,7 @@ //! Cipher Kind #[cfg(feature = "v1-aead-extra")] -use crate::v1::aeadcipher::{Aes128Ccm, Aes128GcmSiv, Aes256Ccm, Aes256GcmSiv, XChaCha20Poly1305}; +use crate::v1::aeadcipher::{Aes128Ccm, Aes128GcmSiv, Aes256Ccm, Aes256GcmSiv, Sm4Ccm, Sm4Gcm, XChaCha20Poly1305}; #[cfg(feature = "v1-aead")] use crate::v1::aeadcipher::{Aes128Gcm, Aes256Gcm, ChaCha20Poly1305}; @@ -230,6 +230,15 @@ pub enum CipherKind { /// AEAD_XCHACHA20_POLY1305 XCHACHA20_POLY1305, + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docrs, doc(cfg(feature = "v1-aead-extra")))] + /// SM4_GCM + SM4_GCM, + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docrs, doc(cfg(feature = "v1-aead-extra")))] + /// SM4_GCM + SM4_CCM, + #[cfg(feature = "v2")] #[cfg_attr(docrs, doc(cfg(feature = "v2")))] /// 2022-blake3-aes-128-gcm @@ -302,7 +311,9 @@ impl CipherKind { AES_128_GCM | AES_256_GCM | CHACHA20_POLY1305 => true, #[cfg(feature = "v1-aead-extra")] - AES_128_CCM | AES_256_CCM | AES_128_GCM_SIV | AES_256_GCM_SIV | XCHACHA20_POLY1305 => true, + AES_128_CCM | AES_256_CCM | AES_128_GCM_SIV | AES_256_GCM_SIV | XCHACHA20_POLY1305 | SM4_GCM | SM4_CCM => { + true + } _ => false, } @@ -426,6 +437,11 @@ impl CipherKind { #[cfg(feature = "v1-aead-extra")] XCHACHA20_POLY1305 => XChaCha20Poly1305::key_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_GCM => Sm4Gcm::key_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_CCM => Sm4Ccm::key_size(), + #[cfg(feature = "v2")] AEAD2022_BLAKE3_AES_128_GCM => Aead2022Aes128Gcm::key_size(), #[cfg(feature = "v2")] @@ -515,6 +531,11 @@ impl CipherKind { #[cfg(feature = "v1-aead-extra")] XCHACHA20_POLY1305 => XChaCha20Poly1305::tag_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_GCM => Sm4Gcm::tag_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_CCM => Sm4Ccm::tag_size(), + #[cfg(feature = "v2")] AEAD2022_BLAKE3_AES_128_GCM => Aead2022Aes128Gcm::tag_size(), #[cfg(feature = "v2")] @@ -669,6 +690,11 @@ impl core::fmt::Display for CipherKind { #[cfg(feature = "v1-aead-extra")] CipherKind::XCHACHA20_POLY1305 => "xchacha20-ietf-poly1305", + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_GCM => "sm4-gcm", + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_CCM => "sm4-ccm", + #[cfg(feature = "v2")] CipherKind::AEAD2022_BLAKE3_AES_128_GCM => "2022-blake3-aes-128-gcm", #[cfg(feature = "v2")] @@ -814,6 +840,11 @@ impl core::str::FromStr for CipherKind { #[cfg(feature = "v1-aead-extra")] "xchacha20-ietf-poly1305" => Ok(XCHACHA20_POLY1305), + #[cfg(feature = "v1-aead-extra")] + "sm4-gcm" => Ok(SM4_GCM), + #[cfg(feature = "v1-aead-extra")] + "sm4-ccm" => Ok(SM4_CCM), + #[cfg(feature = "v2")] "2022-blake3-aes-128-gcm" => Ok(AEAD2022_BLAKE3_AES_128_GCM), #[cfg(feature = "v2")] diff --git a/src/v1/aeadcipher/mod.rs b/src/v1/aeadcipher/mod.rs index 818e835..ec3d211 100644 --- a/src/v1/aeadcipher/mod.rs +++ b/src/v1/aeadcipher/mod.rs @@ -7,12 +7,20 @@ mod aes_gcm; mod aes_gcm_siv; mod chacha20_poly1305; #[cfg(feature = "v1-aead-extra")] +mod sm4_ccm; +#[cfg(feature = "v1-aead-extra")] +mod sm4_gcm; +#[cfg(feature = "v1-aead-extra")] +mod sm4_gcm_cipher; +#[cfg(feature = "v1-aead-extra")] mod xchacha20_poly1305; #[cfg(feature = "v1-aead-extra")] pub use self::{ aes_ccm::{Aes128Ccm, Aes256Ccm}, aes_gcm_siv::{Aes128GcmSiv, Aes256GcmSiv}, + sm4_ccm::Sm4Ccm, + sm4_gcm::Sm4Gcm, xchacha20_poly1305::XChaCha20Poly1305, }; pub use self::{ @@ -34,6 +42,10 @@ enum AeadCipherVariant { Aes128Ccm(Aes128Ccm), #[cfg(feature = "v1-aead-extra")] Aes256Ccm(Aes256Ccm), + #[cfg(feature = "v1-aead-extra")] + Sm4Gcm(Sm4Gcm), + #[cfg(feature = "v1-aead-extra")] + Sm4Ccm(Sm4Ccm), } impl AeadCipherVariant { @@ -52,6 +64,10 @@ impl AeadCipherVariant { CipherKind::AES_128_CCM => AeadCipherVariant::Aes128Ccm(Aes128Ccm::new(key)), #[cfg(feature = "v1-aead-extra")] CipherKind::AES_256_CCM => AeadCipherVariant::Aes256Ccm(Aes256Ccm::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_GCM => AeadCipherVariant::Sm4Gcm(Sm4Gcm::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_CCM => AeadCipherVariant::Sm4Ccm(Sm4Ccm::new(key)), _ => unreachable!("{:?} is not an AEAD cipher", kind), } } @@ -71,6 +87,10 @@ impl AeadCipherVariant { AeadCipherVariant::Aes128Ccm(..) => Aes128Ccm::nonce_size(), #[cfg(feature = "v1-aead-extra")] AeadCipherVariant::Aes256Ccm(..) => Aes256Ccm::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(..) => Sm4Gcm::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(..) => Sm4Ccm::nonce_size(), } } @@ -89,6 +109,10 @@ impl AeadCipherVariant { AeadCipherVariant::Aes128Ccm(..) => CipherKind::AES_128_CCM, #[cfg(feature = "v1-aead-extra")] AeadCipherVariant::Aes256Ccm(..) => CipherKind::AES_256_CCM, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(..) => CipherKind::SM4_GCM, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(..) => CipherKind::SM4_CCM, } } @@ -107,6 +131,10 @@ impl AeadCipherVariant { AeadCipherVariant::Aes128Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), #[cfg(feature = "v1-aead-extra")] AeadCipherVariant::Aes256Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), } } @@ -125,6 +153,10 @@ impl AeadCipherVariant { AeadCipherVariant::Aes128Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), #[cfg(feature = "v1-aead-extra")] AeadCipherVariant::Aes256Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), } } } diff --git a/src/v1/aeadcipher/sm4_ccm.rs b/src/v1/aeadcipher/sm4_ccm.rs new file mode 100644 index 0000000..6f56b39 --- /dev/null +++ b/src/v1/aeadcipher/sm4_ccm.rs @@ -0,0 +1,83 @@ +//! SM4_CCM +//! +//! https://datatracker.ietf.org/doc/html/rfc8998 + +use ccm::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + consts::{U12, U16}, + Ccm, + Nonce, + Tag, +}; +use sm4::Sm4; + +pub struct Sm4Ccm(Ccm); + +impl Sm4Ccm { + pub fn new(key: &[u8]) -> Sm4Ccm { + Sm4Ccm(Ccm::new_from_slice(key).expect("Sm4Ccm")) + } + + pub fn key_size() -> usize { + as KeySizeUser>::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + as AeadCore>::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + as AeadCore>::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_128_CCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} + +#[cfg(test)] +mod test { + use ccm::{ + aead::{Aead, KeyInit, Payload}, + consts::{U12, U16}, + Ccm, + }; + use sm4::Sm4; + + #[test] + fn test_sm4_ccm() { + let iv = hex::decode("00001234567800000000ABCD").unwrap(); + let key = hex::decode("0123456789ABCDEFFEDCBA9876543210").unwrap(); + let plain_text = hex::decode("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA").unwrap(); + let aad = hex::decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2").unwrap(); + let mut cipher_text = hex::decode("48AF93501FA62ADBCD414CCE6034D895DDA1BF8F132F042098661572E7483094FD12E518CE062C98ACEE28D95DF4416BED31A2F04476C18BB40C84A74B97DC5B").unwrap(); + let mut tag = hex::decode("16842D4FA186F56AB33256971FA110F4").unwrap(); + cipher_text.append(&mut tag); // postfix tag + + let nonce = super::Nonce::from_slice(&iv); + + let cipher = Ccm::::new_from_slice(&key).unwrap(); + let plain_text_payload = Payload { + msg: &plain_text, + aad: &aad, + }; + let result = cipher.encrypt(nonce, plain_text_payload).unwrap(); + + assert_eq!(result, cipher_text); + } +} diff --git a/src/v1/aeadcipher/sm4_gcm.rs b/src/v1/aeadcipher/sm4_gcm.rs new file mode 100644 index 0000000..e11d1b1 --- /dev/null +++ b/src/v1/aeadcipher/sm4_gcm.rs @@ -0,0 +1,46 @@ +//! SM4-GCM + +use aead::{AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser}; +use sm4::cipher::Unsigned; + +use super::sm4_gcm_cipher::{Nonce, Sm4Gcm as CryptoSm4Gcm, Tag}; + +pub struct Sm4Gcm(CryptoSm4Gcm); + +impl Sm4Gcm { + pub fn new(key: &[u8]) -> Sm4Gcm { + let key = Key::::from_slice(key); + Sm4Gcm(CryptoSm4Gcm::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("SM4_GCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v1/aeadcipher/sm4_gcm_cipher.rs b/src/v1/aeadcipher/sm4_gcm_cipher.rs new file mode 100644 index 0000000..038e9d3 --- /dev/null +++ b/src/v1/aeadcipher/sm4_gcm_cipher.rs @@ -0,0 +1,202 @@ +//! SM4_GCM +//! +//! https://datatracker.ietf.org/doc/html/rfc8998 + +use aead::{ + consts::{U0, U12, U16}, + generic_array::GenericArray, + AeadCore, + AeadInPlace, + Key, + KeyInit, + KeySizeUser, +}; +use ghash::{universal_hash::UniversalHash, GHash}; +use sm4::{ + cipher::{BlockEncrypt, InnerIvInit, StreamCipherCore, Unsigned}, + Sm4, +}; + +/// Maximum length of associated data. +pub const A_MAX: u64 = 1 << 36; + +/// Maximum length of plaintext. +pub const P_MAX: u64 = 1 << 36; + +/// Maximum length of ciphertext. +pub const C_MAX: u64 = (1 << 36) + 16; + +/// SM4-GCM nonces. +pub type Nonce = GenericArray; + +/// SM4-GCM tags. +pub type Tag = GenericArray; + +/// SM4 block. +type Block = GenericArray; + +/// Counter mode with a 32-bit big endian counter. +type Ctr32BE<'a> = ctr::CtrCore<&'a Sm4, ctr::flavors::Ctr32BE>; + +pub struct Sm4Gcm { + cipher: Sm4, + ghash: GHash, +} + +impl KeySizeUser for Sm4Gcm { + type KeySize = ::KeySize; +} + +impl KeyInit for Sm4Gcm { + fn new(key: &Key) -> Self { + Sm4::new(key).into() + } +} + +impl From for Sm4Gcm { + fn from(cipher: Sm4) -> Self { + let mut ghash_key = ghash::Key::default(); + cipher.encrypt_block(&mut ghash_key); + + let ghash = GHash::new(&ghash_key); + + // ghash_key.zeroize(); + + Self { cipher, ghash } + } +} + +impl AeadCore for Sm4Gcm { + type CiphertextOverhead = U0; + type NonceSize = U12; + type TagSize = U16; +} + +impl AeadInPlace for Sm4Gcm { + fn encrypt_in_place_detached(&self, nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8]) -> aead::Result { + if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { + return Err(aead::Error); + } + + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + ctr.apply_keystream_partial(buffer.into()); + + let full_tag = self.compute_tag(mask, associated_data, buffer); + Ok(Tag::clone_from_slice(&full_tag[..Self::TagSize::to_usize()])) + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> aead::Result<()> { + if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { + return Err(aead::Error); + } + + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + let expected_tag = self.compute_tag(mask, associated_data, buffer); + + use subtle::ConstantTimeEq; + if expected_tag[..::TagSize::to_usize()] + .ct_eq(tag) + .into() + { + ctr.apply_keystream_partial(buffer.into()); + Ok(()) + } else { + Err(aead::Error) + } + } +} + +impl Sm4Gcm { + /// Initialize counter mode. + /// + /// See algorithm described in Section 7.2 of NIST SP800-38D: + /// + /// + /// > Define a block, J0, as follows: + /// > If len(IV)=96, then J0 = IV || 0{31} || 1. + /// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and + /// > J0=GHASH(IV||0s+64||[len(IV)]64). + fn init_ctr(&self, nonce: &Nonce) -> (Ctr32BE, Block) { + let j0 = if ::NonceSize::to_usize() == 12 { + let mut block = ghash::Block::default(); + block[..12].copy_from_slice(nonce); + block[15] = 1; + block + } else { + let mut ghash = self.ghash.clone(); + ghash.update_padded(nonce); + + let mut block = ghash::Block::default(); + let nonce_bits = (::NonceSize::to_usize() as u64) * 8; + block[8..].copy_from_slice(&nonce_bits.to_be_bytes()); + ghash.update(&[block]); + ghash.finalize() + }; + + let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0); + let mut tag_mask = Block::default(); + ctr.write_keystream_block(&mut tag_mask); + (ctr, tag_mask) + } + + /// Authenticate the given plaintext and associated data using GHASH. + fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag { + let mut ghash = self.ghash.clone(); + ghash.update_padded(associated_data); + ghash.update_padded(buffer); + + let associated_data_bits = (associated_data.len() as u64) * 8; + let buffer_bits = (buffer.len() as u64) * 8; + + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&associated_data_bits.to_be_bytes()); + block[8..].copy_from_slice(&buffer_bits.to_be_bytes()); + ghash.update(&[block]); + + let mut tag = ghash.finalize(); + for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) { + *a ^= *b; + } + + tag + } +} + +#[cfg(test)] +mod test { + use aead::{Aead, KeyInit, Payload}; + + #[test] + fn test_sm4_gcm() { + let iv = hex::decode("00001234567800000000ABCD").unwrap(); + let key = hex::decode("0123456789ABCDEFFEDCBA9876543210").unwrap(); + let plain_text = hex::decode("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA").unwrap(); + let aad = hex::decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2").unwrap(); + let mut cipher_text = hex::decode("17F399F08C67D5EE19D0DC9969C4BB7D5FD46FD3756489069157B282BB200735D82710CA5C22F0CCFA7CBF93D496AC15A56834CBCF98C397B4024A2691233B8D").unwrap(); + let mut tag = hex::decode("83DE3541E4C2B58177E065A9BF7B62EC").unwrap(); + cipher_text.append(&mut tag); // postfix tag + + let nonce = super::Nonce::from_slice(&iv); + + let cipher = super::Sm4Gcm::new_from_slice(&key).unwrap(); + let plain_text_payload = Payload { + msg: &plain_text, + aad: &aad, + }; + let result = cipher.encrypt(nonce, plain_text_payload).unwrap(); + + assert_eq!(result, cipher_text); + } +}