From a4ca734d0dd348e9413b51a0307fe8c488620a26 Mon Sep 17 00:00:00 2001 From: huangxt Date: Wed, 11 Mar 2026 18:32:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9ARustCrypto=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: - 新增 sm2/、sm3/、sm4/ 独立 crate,实现 RustCrypto traits - sm2:实现 signature、elliptic-curve traits - sm3:实现 digest、crypto-common traits - sm4:实现 cipher、aead traits - 新增 fuzz/ 模糊测试目标 - 新增 tests/sm2_proptest.rs 属性测试 - 重构 src/sm4/ 使用 RustCrypto AEAD traits - 更新 CI 工作流支持多 crate 测试 - 更新 .gitignore 忽略 fuzz/target/ --- .github/workflows/ci.yml | 115 +++- .gitignore | 3 +- Cargo.lock | 171 ++++- Cargo.toml | 20 + benches/sm4_bench.rs | 36 +- fuzz/Cargo.toml | 37 + fuzz/fuzz_targets/fuzz_sm2_verify.rs | 22 + fuzz/fuzz_targets/fuzz_sm3_digest.rs | 22 + fuzz/fuzz_targets/fuzz_sm4_roundtrip.rs | 31 + sm2/Cargo.toml | 35 + sm2/README.md | 223 ++++++ sm2/src/der.rs | 533 +++++++++++++++ sm2/src/ec.rs | 610 +++++++++++++++++ sm2/src/error.rs | 50 ++ sm2/src/field.rs | 251 +++++++ sm2/src/kdf.rs | 67 ++ sm2/src/key_exchange.rs | 628 +++++++++++++++++ sm2/src/lib.rs | 796 ++++++++++++++++++++++ sm2/src/rfc6979.rs | 176 +++++ sm3/Cargo.toml | 23 + sm3/src/block_api.rs | 98 +++ sm3/src/compress.rs | 81 +++ sm3/src/lib.rs | 124 ++++ sm4/Cargo.toml | 22 + sm4/src/consts.rs | 286 ++++++++ sm4/src/lib.rs | 254 +++++++ src/rustls_provider/aead.rs | 310 +++++++++ src/rustls_provider/mod.rs | 1 + src/rustls_provider/tls13.rs | 2 +- src/sm3/compress.rs | 90 ++- src/sm3/mod.rs | 15 +- src/sm4/cipher.rs | 6 +- src/sm4/mod.rs | 8 +- src/sm4/modes.rs | 860 ------------------------ tests/sm2_proptest.rs | 87 +++ tests/sm4_vectors.rs | 52 +- 36 files changed, 5184 insertions(+), 961 deletions(-) create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/fuzz_sm2_verify.rs create mode 100644 fuzz/fuzz_targets/fuzz_sm3_digest.rs create mode 100644 fuzz/fuzz_targets/fuzz_sm4_roundtrip.rs create mode 100644 sm2/Cargo.toml create mode 100644 sm2/README.md create mode 100644 sm2/src/der.rs create mode 100644 sm2/src/ec.rs create mode 100644 sm2/src/error.rs create mode 100644 sm2/src/field.rs create mode 100644 sm2/src/kdf.rs create mode 100644 sm2/src/key_exchange.rs create mode 100644 sm2/src/lib.rs create mode 100644 sm2/src/rfc6979.rs create mode 100644 sm3/Cargo.toml create mode 100644 sm3/src/block_api.rs create mode 100644 sm3/src/compress.rs create mode 100644 sm3/src/lib.rs create mode 100644 sm4/Cargo.toml create mode 100644 sm4/src/consts.rs create mode 100644 sm4/src/lib.rs create mode 100644 src/rustls_provider/aead.rs delete mode 100644 src/sm4/modes.rs create mode 100644 tests/sm2_proptest.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20a016b..39f2e4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,13 +88,13 @@ jobs: uses: Swatinem/rust-cache@v2 # Reason: 在所有平台上显式设置 bash 以统一 Windows/Linux/macOS 行为 - - name: 运行测试(默认 features = alloc) - run: cargo test --all-targets + - name: 运行测试(默认 features,全 workspace) + run: cargo test --workspace --all-targets shell: bash - name: 运行测试(no_std,无 alloc) # Reason: 验证嵌入式 / WASM / 裸机兼容性 - run: cargo test --no-default-features --lib + run: cargo test --workspace --no-default-features --lib shell: bash - name: 运行文档测试 @@ -128,13 +128,114 @@ jobs: # Reason: MSRV 只验证能编译,不运行测试(避免 dev-dependencies 兼容问题) - name: 检查编译(默认 features) - run: cargo check + run: cargo check --workspace - name: 检查编译(no_std) - run: cargo check --no-default-features + run: cargo check --workspace --no-default-features # ============================================================================= - # Job 4: 测试覆盖率(仅 Linux,上传 Codecov) + # Job 4: no_std 跨目标编译验证 + # ============================================================================= + no-std: + name: no_std (${{ matrix.target }}) + needs: lint + runs-on: ubuntu-latest + strategy: + matrix: + target: + - thumbv7em-none-eabi # ARM Cortex-M4,典型嵌入式目标 + - wasm32-unknown-unknown # WebAssembly + steps: + - name: 签出代码 + uses: actions/checkout@v4 + + - name: 安装 Rust 工具链(stable + 目标平台) + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: 缓存 Cargo 编译产物 + uses: Swatinem/rust-cache@v2 + + # Reason: --no-default-features 禁用 alloc,验证裸机 no_std 兼容性 + - name: 检查 sm3(no_std,无 alloc) + run: cargo check -p sm3 --target ${{ matrix.target }} --no-default-features + + - name: 检查 sm4(no_std,无 alloc) + run: cargo check -p sm4 --target ${{ matrix.target }} --no-default-features + + - name: 检查 sm2(no_std,无 alloc) + run: cargo check -p sm2 --target ${{ matrix.target }} --no-default-features + + # ============================================================================= + # Job 5: Feature Powerset(验证所有 feature 组合均可编译) + # ============================================================================= + feature-powerset: + name: Feature Powerset + needs: lint + runs-on: ubuntu-latest + steps: + - name: 签出代码 + uses: actions/checkout@v4 + + - name: 安装 Rust 工具链(stable) + uses: dtolnay/rust-toolchain@stable + + - name: 缓存 Cargo 编译产物 + uses: Swatinem/rust-cache@v2 + + - name: 安装 cargo-hack + uses: taiki-e/install-action@cargo-hack + + # Reason: 验证所有 feature 组合均能编译,防止 feature 互斥或遗漏依赖 + - name: 检查 sm3 feature powerset + run: cargo hack check -p sm3 --feature-powerset --no-dev-deps + + - name: 检查 sm4 feature powerset + run: cargo hack check -p sm4 --feature-powerset --no-dev-deps + + - name: 检查 sm2 feature powerset + run: cargo hack check -p sm2 --feature-powerset --no-dev-deps + + # ============================================================================= + # Job 6: Miri 内存安全验证 + # ============================================================================= + miri: + name: Miri + needs: lint + runs-on: ubuntu-latest + steps: + - name: 签出代码 + uses: actions/checkout@v4 + + - name: 安装 Rust nightly + Miri 组件 + uses: dtolnay/rust-toolchain@nightly + with: + components: miri + + - name: 缓存 Cargo 编译产物 + uses: Swatinem/rust-cache@v2 + + # Reason: 只对纯算法 crate(sm3/sm4/sm2)运行 Miri, + # 避免对系统调用密集的代码产生误报。 + # -Zmiri-strict-provenance 开启严格指针来源检查。 + - name: Miri 检查 sm3 + run: cargo miri test -p sm3 + env: + MIRIFLAGS: "-Zmiri-strict-provenance" + + - name: Miri 检查 sm4 + run: cargo miri test -p sm4 + env: + MIRIFLAGS: "-Zmiri-strict-provenance" + + - name: Miri 检查 sm2(仅单元测试,跳过 alloc 密集测试) + run: cargo miri test -p sm2 --lib + env: + MIRIFLAGS: "-Zmiri-strict-provenance" + + # ============================================================================= + # Job 7: 测试覆盖率(仅 Linux,上传 Codecov) # ============================================================================= coverage: name: Coverage @@ -165,7 +266,7 @@ jobs: # Reason: fail_ci_if_error=false 避免 Codecov 暂时不可用时阻断 CI # ============================================================================= - # Job 5: 性能回归检测(仅 PR 触发) + # Job 8: 性能回归检测(仅 PR 触发) # ============================================================================= benchmark: name: Benchmark diff --git a/.gitignore b/.gitignore index 8516044..6c4b308 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -/reference \ No newline at end of file +/reference +fuzz/target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4679a07..b7dc347 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ dependencies = [ "ark-serialize", "ark-std", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools 0.13.0", "num-bigint", @@ -74,7 +74,7 @@ checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ "ark-std", "arrayvec", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -100,6 +100,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "blobby" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1" + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -161,6 +182,17 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "blobby", + "crypto-common 0.2.1", + "inout", +] + [[package]] name = "clap" version = "4.5.60" @@ -186,6 +218,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "criterion" version = "0.5.1" @@ -273,13 +311,34 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" +dependencies = [ + "blobby", + "block-buffer", + "const-oid", + "crypto-common 0.2.1", ] [[package]] @@ -370,12 +429,36 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hex-literal" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +[[package]] +name = "hybrid-array" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +dependencies = [ + "typenum", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -444,6 +527,7 @@ dependencies = [ "crypto-bigint", "getrandom", "hex", + "proptest", "rand 0.8.5", "rand_core 0.6.4", "rustls", @@ -557,6 +641,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + [[package]] name = "quote" version = "1.0.45" @@ -573,7 +671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -596,6 +694,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -611,6 +719,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rayon" version = "1.11.0" @@ -771,6 +888,44 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" + +[[package]] +name = "sm2" +version = "0.1.0" +dependencies = [ + "crypto-bigint", + "digest 0.11.1", + "hex-literal 0.4.1", + "rand 0.8.5", + "rand_core 0.6.4", + "signature", + "sm3", + "subtle", + "zeroize", +] + +[[package]] +name = "sm3" +version = "0.1.0" +dependencies = [ + "digest 0.11.1", + "hex-literal 0.4.1", +] + +[[package]] +name = "sm4" +version = "0.1.0" +dependencies = [ + "cipher", + "hex-literal 0.4.1", + "zeroize", +] + [[package]] name = "sm9_core" version = "0.5.0" @@ -780,7 +935,7 @@ dependencies = [ "ark-ff", "byteorder", "crunchy", - "hex-literal", + "hex-literal 1.1.0", "lazy_static", "num-traits", "rand 0.9.2", @@ -825,6 +980,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 0e0651f..54054f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,22 @@ +# ── Workspace 配置 ────────────────────────────────────────────────────────── +[workspace] +resolver = "2" +members = [ + ".", # libsmx (现有单体 crate,保持向后兼容) + "sm2", # SM2 独立 crate(实现 signature::Signer/Verifier) + "sm3", # SM3 独立 crate(实现 digest::Digest) + "sm4", # SM4 独立 crate(实现 cipher::BlockCipher) +] + +[workspace.dependencies] +digest = { version = "0.11.1", default-features = false } +cipher = { version = "0.5.1", default-features = false } +subtle = { version = "2.6", default-features = false } +zeroize = { version = "1.8", default-features = false, features = ["derive"] } +signature = { version = "2.2", default-features = false } +hex-literal = "0.4" + +# ── libsmx Package 配置(保留现有不变)──────────────────────────────────── [package] name = "libsmx" version = "0.3.0" @@ -52,6 +71,7 @@ hex = "0.4" criterion = { version = "0.5", features = ["html_reports"] } rand = "0.8" sm9_core = "0.5.0" +proptest = { version = "1", default-features = false, features = ["alloc"] } # rustls-provider 集成测试依赖(通过 feature 条件编译保护) rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["log", "webpki"] } pki-types = { package = "rustls-pki-types", version = "1", features = ["alloc"] } diff --git a/benches/sm4_bench.rs b/benches/sm4_bench.rs index fb888fc..8c6add3 100644 --- a/benches/sm4_bench.rs +++ b/benches/sm4_bench.rs @@ -1,15 +1,14 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use libsmx::sm4::{sm4_encrypt_ecb, Sm4Key}; +use libsmx::sm4::Sm4Key; -fn bench_sm4_ecb(c: &mut Criterion) { - let mut group = c.benchmark_group("SM4-ECB"); +fn bench_sm4_block_encrypt(c: &mut Criterion) { + let mut group = c.benchmark_group("SM4-block"); let key = [0u8; 16]; - for size in [16usize, 1024, 65536] { - let data = vec![0u8; size]; - group.bench_with_input(BenchmarkId::new("encrypt", size), &data, |b, d| { - b.iter(|| sm4_encrypt_ecb(&key, d)); - }); - } + let sm4 = Sm4Key::new(&key); + group.bench_function("encrypt_block", |b| { + let mut block = [0u8; 16]; + b.iter(|| sm4.encrypt_block(&mut block)); + }); group.finish(); } @@ -20,5 +19,22 @@ fn bench_sm4_key_new(c: &mut Criterion) { }); } -criterion_group!(benches, bench_sm4_ecb, bench_sm4_key_new); +fn bench_sm4_throughput(c: &mut Criterion) { + let mut group = c.benchmark_group("SM4-throughput"); + let key = [0u8; 16]; + let sm4 = Sm4Key::new(&key); + for size in [16usize, 1024, 65536] { + let mut data = vec![0u8; size]; + group.bench_with_input(BenchmarkId::new("encrypt", size), &size, |b, _| { + b.iter(|| { + for chunk in data.chunks_exact_mut(16) { + sm4.encrypt_block(chunk.try_into().unwrap()); + } + }); + }); + } + group.finish(); +} + +criterion_group!(benches, bench_sm4_block_encrypt, bench_sm4_key_new, bench_sm4_throughput); criterion_main!(benches); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..597737a --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "libsmx-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +# Reason: 空 [workspace] 使 fuzz crate 独立于根 workspace, +# 这是 cargo-fuzz 的标准做法(需要 nightly 且不应影响 stable CI)。 +[workspace] + +[dependencies] +libfuzzer-sys = "0.4" +libsmx = { path = ".." } +sm2 = { path = "../sm2" } +sm3 = { path = "../sm3" } +sm4 = { path = "../sm4" } + +[[bin]] +name = "fuzz_sm3_digest" +path = "fuzz_targets/fuzz_sm3_digest.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_sm4_roundtrip" +path = "fuzz_targets/fuzz_sm4_roundtrip.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_sm2_verify" +path = "fuzz_targets/fuzz_sm2_verify.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_sm2_verify.rs b/fuzz/fuzz_targets/fuzz_sm2_verify.rs new file mode 100644 index 0000000..3ac972f --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_sm2_verify.rs @@ -0,0 +1,22 @@ +//! Fuzz target: SM2 验签不 panic(接受任意字节输入) +//! +//! 对任意输入调用 sm2::verify 不应 panic,只能返回 Ok 或 Err。 +//! 验证实现对格式错误输入的健壮性。 + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // 需要至少 32 + 65 + 64 = 161 字节 + if data.len() < 161 { + return; + } + + let e: &[u8; 32] = data[..32].try_into().unwrap(); + let pub_key: &[u8; 65] = data[32..97].try_into().unwrap(); + let sig: &[u8; 64] = data[97..161].try_into().unwrap(); + + // 只要不 panic 即可;Ok 或 Err 都是合法结果 + let _ = sm2::verify(e, pub_key, sig); +}); diff --git a/fuzz/fuzz_targets/fuzz_sm3_digest.rs b/fuzz/fuzz_targets/fuzz_sm3_digest.rs new file mode 100644 index 0000000..9a3f972 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_sm3_digest.rs @@ -0,0 +1,22 @@ +//! Fuzz target: SM3 一次性哈希与流式哈希必须一致 +//! +//! 验证 Sm3::digest(data) == 逐字节 Sm3::update + finalize 的结果相同。 + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use sm3::Digest; + +fuzz_target!(|data: &[u8]| { + // 一次性哈希 + let hash1 = sm3::Sm3::digest(data); + + // 流式哈希(逐字节) + let mut h = sm3::Sm3::new(); + for byte in data { + h.update(&[*byte]); + } + let hash2 = h.finalize(); + + assert_eq!(hash1, hash2, "one-shot and streaming SM3 must agree"); +}); diff --git a/fuzz/fuzz_targets/fuzz_sm4_roundtrip.rs b/fuzz/fuzz_targets/fuzz_sm4_roundtrip.rs new file mode 100644 index 0000000..b427cb3 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_sm4_roundtrip.rs @@ -0,0 +1,31 @@ +//! Fuzz target: SM4 加密后解密必须还原原始数据 +//! +//! 验证对任意 key + block,encrypt 后 decrypt 得到原始 block。 + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use sm4::{Sm4, KeyInit, BlockCipherEncrypt, BlockCipherDecrypt}; +use sm4::cipher::array::Array; + +fuzz_target!(|data: &[u8]| { + // 需要至少 32 字节(16 字节 key + 16 字节 block) + if data.len() < 32 { + return; + } + + let key: [u8; 16] = data[..16].try_into().unwrap(); + let block_data: [u8; 16] = data[16..32].try_into().unwrap(); + + let cipher = Sm4::new(&Array::from(key)); + let mut block = Array::from(block_data); + let original = block.clone(); + + // 加密 + cipher.encrypt_block(&mut block); + // 解密 + cipher.decrypt_block(&mut block); + + // 还原检查 + assert_eq!(block, original, "SM4 encrypt then decrypt must restore original"); +}); diff --git a/sm2/Cargo.toml b/sm2/Cargo.toml new file mode 100644 index 0000000..e8f3041 --- /dev/null +++ b/sm2/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "sm2" +version = "0.1.0" +edition = "2021" +rust-version = "1.83.0" +license = "Apache-2.0" +description = "SM2 (ShangMi 2) elliptic curve cryptography — signature, encryption, key exchange (GB/T 32918-2016). Pure-Rust, no_std." +repository = "https://github.com/kintaiW/libsmx" +documentation = "https://docs.rs/sm2" +readme = "README.md" +categories = ["cryptography", "no-std"] +keywords = ["crypto", "ecc", "sm2", "shangmi", "signature"] + +[dependencies] +# SM3 is used by SM2 for Z-value and message digest computation +sm3 = { path = "../sm3" } +# digest::Digest + Update traits (re-exported by sm3, but listed explicitly for clarity) +digest = { workspace = true } +crypto-bigint = { version = "0.6", default-features = false } +subtle = { workspace = true } +zeroize = { workspace = true } +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +# signature trait integration +signature = { workspace = true } + +[dev-dependencies] +hex-literal = { workspace = true } +rand = { version = "0.8", default-features = false, features = ["std_rng"] } + +[features] +default = ["alloc"] +# alloc: required for encrypt/decrypt (Vec-based ciphertext) and DER encoding +alloc = [] +# hazmat: exposes sign_with_k (dangerous raw-k API, for testing only) +hazmat = [] diff --git a/sm2/README.md b/sm2/README.md new file mode 100644 index 0000000..d8b6865 --- /dev/null +++ b/sm2/README.md @@ -0,0 +1,223 @@ +# sm2 + +**SM2 椭圆曲线公钥密码算法** — 纯 Rust、`no_std`、常量时间实现,符合 GB/T 32918-2016。 + +**SM2 Elliptic Curve Public-Key Cryptography** — Pure-Rust, `no_std`, constant-time implementation conforming to GB/T 32918-2016. + +[![Crates.io](https://img.shields.io/crates/v/sm2.svg)](https://crates.io/crates/sm2) +[![Docs.rs](https://docs.rs/sm2/badge.svg)](https://docs.rs/sm2) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](../LICENSE) + +--- + +## 目录 / Table of Contents + +- [功能 / Features](#功能--features) +- [快速开始 / Quick Start](#快速开始--quick-start) +- [API 概览 / API Overview](#api-概览--api-overview) +- [安全性 / Security](#安全性--security) +- [Feature 标志 / Feature Flags](#feature-标志--feature-flags) +- [依赖 / Dependencies](#依赖--dependencies) + +--- + +## 功能 / Features + +**中文:** 本 crate 实现以下 GB/T 32918-2016 标准算法: + +**English:** This crate implements the following GB/T 32918-2016 standard algorithms: + +| 功能 / Feature | 函数/类型 / Function / Type | 标准 / Standard | +|---|---|---| +| 密钥生成 / Key Generation | `generate_keypair` | GB/T 32918.1 §6.1 | +| 数字签名 / Digital Signature | `sign`, `sign_message`, `SigningKey` | GB/T 32918.2 §6.2 | +| 签名验证 / Verification | `verify`, `verify_message`, `VerifyingKey` | GB/T 32918.2 §6.3 | +| 公钥加密 / Encryption | `encrypt` | GB/T 32918.4 §7.1 | +| 公钥解密 / Decryption | `decrypt` | GB/T 32918.4 §7.2 | +| 密钥交换 / Key Exchange | `key_exchange::ecdh`, `exchange_a/b` | GB/T 32918.3 | +| DER 编解码 / DER Encoding | `der::sig_to_der`, `private_key_from_pkcs8_der` | RFC 5915/5480 | + +--- + +## 快速开始 / Quick Start + +在 `Cargo.toml` 中添加 / Add to your `Cargo.toml`: + +```toml +[dependencies] +sm2 = { path = "path/to/sm2" } +# 或 crates.io 发布后 / or after crates.io release: +# sm2 = "0.1" +``` + +### 签名与验签 / Sign and Verify + +```rust +use sm2::{SigningKey, VerifyingKey, DEFAULT_ID, generate_keypair}; +use sm2::signature::{Signer, Verifier}; +use rand_core::OsRng; + +// 生成密钥对 / Generate key pair +let (private_key, public_key_bytes) = generate_keypair(&mut OsRng); + +// 创建签名/验证密钥 / Create signing and verifying keys +let signing_key = SigningKey::new(private_key, DEFAULT_ID); +let verifying_key = VerifyingKey::new(public_key_bytes, DEFAULT_ID); + +// 签名 / Sign +let message = b"Hello, SM2!"; +let signature = signing_key.sign(message); + +// 验签 / Verify +verifying_key.verify(message, &signature).expect("验签应通过 / verification should pass"); +``` + +### 底层签名 API / Low-level Signing API + +```rust +use sm2::{PrivateKey, generate_keypair, get_z, get_e, sign, verify, DEFAULT_ID}; +use rand_core::OsRng; + +let (pri_key, pub_key) = generate_keypair(&mut OsRng); + +// 计算 Z 值和摘要 / Compute Z-value and digest +let z = get_z(DEFAULT_ID, &pub_key); +let e = get_e(&z, b"my message"); + +// 签名 / Sign +let sig = sign(&e, &pri_key, &mut OsRng); + +// 验签 / Verify +verify(&e, &pub_key, &sig).expect("ok"); +``` + +### 公钥加解密 / Public-key Encrypt / Decrypt + +```rust +# #[cfg(feature = "alloc")] +use sm2::{encrypt, decrypt, generate_keypair}; +use rand_core::OsRng; + +let (pri_key, pub_key) = generate_keypair(&mut OsRng); +let plaintext = b"secret message"; + +let ciphertext = encrypt(&pub_key, plaintext, &mut OsRng).unwrap(); +let recovered = decrypt(&pri_key, &ciphertext).unwrap(); +assert_eq!(recovered, plaintext); +``` + +密文格式为 `C1 || C3 || C2`(65 + 32 + n 字节),符合 GB/T 32918.4 §6.1。 + +Ciphertext format: `C1 || C3 || C2` (65 + 32 + n bytes), per GB/T 32918.4 §6.1. + +### SM2-ECDH 密钥交换 / Key Exchange + +```rust +use sm2::{generate_keypair, key_exchange::ecdh}; +use rand_core::OsRng; + +let (pri_a, pub_a) = generate_keypair(&mut OsRng); +let (pri_b, pub_b) = generate_keypair(&mut OsRng); + +// 双方各自计算,结果一致 / Both parties compute the same shared secret +let shared_a = ecdh(&pri_a, &pub_b).unwrap(); +let shared_b = ecdh(&pri_b, &pub_a).unwrap(); +assert_eq!(shared_a, shared_b); +``` + +--- + +## API 概览 / API Overview + +### 类型 / Types + +| 类型 / Type | 说明 / Description | +|---|---| +| `PrivateKey` | SM2 私钥(32 字节,离开作用域自动清零)/ SM2 private key (auto-zeroized on drop) | +| `SigningKey<'id>` | 签名密钥(私钥 + 用户 ID)/ Signing key (private key + user ID) | +| `VerifyingKey<'id>` | 验证密钥(公钥 + 用户 ID)/ Verifying key (public key + user ID) | +| `Sm2Signature` | 签名结果(r\|\|s,64 字节)/ Signature (r\|\|s, 64 bytes) | +| `key_exchange::EphemeralKey` | 密钥交换临时密钥对 / Ephemeral key pair for key exchange | +| `Error` | 统一错误类型 / Unified error type | + +### 常量 / Constants + +| 常量 / Constant | 值 / Value | 说明 / Description | +|---|---|---| +| `DEFAULT_ID` | `b"1234567812345678"` | GB/T 32918.2 §A.2 示例用户 ID / Example user ID from spec | + +### 关键函数 / Key Functions + +``` +generate_keypair(rng) → (PrivateKey, [u8; 65]) +get_z(id, pub_key) → [u8; 32] +get_e(z, msg) → [u8; 32] +sign(e, pri_key, rng) → [u8; 64] +sign_message(msg, id, pri, rng) → [u8; 64] +verify(e, pub_key, sig) → Result<(), Error> +verify_message(msg, id, pub, sig) → Result<(), Error> +encrypt(pub_key, msg, rng) → Result, Error> // alloc +decrypt(pri_key, ciphertext) → Result, Error> // alloc +``` + +--- + +## 安全性 / Security + +**中文:** + +- **常量时间**:所有私钥相关运算均为常量时间(Montgomery 域算术 + `subtle::ConditionallySelectable`) +- **标量乘法**:固定迭代 256 位,不跳过前导零,防止时序侧信道 +- **自动清零**:`PrivateKey` 离开作用域后自动清零([`ZeroizeOnDrop`]) +- **无 unsafe**:全 crate 使用 `#![forbid(unsafe_code)]` +- **SM4 S-box**:(通过 `sm4` 依赖)使用布尔电路位切片实现,无查表 + +**English:** + +- **Constant-time**: All secret-dependent operations use Montgomery-domain arithmetic + `subtle::ConditionallySelectable` +- **Scalar multiplication**: Iterates all 256 bits regardless of leading zeros — no timing leakage +- **Auto-zeroize**: `PrivateKey` is automatically cleared on drop via [`ZeroizeOnDrop`] +- **No unsafe code**: The entire crate is `#![forbid(unsafe_code)]` +- **Bitslice S-box**: (via `sm4` dependency) uses boolean-circuit implementation, no table lookups + +> **危险 API / Dangerous API**: `sign_with_k` 仅在启用 `hazmat` feature 时可用,用于测试向量验证。误用相同 k 值会泄露私钥。 +> +> `sign_with_k` is only available with the `hazmat` feature, intended for test-vector validation only. Reusing k across signatures leaks the private key. + +--- + +## Feature 标志 / Feature Flags + +| Feature | 默认启用 / Default | 说明 / Description | +|---|---|---| +| `alloc` | ✅ | 启用 `encrypt`/`decrypt` 和 DER 编码(需要 `Vec`)/ Enables `encrypt`/`decrypt` and DER encoding (requires `Vec`) | +| `hazmat` | ❌ | 暴露 `sign_with_k`(危险的固定 k 签名,仅用于测试)/ Exposes `sign_with_k` (dangerous fixed-k signing, test only) | + +--- + +## 依赖 / Dependencies + +| Crate | 版本 / Version | 用途 / Purpose | +|---|---|---| +| `sm3` | workspace | SM3 哈希(Z 值、消息摘要、KDF)/ SM3 hash (Z-value, digest, KDF) | +| `crypto-bigint` | 0.6 | 常量时间大整数(Montgomery 域)/ Constant-time big integers | +| `subtle` | 2.6 | 常量时间比较 / Constant-time comparisons | +| `zeroize` | 1.8 | 密钥安全清零 / Secure key zeroization | +| `rand_core` | 0.6 | RNG trait(含 OsRng)/ RNG traits (including OsRng) | +| `signature` | 2.2 | `Signer`/`Verifier` trait 集成 / `Signer`/`Verifier` trait integration | + +--- + +## 许可证 / License + +Apache-2.0 — 见 / see [`LICENSE`](../LICENSE) + +--- + +## 参考标准 / Reference Standards + +- GB/T 32918.1-2016:SM2 公钥密码算法 第1部分:总则 +- GB/T 32918.2-2016:SM2 公钥密码算法 第2部分:数字签名算法 +- GB/T 32918.3-2016:SM2 公钥密码算法 第3部分:密钥交换协议 +- GB/T 32918.4-2016:SM2 公钥密码算法 第4部分:公钥加密算法 +- GB/T 32918.5-2017:SM2 公钥密码算法 第5部分:参数定义 diff --git a/sm2/src/der.rs b/sm2/src/der.rs new file mode 100644 index 0000000..9e1a70b --- /dev/null +++ b/sm2/src/der.rs @@ -0,0 +1,533 @@ +//! SM2 签名与密钥 DER 编解码 +//! +//! ## 签名格式 +//! TLS 使用 ASN.1 DER 格式表示签名: +//! ```text +//! SEQUENCE { +//! INTEGER r, +//! INTEGER s +//! } +//! ``` +//! 而 libsmx 内部使用原始 `r||s`(64 字节)。本模块提供两者互转。 +//! +//! ## 私钥格式 +//! - **SEC1**(RFC 5915):`ECPrivateKey SEQUENCE { version INTEGER(1), privateKey OCTET STRING, ... }` +//! - **PKCS#8**(RFC 5958):`PrivateKeyInfo SEQUENCE { version INTEGER(0), algorithm, privateKey OCTET STRING(SEC1) }` +//! +//! ## 公钥 SPKI 格式 +//! rustls `SigningKey::public_key()` 需要 `SubjectPublicKeyInfoDer`: +//! ```text +//! SEQUENCE { +//! SEQUENCE { +//! OID id-ecPublicKey (1.2.840.10045.2.1) +//! OID SM2 (1.2.156.10197.1.301) +//! } +//! BIT STRING (04 || x(32B) || y(32B)) +//! } +//! ``` +//! +//! ## DER INTEGER 编码规则 +//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数) +//! - tag = 0x02,length 占 1 字节(r/s < 256 位时长度 ≤ 33) +//! - SEQUENCE tag = 0x30 + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crate::error::Error; +use crate::PrivateKey; + +/// 将原始签名 `r||s`(64 字节)编码为 DER SEQUENCE +/// +/// 输出格式:`30 02 02 ` +#[cfg(feature = "alloc")] +pub fn sig_to_der(raw: &[u8; 64]) -> Vec { + let r = &raw[..32]; + let s = &raw[32..]; + + let r_enc = encode_integer(r); + let s_enc = encode_integer(s); + + let inner_len = r_enc.len() + s_enc.len(); + let mut der = Vec::with_capacity(2 + inner_len); + der.push(0x30); // SEQUENCE tag + der.push(inner_len as u8); // SEQUENCE length(inner < 256 字节) + der.extend_from_slice(&r_enc); + der.extend_from_slice(&s_enc); + der +} + +/// 将 DER 编码签名解码为原始 `r||s`(64 字节) +/// +/// # 错误 +/// 格式不合法时返回 `Error::InvalidSignature` +pub fn sig_from_der(der: &[u8]) -> Result<[u8; 64], Error> { + let err = || Error::InvalidSignature; + + // SEQUENCE tag + let (tag, rest) = split_first(der).ok_or_else(err)?; + if *tag != 0x30 { + return Err(err()); + } + + // SEQUENCE length + let (seq_len, rest) = split_first(rest).ok_or_else(err)?; + let seq_len = *seq_len as usize; + if rest.len() < seq_len { + return Err(err()); + } + let body = &rest[..seq_len]; + + // 解析 r + let (r_bytes, body) = decode_integer(body).ok_or_else(err)?; + + // 解析 s + let (s_bytes, body) = decode_integer(body).ok_or_else(err)?; + + // 不应有多余数据 + if !body.is_empty() { + return Err(err()); + } + + // r 和 s 都必须是正整数,不超过 32 字节 + if r_bytes.is_empty() || r_bytes.len() > 33 || s_bytes.is_empty() || s_bytes.len() > 33 { + return Err(err()); + } + + let mut raw = [0u8; 64]; + // Reason: DER INTEGER 可能有前缀 0x00(最高位保护),去除后左对齐写入 32 字节槽 + let r_stripped = strip_leading_zero(r_bytes); + let s_stripped = strip_leading_zero(s_bytes); + if r_stripped.len() > 32 || s_stripped.len() > 32 { + return Err(err()); + } + let r_off = 32 - r_stripped.len(); + let s_off = 32 - s_stripped.len(); + raw[r_off..32].copy_from_slice(r_stripped); + raw[32 + s_off..64].copy_from_slice(s_stripped); + + Ok(raw) +} + +// ── 内部辅助 ────────────────────────────────────────────────────────────────── + +/// 将 32 字节大端整数编码为 DER INTEGER(带 tag 0x02 和 length) +#[cfg(feature = "alloc")] +fn encode_integer(bytes: &[u8]) -> Vec { + // 去除前导零(至少保留 1 字节) + let start = bytes + .iter() + .position(|&b| b != 0) + .unwrap_or(bytes.len() - 1); + let val = &bytes[start..]; + + // 最高位为 1 时需补 0x00,防止被解析为负数 + let needs_pad = val[0] & 0x80 != 0; + let val_len = val.len() + if needs_pad { 1 } else { 0 }; + + let mut enc = Vec::with_capacity(2 + val_len); + enc.push(0x02); // INTEGER tag + enc.push(val_len as u8); // length + if needs_pad { + enc.push(0x00); + } + enc.extend_from_slice(val); + enc +} + +/// 从字节流中解析一个 DER INTEGER,返回 (value_bytes, 剩余字节) +fn decode_integer(data: &[u8]) -> Option<(&[u8], &[u8])> { + let (tag, rest) = split_first(data)?; + if *tag != 0x02 { + return None; + } + let (len, rest) = split_first(rest)?; + let len = *len as usize; + if rest.len() < len { + return None; + } + Some((&rest[..len], &rest[len..])) +} + +/// 去除前导 0x00 字节 +fn strip_leading_zero(bytes: &[u8]) -> &[u8] { + match bytes.iter().position(|&b| b != 0) { + Some(i) => &bytes[i..], + None => &bytes[bytes.len().saturating_sub(1)..], // 全零时保留末字节 + } +} + +fn split_first(data: &[u8]) -> Option<(&u8, &[u8])> { + data.split_first() +} + +// ── DER 长度解码 ────────────────────────────────────────────────────────────── + +/// 解析 DER 长度字段,返回 (length, 剩余字节) +/// +/// 支持:单字节(< 0x80)、两字节(0x81 nn)、三字节(0x82 nn nn) +fn parse_length(data: &[u8]) -> Option<(usize, &[u8])> { + let (first, rest) = data.split_first()?; + if *first < 0x80 { + // Reason: 最高位为 0 时,本字节直接表示长度 + Some((*first as usize, rest)) + } else if *first == 0x81 { + let (len, rest) = rest.split_first()?; + Some((*len as usize, rest)) + } else if *first == 0x82 { + if rest.len() < 2 { + return None; + } + let len = (rest[0] as usize) << 8 | rest[1] as usize; + Some((len, &rest[2..])) + } else { + // 不支持更长或不定长编码 + None + } +} + +/// 解析一个 TLV(tag-length-value),返回 (value_bytes, 剩余字节) +fn parse_tlv(data: &[u8], expected_tag: u8) -> Option<(&[u8], &[u8])> { + let (tag, rest) = data.split_first()?; + if *tag != expected_tag { + return None; + } + let (len, rest) = parse_length(rest)?; + if rest.len() < len { + return None; + } + Some((&rest[..len], &rest[len..])) +} + +// ── 私钥 DER 解析 ───────────────────────────────────────────────────────────── + +/// 从 SEC1 DER 解析 SM2 私钥(RFC 5915) +/// +/// 格式: +/// ```text +/// ECPrivateKey ::= SEQUENCE { +/// version INTEGER { ecPrivkeyVer1(1) }, +/// privateKey OCTET STRING, -- 32 字节原始私钥 +/// [0] ECParameters OPTIONAL, +/// [1] BIT STRING OPTIONAL +/// } +/// ``` +/// +/// # 错误 +/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey` +pub fn private_key_from_sec1_der(der: &[u8]) -> Result { + let err = || Error::InvalidPrivateKey; + + // 解析外层 SEQUENCE + let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?; + + // version INTEGER,值应为 1(ecPrivkeyVer1) + let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?; + if ver_bytes != [0x01] { + return Err(err()); + } + + // privateKey OCTET STRING(32 字节) + let (key_bytes, _rest) = parse_tlv(rest, 0x04).ok_or_else(err)?; + if key_bytes.len() != 32 { + return Err(err()); + } + let key_arr: &[u8; 32] = key_bytes.try_into().map_err(|_| err())?; + + PrivateKey::from_bytes(key_arr) +} + +/// 从 PKCS#8 DER 解析 SM2 私钥(RFC 5958) +/// +/// 格式: +/// ```text +/// PrivateKeyInfo ::= SEQUENCE { +/// version INTEGER (0), +/// algorithm AlgorithmIdentifier SEQUENCE { ... }, +/// privateKey OCTET STRING (SEC1 DER) +/// } +/// ``` +/// +/// # 错误 +/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey` +pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result { + let err = || Error::InvalidPrivateKey; + + // 解析外层 SEQUENCE(PrivateKeyInfo) + let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?; + + // version INTEGER,值应为 0 + let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?; + if ver_bytes != [0x00] { + return Err(err()); + } + + // AlgorithmIdentifier SEQUENCE(跳过,不验证 OID) + let (_, rest) = parse_tlv(rest, 0x30).ok_or_else(err)?; + + // privateKey OCTET STRING(内含 SEC1 DER) + let (sec1_der, _) = parse_tlv(rest, 0x04).ok_or_else(err)?; + + private_key_from_sec1_der(sec1_der) +} + +// ── SM2 公钥 SPKI DER 编码 ──────────────────────────────────────────────────── + +/// 将 SM2 公钥(65 字节,04||x||y)编码为 SubjectPublicKeyInfo DER +/// +/// 格式(RFC 5480): +/// ```text +/// SEQUENCE { +/// SEQUENCE { +/// OID 1.2.840.10045.2.1 (id-ecPublicKey, 7 字节) +/// OID 1.2.156.10197.1.301 (SM2, 8 字节) +/// } +/// BIT STRING 0x00 || pub_key (65 字节 + 1 字节前缀) +/// } +/// ``` +/// +/// 此格式是 rustls `SigningKey::public_key()` 所需的 `SubjectPublicKeyInfoDer`。 +#[cfg(feature = "alloc")] +pub fn public_key_to_spki_der(pub_key: &[u8; 65]) -> Vec { + // OID 1.2.840.10045.2.1 (id-ecPublicKey): 06 07 2a 86 48 ce 3d 02 01 + let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]; + // OID 1.2.156.10197.1.301 (SM2): 06 08 2a 81 1c cf 55 01 82 2d + let oid_sm2: &[u8] = &[0x06, 0x08, 0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d]; + + // AlgorithmIdentifier SEQUENCE + let alg_inner_len = oid_ec.len() + oid_sm2.len(); + let mut alg = Vec::with_capacity(2 + alg_inner_len); + alg.push(0x30); + alg.push(alg_inner_len as u8); + alg.extend_from_slice(oid_ec); + alg.extend_from_slice(oid_sm2); + + // BIT STRING: 0x03 0x00 + // Reason: 0x00 是 unused bits 字段,表示最后一字节无填充位 + let bit_str_len = 1 + pub_key.len(); // 0x00 前缀 + 65 字节公钥 + let mut bit_str = Vec::with_capacity(2 + bit_str_len); + bit_str.push(0x03); + bit_str.push(bit_str_len as u8); + bit_str.push(0x00); // unused bits = 0 + bit_str.extend_from_slice(pub_key); + + // 外层 SEQUENCE + let outer_len = alg.len() + bit_str.len(); + let mut der = Vec::with_capacity(2 + outer_len); + der.push(0x30); + der.push(outer_len as u8); + der.extend_from_slice(&alg); + der.extend_from_slice(&bit_str); + der +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_raw(r: [u8; 32], s: [u8; 32]) -> [u8; 64] { + let mut raw = [0u8; 64]; + raw[..32].copy_from_slice(&r); + raw[32..].copy_from_slice(&s); + raw + } + + #[cfg(feature = "alloc")] + #[test] + fn test_der_roundtrip_basic() { + let r = [0x01u8; 32]; + let s = [0x02u8; 32]; + let raw = make_raw(r, s); + let der = sig_to_der(&raw); + let recovered = sig_from_der(&der).unwrap(); + assert_eq!(recovered, raw); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_der_roundtrip_high_bit_set() { + // r/s 最高位为 1,需要 DER 填充 0x00 + let mut r = [0u8; 32]; + r[0] = 0x80; // 最高位为 1 + let mut s = [0u8; 32]; + s[0] = 0xFF; + let raw = make_raw(r, s); + let der = sig_to_der(&raw); + // 验证 DER 中有 0x00 填充 + let recovered = sig_from_der(&der).unwrap(); + assert_eq!(recovered, raw); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_der_roundtrip_leading_zeros() { + // r 前有大量前导零 + let mut r = [0u8; 32]; + r[31] = 0x42; // 只有最后一字节非零 + let s = [0x01u8; 32]; + let raw = make_raw(r, s); + let der = sig_to_der(&raw); + let recovered = sig_from_der(&der).unwrap(); + assert_eq!(recovered, raw); + } + + #[test] + fn test_der_invalid_tag() { + // 非 SEQUENCE tag + let bad = [0x10, 0x08, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00]; + assert!(sig_from_der(&bad).is_err()); + } + + #[test] + fn test_der_truncated() { + let bad = [0x30, 0x10]; // length 声明 16 字节但无内容 + assert!(sig_from_der(&bad).is_err()); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_der_structure() { + // 验证 DER 字节结构符合 ASN.1 规范 + let r = [0x01u8; 32]; + let s = [0x01u8; 32]; + let raw = make_raw(r, s); + let der = sig_to_der(&raw); + assert_eq!(der[0], 0x30); // SEQUENCE + assert_eq!(der[2], 0x02); // INTEGER tag for r + // 长度字段合理(r/s 各最多 33 字节 + 2 字节头 = 35,×2 + 2 = 72) + assert!(der.len() <= 72); + assert!(der.len() >= 8); + } + + // ── 私钥 DER 解析测试 ────────────────────────────────────────────────────── + + // 已知 SM2 私钥原始字节(与其他测试共用) + const RAW_KEY: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, 0x9f, + 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, 0x4d, 0xf7, + 0xc5, 0xb8, + ]; + + /// 构造最小 SEC1 DER(只有 version + privateKey 字段) + #[cfg(feature = "alloc")] + fn make_sec1_der(key: &[u8; 32]) -> alloc::vec::Vec { + // version INTEGER = 1:02 01 01 + // privateKey OCTET STRING:04 20 <32 bytes> + // inner = 3 + 2 + 32 = 37 bytes → SEQUENCE 30 25 ... + let mut der = alloc::vec![0x30u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20]; + der.extend_from_slice(key); + der + } + + /// 构造最小 PKCS#8 DER(包含虚拟 AlgorithmIdentifier OID) + #[cfg(feature = "alloc")] + fn make_pkcs8_der(key: &[u8; 32]) -> alloc::vec::Vec { + let sec1 = make_sec1_der(key); + // AlgorithmIdentifier 最小化:30 06 06 01 00 06 01 00(两个 OID,各 1 字节占位) + let alg_id: &[u8] = &[0x30, 0x06, 0x06, 0x01, 0x00, 0x06, 0x01, 0x00]; + // version INTEGER = 0:02 01 00 + let version: &[u8] = &[0x02, 0x01, 0x00]; + // privateKey OCTET STRING 包装 sec1 + let mut priv_oct = alloc::vec![0x04u8, sec1.len() as u8]; + priv_oct.extend_from_slice(&sec1); + // inner = version + alg_id + priv_oct + let inner_len = version.len() + alg_id.len() + priv_oct.len(); + let mut der = alloc::vec![0x30u8, inner_len as u8]; + der.extend_from_slice(version); + der.extend_from_slice(alg_id); + der.extend_from_slice(&priv_oct); + der + } + + #[cfg(feature = "alloc")] + #[test] + fn test_sec1_der_roundtrip() { + let der = make_sec1_der(&RAW_KEY); + let key = private_key_from_sec1_der(&der).expect("SEC1 解析应成功"); + assert_eq!(key.as_bytes(), &RAW_KEY); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_pkcs8_der_roundtrip() { + let der = make_pkcs8_der(&RAW_KEY); + let key = private_key_from_pkcs8_der(&der).expect("PKCS#8 解析应成功"); + assert_eq!(key.as_bytes(), &RAW_KEY); + } + + #[test] + fn test_sec1_der_invalid_tag() { + // 首字节不是 SEQUENCE tag + let bad = [0x02u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, 0x00]; + assert!(private_key_from_sec1_der(&bad).is_err()); + } + + #[test] + fn test_sec1_der_wrong_version() { + // version 应为 1,此处给 0;最后 32 字节填充为 RAW_KEY + let mut der = [0u8; 39]; + der[0] = 0x30; + der[1] = 0x25; // SEQUENCE length 37 + der[2] = 0x02; + der[3] = 0x01; + der[4] = 0x00; // version = 0(错误,应为 1) + der[5] = 0x04; + der[6] = 0x20; // OCTET STRING 32 字节 + der[7..39].copy_from_slice(&RAW_KEY); + assert!(private_key_from_sec1_der(&der).is_err()); + } + + #[test] + fn test_sec1_der_key_too_short() { + // privateKey 只有 16 字节(不足 32) + let der = [ + 0x30, 0x15, // SEQUENCE 21 字节 + 0x02, 0x01, 0x01, // version = 1 + 0x04, 0x10, // OCTET STRING 16 字节 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + assert!(private_key_from_sec1_der(&der).is_err()); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_pkcs8_der_invalid_outer_tag() { + let mut der = make_pkcs8_der(&RAW_KEY); + der[0] = 0x04; // 破坏外层 SEQUENCE tag + assert!(private_key_from_pkcs8_der(&der).is_err()); + } + + // ── SPKI DER 测试 ────────────────────────────────────────────────────────── + + #[cfg(feature = "alloc")] + #[test] + fn test_spki_der_structure() { + use crate::PrivateKey; + let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap(); + let pub_key = pri.public_key(); + let spki = public_key_to_spki_der(&pub_key); + + // 外层 SEQUENCE + assert_eq!(spki[0], 0x30, "外层 tag 应为 SEQUENCE"); + // BIT STRING 内包含 04||x||y(65字节) + // 确认公钥原始字节出现在 SPKI 中 + let pos = spki.windows(65).position(|w| w == pub_key); + assert!(pos.is_some(), "SPKI 应包含原始公钥字节"); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_spki_der_oid_ec() { + use crate::PrivateKey; + let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap(); + let pub_key = pri.public_key(); + let spki = public_key_to_spki_der(&pub_key); + // id-ecPublicKey OID bytes + let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]; + assert!( + spki.windows(oid_ec.len()).any(|w| w == oid_ec), + "SPKI 应包含 id-ecPublicKey OID" + ); + } +} diff --git a/sm2/src/ec.rs b/sm2/src/ec.rs new file mode 100644 index 0000000..2149310 --- /dev/null +++ b/sm2/src/ec.rs @@ -0,0 +1,610 @@ +//! SM2 椭圆曲线点运算(GB/T 32918.1-2016 §4.2) +//! +//! 使用 Jacobian 射影坐标(X:Y:Z),仿射坐标满足 x = X/Z², y = Y/Z³。 +//! 避免热路径中的 Fp 求逆运算,性能优于仿射坐标加法。 + +use crypto_bigint::U256; +use subtle::{Choice, ConditionallySelectable}; + +use crate::error::Error; +use crate::field::{ + fp_add, fp_from_bytes, fp_inv, fp_mul, fp_neg, fp_square, fp_sub, fp_to_bytes, Fp, CURVE_A, + CURVE_B, FIELD_MODULUS, GX, GY, +}; + +// ── 仿射坐标点 ──────────────────────────────────────────────────────────────── + +/// SM2 曲线上的仿射坐标点(公开类型,用于序列化/反序列化) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct AffinePoint { + /// x 坐标 + pub x: Fp, + /// y 坐标 + pub y: Fp, +} + +// ── Jacobian 射影坐标点(内部运算专用)──────────────────────────────────────── + +/// SM2 曲线上的 Jacobian 射影坐标点(内部使用) +/// +/// 仿射点 (x, y) 对应射影点 (X:Y:Z) 满足 x = X/Z², y = Y/Z³ +#[derive(Clone, Copy, Debug)] +pub struct JacobianPoint { + pub(crate) x: Fp, + pub(crate) y: Fp, + pub(crate) z: Fp, +} + +// ── Jacobian 常量时间选择 ────────────────────────────────────────────────────── + +/// 为 JacobianPoint 实现常量时间条件选择 +/// +/// Reason: 标量乘中用掩码选择替代 if/else,消除基于标量位的条件分支。 +impl ConditionallySelectable for JacobianPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + JacobianPoint { + x: Fp::conditional_select(&a.x, &b.x, choice), + y: Fp::conditional_select(&a.y, &b.y, choice), + z: Fp::conditional_select(&a.z, &b.z, choice), + } + } +} + +impl JacobianPoint { + /// 无穷远点(群的单位元),用 Z=0 表示 + pub const INFINITY: Self = JacobianPoint { + x: Fp::ONE, + y: Fp::ONE, + z: Fp::ZERO, + }; + + /// 从仿射坐标构造(Z=1) + pub fn from_affine(p: &AffinePoint) -> Self { + JacobianPoint { + x: p.x, + y: p.y, + z: Fp::ONE, + } + } + + /// 转换为仿射坐标(需要一次 Fp 求逆,仅在最终输出时使用) + pub fn to_affine(&self) -> Result { + if self.is_infinity() { + return Err(Error::PointAtInfinity); + } + let z_inv = fp_inv(&self.z).ok_or(Error::PointAtInfinity)?; + let z_inv2 = fp_square(&z_inv); + let z_inv3 = fp_mul(&z_inv2, &z_inv); + Ok(AffinePoint { + x: fp_mul(&self.x, &z_inv2), + y: fp_mul(&self.y, &z_inv3), + }) + } + + /// 判断是否为无穷远点(常量时间,公开接口) + pub fn is_infinity(&self) -> bool { + bool::from(self.ct_is_infinity()) + } + + /// 常量时间无穷远判断(内部辅助,返回 Choice) + /// + /// Reason: 返回 Choice 供 conditional_select 直接使用,避免 bool 转换后再转回 Choice + fn ct_is_infinity(&self) -> Choice { + // Reason: 用 ConstantTimeEq 比较所有 32 字节,执行时间与 Z 值无关, + // 替代 Iterator::all 的短路求值(后者泄露 Z 坐标前缀信息)。 + use subtle::ConstantTimeEq; + fp_to_bytes(&self.z).ct_eq(&[0u8; 32]) + } + + /// 点倍运算(Jacobian 坐标,a=-3 优化公式,完全常量时间) + /// + /// 公式来自 + /// SM2 曲线 a = p-3 ≡ -3 (mod p),使用 a=-3 特化公式降低乘法次数。 + /// + /// # 安全性 + /// 无条件执行完整运算,用 `conditional_select` 处理无穷远退化情况, + /// 消除 `if is_infinity()` 分支对标量前导零位的泄露。 + pub fn double(&self) -> Self { + let (x1, y1, z1) = (&self.x, &self.y, &self.z); + + let delta = fp_square(z1); // Z1² + let gamma = fp_square(y1); // Y1² + let beta = fp_mul(x1, &gamma); // X1·Y1² + + // alpha = 3·(X1-delta)·(X1+delta) [a=-3 优化] + let alpha = fp_mul(&fp_sub(x1, &delta), &fp_add(x1, &delta)); + let alpha = fp_add(&fp_add(&alpha, &alpha), &alpha); // 3·alpha + + // X3 = alpha² - 8·beta + let x3 = fp_sub(&fp_square(&alpha), &double2(&double1(&beta))); + + // Z3 = (Y1+Z1)² - gamma - delta + let z3 = fp_sub(&fp_sub(&fp_square(&fp_add(y1, z1)), &gamma), &delta); + + // Y3 = alpha·(4·beta - X3) - 8·gamma² + let gamma2 = fp_square(&gamma); + let y3 = fp_sub( + &fp_mul(&alpha, &fp_sub(&double2(&beta), &x3)), + &double2(&double1(&gamma2)), + ); + + let d = JacobianPoint { + x: x3, + y: y3, + z: z3, + }; + // Reason: 无穷远点的倍点仍为无穷远点;用掩码选择替代 if 分支, + // 避免 scalar_mul 热路径中泄露哪些迭代位为前导零。 + JacobianPoint::conditional_select(&d, self, self.ct_is_infinity()) + } + + /// 点加运算(完全常量时间,无条件分支) + /// + /// 公式来自 + /// + /// # 安全性 + /// 采用"计算所有情况 + 掩码选择"策略,消除全部退化情况的条件分支: + /// - P = ∞ → Q(无穷远加法单位元) + /// - Q = ∞ → P + /// - P = Q → double(P)(相同点,用 ct_eq 检测 H==0 且 R==0) + /// - P = -Q → ∞(互反点,用 ct_eq 检测 H==0 且 R≠0) + /// - 正常情况 → 标准 Jacobian 加法 + /// + /// Reason: 原实现的 3 处 `if` 分支(is_infinity、H==0、R==0) + /// 在 scalar_mul 热路径中泄露标量的汉明重量及位分布。 + pub fn add(p: &JacobianPoint, q: &JacobianPoint) -> JacobianPoint { + use subtle::ConstantTimeEq; + + let z1sq = fp_square(&p.z); + let z2sq = fp_square(&q.z); + let u1 = fp_mul(&p.x, &z2sq); // X1·Z2² + let u2 = fp_mul(&q.x, &z1sq); // X2·Z1² + let s1 = fp_mul(&p.y, &fp_mul(&q.z, &z2sq)); // Y1·Z2³ + let s2 = fp_mul(&q.y, &fp_mul(&p.z, &z1sq)); // Y2·Z1³ + + let h = fp_sub(&u2, &u1); + let r = fp_sub(&s2, &s1); + + // 常量时间零判断(替代 Iterator::all 短路) + let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]); + let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]); + + // 无条件执行标准 Jacobian 加法(当 h==0 时结果为垃圾值,后续掩码覆盖) + let h2 = fp_square(&h); + let h3 = fp_mul(&h, &h2); + let u1h2 = fp_mul(&u1, &h2); + + // X3 = R² - H³ - 2·U1·H² + let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2)); + // Y3 = R·(U1·H² - X3) - S1·H³ + let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&s1, &h3)); + // Z3 = H·Z1·Z2 (当 H==0 时 z3=0,即 INFINITY,与下面掩码一致) + let z3 = fp_mul(&fp_mul(&h, &p.z), &q.z); + let normal = JacobianPoint { + x: x3, + y: y3, + z: z3, + }; + + // 预计算 P==Q 退化情况的结果(无条件执行,结果由掩码决定是否使用) + let double_p = p.double(); + + // 按优先级从低到高用 conditional_select 叠加(后面覆盖前面): + // 优先级 1(最低):正常 Jacobian 加法 + let result = normal; + // 优先级 2:P == -Q → INFINITY(h==0 且 r≠0) + let result = JacobianPoint::conditional_select( + &result, + &JacobianPoint::INFINITY, + h_is_zero & !r_is_zero, + ); + // 优先级 3:P == Q → double(P)(h==0 且 r==0) + let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero); + // 优先级 4:Q 是无穷远 → P(加法单位元) + let result = JacobianPoint::conditional_select(&result, p, q.ct_is_infinity()); + // 优先级 5(最高):P 是无穷远 → Q + JacobianPoint::conditional_select(&result, q, p.ct_is_infinity()) + } + + /// 标量乘 k·P(常量时间,固定 256 位迭代) + /// + /// Reason: 固定迭代次数 + `conditional_select` 掩码选择,消除基于标量位的条件分支, + /// 防止时序侧信道攻击。执行路径与标量 k 的值完全无关。 + pub fn scalar_mul(k: &U256, p: &JacobianPoint) -> JacobianPoint { + let mut result = JacobianPoint::INFINITY; + + // 固定 256 次迭代,不跳过前导零 + for byte in &k.to_be_bytes() { + for b in (0..8).rev() { + // 始终执行倍点(与标量位无关) + result = result.double(); + + // 始终计算加法(与标量位无关) + let sum = JacobianPoint::add(&result, p); + + // Reason: 用掩码选择结果,无条件分支:bit=1 取 sum,bit=0 取 result + let bit = Choice::from((byte >> b) & 1); + result = JacobianPoint::conditional_select(&result, &sum, bit); + } + } + result + } + + /// 基点标量乘 k·G(密钥生成和签名专用,使用 w=4 固定窗口加速) + pub fn scalar_mul_g(k: &U256) -> JacobianPoint { + scalar_mul_g_window(k) + } +} + +// ── 辅助倍增函数(用于 Jacobian 公式中的常数倍计算)──────────────────────── + +#[inline] +fn double1(a: &Fp) -> Fp { + fp_add(a, a) +} + +#[inline] +fn double2(a: &Fp) -> Fp { + let t = double1(a); + double1(&t) +} + +// ── 混合 Jacobian-仿射加法(q.Z = 1 优化)──────────────────────────────────── + +/// 混合点加 P(Jacobian)+ Q(Affine,Z=1) +/// +/// 相比标准 Jacobian+Jacobian 加法,利用 Z_Q=1 省去: +/// - Z2² 计算(1 次 fp_square) +/// - X1·Z2² 简化为 X1(0 次乘法) +/// - Y1·Z2³ 简化为 Y1(0 次乘法) +/// - Z3 中的 Z2 乘法(Z3 = H·Z1,而非 H·Z1·Z2) +/// +/// 共节省约 3~4 次域乘法,用于预计算表构建和 multi_scalar_mul 内循环。 +/// +/// # 安全性 +/// 完全常量时间,退化情况处理与 `JacobianPoint::add` 相同。 +fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint { + use subtle::ConstantTimeEq; + + // Z_Q = 1,故 u1 = X1,s1 = Y1(无需额外乘法) + let z1sq = fp_square(&p.z); // Z1² + let z1cu = fp_mul(&p.z, &z1sq); // Z1³ + let u2 = fp_mul(&q.x, &z1sq); // X2·Z1² + let s2 = fp_mul(&q.y, &z1cu); // Y2·Z1³ + + let h = fp_sub(&u2, &p.x); + let r = fp_sub(&s2, &p.y); + + let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]); + let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]); + + let h2 = fp_square(&h); + let h3 = fp_mul(&h, &h2); + let u1h2 = fp_mul(&p.x, &h2); + + let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2)); + let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&p.y, &h3)); + // Reason: Z_Q = 1,故 Z3 = H·Z1·Z2 = H·Z1,节省一次乘法 + let z3 = fp_mul(&h, &p.z); + let normal = JacobianPoint { + x: x3, + y: y3, + z: z3, + }; + + let double_p = p.double(); + + let result = normal; + let result = JacobianPoint::conditional_select( + &result, + &JacobianPoint::INFINITY, + h_is_zero & !r_is_zero, + ); + let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero); + // P = INFINITY → 返回 Q(注:预计算表中 Q 绝不是无穷远点, + // 但在通用调用中仍需正确处理) + let q_jac = JacobianPoint::from_affine(q); + JacobianPoint::conditional_select(&result, &q_jac, p.ct_is_infinity()) +} + +// ── SM2 基点固定窗口标量乘(w=4)───────────────────────────────────────────── + +/// 基点固定窗口标量乘 k·G(w=4,预计算 15 个点,常量时间) +/// +/// 原理:将 256-bit 标量按 4-bit 切分为 64 个窗口。 +/// 每个窗口先执行 4 次倍点,再常量时间查表做一次加法。 +/// 共需 256 次 double + 64 次 add,相比双倍-加法的 256 次 add 节省约 75%。 +/// +/// Reason: 预计算表仅含 G 的已知倍数(公开常量基点),不依赖秘密输入; +/// 窗口值为秘密标量位,但表查找通过 15 次 `conditional_select` 实现, +/// 不含任何数据依赖分支,保持常量时间性质。 +fn scalar_mul_g_window(k: &U256) -> JacobianPoint { + use subtle::ConstantTimeEq; + + let g_aff = AffinePoint { x: GX, y: GY }; + let g_jac = JacobianPoint::from_affine(&g_aff); + + // 预计算表:table[i] = i·G,i = 0..=15(table[0] = INFINITY,占位不用) + // Reason: 使用 add_mixed 构建表,g_aff 始终 Z=1,节省约 3 次域乘/步 + let mut table = [JacobianPoint::INFINITY; 16]; + table[1] = g_jac; + for i in 2..=15usize { + table[i] = add_mixed(&table[i - 1], &g_aff); + } + + let mut result = JacobianPoint::INFINITY; + for byte in &k.to_be_bytes() { + // ── 高 4 位窗口 ───────────────────────────────────────────────────── + for _ in 0..4 { + result = result.double(); + } + let window = byte >> 4; + // 常量时间表查找:遍历 1..=15,用 ct_eq 选出 table[window] + let mut sel = JacobianPoint::INFINITY; + for j in 1u8..=15 { + let eq = window.ct_eq(&j); + sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq); + } + // window=0 时 sel 仍为 INFINITY,add(result, INFINITY) = result + result = JacobianPoint::add(&result, &sel); + + // ── 低 4 位窗口 ───────────────────────────────────────────────────── + for _ in 0..4 { + result = result.double(); + } + let window = byte & 0xF; + let mut sel = JacobianPoint::INFINITY; + for j in 1u8..=15 { + let eq = window.ct_eq(&j); + sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq); + } + result = JacobianPoint::add(&result, &sel); + } + result +} + +// ── AffinePoint 公开接口 ────────────────────────────────────────────────────── + +impl AffinePoint { + /// SM2 基点 G + pub fn generator() -> Self { + AffinePoint { x: GX, y: GY } + } + + /// 验证点是否在 SM2 曲线上:y² ≡ x³ + ax + b (mod p) + pub fn is_on_curve(&self) -> bool { + let x2 = fp_square(&self.x); + let x3 = fp_mul(&x2, &self.x); + let ax = fp_mul(&CURVE_A, &self.x); + let rhs = fp_add(&fp_add(&x3, &ax), &CURVE_B); + fp_square(&self.y) == rhs + } + + /// 从未压缩格式 04||x||y(65 字节)解析点 + /// + /// 符合 GB/T 32918.1-2016 §4.2.9 + pub fn from_bytes(bytes: &[u8; 65]) -> Result { + if bytes[0] != 0x04 { + return Err(Error::InvalidPublicKey); + } + let x_bytes: [u8; 32] = bytes[1..33].try_into().unwrap(); + let y_bytes: [u8; 32] = bytes[33..65].try_into().unwrap(); + + // 检查坐标在 [0, p-1] 范围内 + use crypto_bigint::subtle::ConstantTimeGreater; + let x_val = U256::from_be_slice(&x_bytes); + let y_val = U256::from_be_slice(&y_bytes); + if bool::from(x_val.ct_gt(&FIELD_MODULUS)) + || x_val == FIELD_MODULUS + || bool::from(y_val.ct_gt(&FIELD_MODULUS)) + || y_val == FIELD_MODULUS + { + return Err(Error::InvalidPublicKey); + } + + let p = AffinePoint { + x: fp_from_bytes(&x_bytes), + y: fp_from_bytes(&y_bytes), + }; + if !p.is_on_curve() { + return Err(Error::InvalidPublicKey); + } + Ok(p) + } + + /// 序列化为未压缩格式 04||x||y(65 字节) + pub fn to_bytes(&self) -> [u8; 65] { + let mut out = [0u8; 65]; + out[0] = 0x04; + out[1..33].copy_from_slice(&fp_to_bytes(&self.x)); + out[33..65].copy_from_slice(&fp_to_bytes(&self.y)); + out + } + + /// 从压缩格式 02/03||x(33 字节)解压缩点 + /// + /// 符合 GB/T 32918.1-2016 §4.2.10 + pub fn decompress(bytes: &[u8; 33]) -> Result { + let prefix = bytes[0]; + if prefix != 0x02 && prefix != 0x03 { + return Err(Error::InvalidPublicKey); + } + let x_bytes: [u8; 32] = bytes[1..33].try_into().unwrap(); + + use crypto_bigint::subtle::ConstantTimeGreater; + let x_val = U256::from_be_slice(&x_bytes); + if bool::from(x_val.ct_gt(&FIELD_MODULUS)) || x_val == FIELD_MODULUS { + return Err(Error::InvalidPublicKey); + } + + let x = fp_from_bytes(&x_bytes); + + // 计算 y² = x³ + ax + b + let x2 = fp_square(&x); + let x3 = fp_mul(&x2, &x); + let ax = fp_mul(&CURVE_A, &x); + let y2 = fp_add(&fp_add(&x3, &ax), &CURVE_B); + + let y = crate::field::fp_sqrt(&y2).ok_or(Error::InvalidPublicKey)?; + + // 按前缀奇偶性选择正确的 y + // prefix 02 → 偶数(LSB=0),prefix 03 → 奇数(LSB=1) + let y_lsb = fp_to_bytes(&y)[31] & 1; + let want_odd = prefix & 1; + let y_final = if y_lsb == want_odd { y } else { fp_neg(&y) }; + + Ok(AffinePoint { x, y: y_final }) + } +} + +// ── 双标量乘:u·G + v·Q(用于签名验证)───────────────────────────────────── + +/// 计算 u·G + v·Q(顺序双标量乘,用于 SM2 验签第 3 步) +/// 双标量乘 u·G + v·Q(Shamir's trick 交错法,用于验签) +/// +/// Reason: 验签时 u、v 均为公开值(非秘密),无需常量时间。 +/// Shamir's trick 预计算 {P, Q, P+Q},每位只需 1 次 double + 最多 1 次 add, +/// 比两次独立标量乘(各 256 次 double + 平均 128 add)快约 25%。 +pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result { + // Reason: u、v 均为验签公开值,non-CT 的 match 分支不泄露秘密; + // 使用 add_mixed(Jacobian, Affine) 替代全量 Jacobian add, + // 节省约 3 次域乘/步,g 和 q 已是仿射坐标直接传入。 + let g = AffinePoint::generator(); + let q_jac = JacobianPoint::from_affine(q); + let g_jac = JacobianPoint::from_affine(&g); + // 预计算 G+Q(Jacobian,含退化处理) + let gq_jac = JacobianPoint::add(&g_jac, &q_jac); + + let u_bytes = u.to_be_bytes(); + let v_bytes = v.to_be_bytes(); + + let mut result = JacobianPoint::INFINITY; + + for i in 0..32 { + let ub = u_bytes[i]; + let vb = v_bytes[i]; + for b in (0..8).rev() { + result = result.double(); + let ui = (ub >> b) & 1; + let vi = (vb >> b) & 1; + // Reason: u、v 公开,match 分支安全;add_mixed 对仿射 g/q 节省域乘, + // gq 为 Jacobian 仍用全量 add(无额外求逆开销) + match (ui, vi) { + (1, 0) => result = add_mixed(&result, &g), + (0, 1) => result = add_mixed(&result, q), + (1, 1) => result = JacobianPoint::add(&result, &gq_jac), + _ => {} + } + } + } + result.to_affine() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::field::{fp_to_bytes, GX, GY}; + + #[test] + fn test_generator_on_curve() { + assert!(AffinePoint::generator().is_on_curve()); + } + + #[test] + fn test_double_stays_on_curve() { + let g = JacobianPoint::from_affine(&AffinePoint::generator()); + let g2 = g.double().to_affine().unwrap(); + assert!(g2.is_on_curve()); + } + + #[test] + fn test_add_commutativity() { + let g = JacobianPoint::from_affine(&AffinePoint::generator()); + let g2 = g.double(); + let p1 = JacobianPoint::add(&g2, &g).to_affine().unwrap(); + let p2 = JacobianPoint::add(&g, &g2).to_affine().unwrap(); + assert_eq!(fp_to_bytes(&p1.x), fp_to_bytes(&p2.x)); + assert_eq!(fp_to_bytes(&p1.y), fp_to_bytes(&p2.y)); + assert!(p1.is_on_curve()); + } + + #[test] + fn test_scalar_mul_one_is_g() { + let g1 = JacobianPoint::scalar_mul_g(&U256::ONE).to_affine().unwrap(); + assert_eq!(fp_to_bytes(&g1.x), fp_to_bytes(&GX)); + assert_eq!(fp_to_bytes(&g1.y), fp_to_bytes(&GY)); + } + + #[test] + fn test_serialization_roundtrip() { + let g = AffinePoint::generator(); + let bytes = g.to_bytes(); + assert_eq!(bytes[0], 0x04); + let g2 = AffinePoint::from_bytes(&bytes).unwrap(); + assert_eq!(fp_to_bytes(&g.x), fp_to_bytes(&g2.x)); + assert_eq!(fp_to_bytes(&g.y), fp_to_bytes(&g2.y)); + } + + #[test] + fn test_keypair_on_curve() { + // 测试私钥 → 公钥在曲线上 + let k_hex = "f927525e176ae5607c628bc508ec0465ef285b74415bf876130a8a5d004c789e"; + let k_bytes: [u8; 32] = { + let mut b = [0u8; 32]; + for (i, chunk) in k_hex.as_bytes().chunks(2).enumerate() { + b[i] = u8::from_str_radix(core::str::from_utf8(chunk).unwrap(), 16).unwrap(); + } + b + }; + let k = U256::from_be_slice(&k_bytes); + let pub_aff = JacobianPoint::scalar_mul_g(&k).to_affine().unwrap(); + assert!(pub_aff.is_on_curve()); + // 验证 y² = x³ + ax + b + let x2 = fp_square(&pub_aff.x); + let x3 = fp_mul(&x2, &pub_aff.x); + let ax = fp_mul(&CURVE_A, &pub_aff.x); + let rhs = fp_add(&fp_add(&x3, &ax), &CURVE_B); + assert_eq!(rhs, fp_square(&pub_aff.y)); + } + + /// 验证完备加法公式的退化情况(常量时间 add 的正确性) + #[test] + fn test_add_degenerate_cases() { + let g = JacobianPoint::from_affine(&AffinePoint::generator()); + let inf = JacobianPoint::INFINITY; + + // ∞ + G = G + let r = JacobianPoint::add(&inf, &g).to_affine().unwrap(); + assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "∞ + G 的 x 坐标错误"); + assert_eq!(fp_to_bytes(&r.y), fp_to_bytes(&GY), "∞ + G 的 y 坐标错误"); + + // G + ∞ = G + let r = JacobianPoint::add(&g, &inf).to_affine().unwrap(); + assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "G + ∞ 的 x 坐标错误"); + + // G + G = 2G(通过 add 和 double 各算一次,结果应相同) + let add_gg = JacobianPoint::add(&g, &g).to_affine().unwrap(); + let double_g = g.double().to_affine().unwrap(); + assert_eq!( + fp_to_bytes(&add_gg.x), + fp_to_bytes(&double_g.x), + "add(G,G) != double(G) 的 x 坐标" + ); + assert_eq!( + fp_to_bytes(&add_gg.y), + fp_to_bytes(&double_g.y), + "add(G,G) != double(G) 的 y 坐标" + ); + + // G + (-G) = ∞(互反点,y 取负) + let g_neg = JacobianPoint { + x: g.x, + y: fp_neg(&g.y), + z: g.z, + }; + assert!( + JacobianPoint::add(&g, &g_neg).is_infinity(), + "G + (-G) 应为无穷远点" + ); + } +} diff --git a/sm2/src/error.rs b/sm2/src/error.rs new file mode 100644 index 0000000..de62556 --- /dev/null +++ b/sm2/src/error.rs @@ -0,0 +1,50 @@ +//! SM2 错误类型 +//! +//! SM2 Error types used by all public APIs. + +use core::fmt; + +/// SM2 操作的统一错误类型 +/// +/// Unified error type for all SM2 operations. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Error { + /// 私钥不合法(d ∉ [1, n-2])/ Invalid private key (d ∉ [1, n-2]) + InvalidPrivateKey, + /// 公钥不合法(格式错误或不在曲线上)/ Invalid public key (bad format or not on curve) + InvalidPublicKey, + /// 签名格式不合法 / Invalid signature format + InvalidSignature, + /// 验签失败 / Signature verification failed + VerifyFailed, + /// 解密失败(C3 校验不通过)/ Decryption failed (C3 check failed) + DecryptFailed, + /// 点在无穷远处 / Point at infinity + PointAtInfinity, + /// 输入长度不合法 / Invalid input length + InvalidInputLength, + /// 密钥交换失败 / Key exchange failed + KeyExchangeFailed, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidPrivateKey => f.write_str("SM2: invalid private key"), + Error::InvalidPublicKey => f.write_str("SM2: invalid public key"), + Error::InvalidSignature => f.write_str("SM2: invalid signature format"), + Error::VerifyFailed => f.write_str("SM2: signature verification failed"), + Error::DecryptFailed => f.write_str("SM2: decryption failed"), + Error::PointAtInfinity => f.write_str("SM2: point at infinity"), + Error::InvalidInputLength => f.write_str("SM2: invalid input length"), + Error::KeyExchangeFailed => f.write_str("SM2: key exchange failed"), + } + } +} + +/// Bridge to `signature::Error` for `Signer`/`Verifier` trait impls. +impl From for signature::Error { + fn from(_: Error) -> Self { + signature::Error::new() + } +} diff --git a/sm2/src/field.rs b/sm2/src/field.rs new file mode 100644 index 0000000..b52175d --- /dev/null +++ b/sm2/src/field.rs @@ -0,0 +1,251 @@ +//! SM2 sm2p256v1 素域 Fp 与标量域 Fn +//! +//! 曲线参数来自 GB/T 32918.1-2016 附录 A。 +//! 所有算术通过 `crypto-bigint` 的 `ConstMontyForm` 实现,常量时间。 + +use crypto_bigint::{impl_modulus, modular::ConstMontyForm, U256}; + +// ── 模数定义 ────────────────────────────────────────────────────────────────── + +// SM2 素数域模数 p +// p = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF +impl_modulus!( + Sm2FieldModulus, + U256, + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF" +); + +// SM2 群阶 n +// n = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 +impl_modulus!( + Sm2GroupOrder, + U256, + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123" +); + +/// SM2 素域元素(基于 Montgomery 形式的常量时间运算) +pub type Fp = ConstMontyForm; + +/// SM2 标量域元素(群阶 n 上的模运算) +pub type Fn = ConstMontyForm; + +// ── 曲线参数常量(GB/T 32918.1-2016 附录 A)───────────────────────────────── + +/// 曲线系数 a = p - 3 +pub const CURVE_A: Fp = Fp::new(&U256::from_be_hex( + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", +)); + +/// 曲线系数 b +pub const CURVE_B: Fp = Fp::new(&U256::from_be_hex( + "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", +)); + +/// 基点 G 的 x 坐标 +pub const GX: Fp = Fp::new(&U256::from_be_hex( + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", +)); + +/// 基点 G 的 y 坐标 +pub const GY: Fp = Fp::new(&U256::from_be_hex( + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", +)); + +/// 域模数 p(用于坐标范围检查) +pub const FIELD_MODULUS: U256 = + U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"); + +/// 群阶 n(用于标量范围检查) +pub const GROUP_ORDER: U256 = + U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"); + +/// 群阶 n - 1(私钥合法性检查:d ∈ [1, n-2] → d < n-1) +pub const GROUP_ORDER_MINUS_1: U256 = + U256::from_be_hex("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54122"); + +// ── Fp 工具函数 ─────────────────────────────────────────────────────────────── + +/// 从大端字节构造 Fp(调用方保证 bytes 表示的值 < p) +#[inline] +pub fn fp_from_bytes(bytes: &[u8; 32]) -> Fp { + Fp::new(&U256::from_be_slice(bytes)) +} + +/// 将 Fp 元素转为大端字节 +#[inline] +pub fn fp_to_bytes(a: &Fp) -> [u8; 32] { + a.retrieve().to_be_bytes() +} + +/// 从大端字节构造 Fn(标量,调用方保证值 < n) +#[inline] +pub fn fn_from_bytes(bytes: &[u8; 32]) -> Fn { + Fn::new(&U256::from_be_slice(bytes)) +} + +/// 将 Fn 元素转为大端字节 +#[inline] +pub fn fn_to_bytes(a: &Fn) -> [u8; 32] { + a.retrieve().to_be_bytes() +} + +/// Fp 加法(模 p) +#[inline] +pub fn fp_add(a: &Fp, b: &Fp) -> Fp { + a.add(b) +} + +/// Fp 减法(模 p) +#[inline] +pub fn fp_sub(a: &Fp, b: &Fp) -> Fp { + a.sub(b) +} + +/// Fp 乘法(Montgomery 乘,常量时间) +#[inline] +pub fn fp_mul(a: &Fp, b: &Fp) -> Fp { + a.mul(b) +} + +/// Fp 取负(模 p) +#[inline] +pub fn fp_neg(a: &Fp) -> Fp { + a.neg() +} + +/// Fp 平方(常量时间) +#[inline] +pub fn fp_square(a: &Fp) -> Fp { + a.square() +} + +/// Fp 求逆(Bernstein-Yang 算法,常量时间) +/// 返回 None 当且仅当 a == 0 +pub fn fp_inv(a: &Fp) -> Option { + let inv = a.inv(); + // CtOption 转换为 Option + if bool::from(inv.is_some()) { + // Reason: ConstantTimeEq 保证此 unwrap 不可能 panic(is_some 为真) + Some(inv.unwrap()) + } else { + None + } +} + +/// Fp 平方根(用于点解压缩) +/// +/// SM2 素数 p ≡ 3 (mod 4),故 sqrt(a) = a^((p+1)/4) mod p。 +/// 若结果的平方 ≠ a,则 a 不是二次剩余,返回 None。 +pub fn fp_sqrt(a: &Fp) -> Option { + // (p+1)/4 = 3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFC000000040000000000000000 + // p = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 00000000 FFFFFFFFFFFFFFFF + // p+1 = FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 00000001 0000000000000000 + // /4 (右移2位) = 3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFC0000000 40000000 00000000 + let exp = U256::from_be_hex("3FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC00000004000000000000000"); + let candidate = a.pow(&exp); + // 验证 candidate^2 == a(常量时间比较,ConstMontyForm 的 PartialEq 是常量时间) + if candidate.square() == *a { + Some(candidate) + } else { + None + } +} + +/// Fn 加法(模 n) +#[inline] +pub fn fn_add(a: &Fn, b: &Fn) -> Fn { + a.add(b) +} + +/// Fn 减法(模 n) +#[inline] +pub fn fn_sub(a: &Fn, b: &Fn) -> Fn { + a.sub(b) +} + +/// Fn 乘法(模 n,Montgomery 形式) +#[inline] +pub fn fn_mul(a: &Fn, b: &Fn) -> Fn { + a.mul(b) +} + +/// Fn 取负(模 n) +#[inline] +pub fn fn_neg(a: &Fn) -> Fn { + a.neg() +} + +/// Fn 求逆(Bernstein-Yang 算法,常量时间) +/// 返回 None 当且仅当 a == 0 +pub fn fn_inv(a: &Fn) -> Option { + let inv = a.inv(); + if bool::from(inv.is_some()) { + Some(inv.unwrap()) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fp_add_sub_symmetric() { + let a = fp_from_bytes(&[ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]); + let b = fp_from_bytes(&[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, + ]); + let sum = fp_add(&a, &b); + let diff = fp_sub(&sum, &b); + assert_eq!(fp_to_bytes(&diff), fp_to_bytes(&a)); + } + + #[test] + fn test_fp_mul_by_one() { + let gx = GX; + let result = fp_mul(&gx, &Fp::ONE); + assert_eq!(fp_to_bytes(&result), fp_to_bytes(&gx)); + } + + #[test] + fn test_fp_inv_roundtrip() { + let two = fp_from_bytes(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, + ]); + let inv = fp_inv(&two).expect("2 的逆元应存在"); + assert_eq!(fp_mul(&two, &inv), Fp::ONE); + } + + #[test] + fn test_fp_zero_has_no_inv() { + assert!(fp_inv(&Fp::ZERO).is_none()); + } + + #[test] + fn test_fp_sqrt_of_four() { + let four = fp_from_bytes(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, + ]); + let root = fp_sqrt(&four).expect("4 应有平方根"); + assert_eq!(fp_square(&root), four); + } + + #[test] + fn test_fn_inv_roundtrip() { + let three = fn_from_bytes(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, + ]); + let inv = fn_inv(&three).expect("3^-1 应存在"); + assert_eq!(fn_mul(&three, &inv), Fn::ONE); + } +} diff --git a/sm2/src/kdf.rs b/sm2/src/kdf.rs new file mode 100644 index 0000000..b43f80c --- /dev/null +++ b/sm2/src/kdf.rs @@ -0,0 +1,67 @@ +//! SM2 KDF 密钥派生函数(GB/T 32918.4-2016 §5.4.3) +//! +//! KDF(Z, klen) = ‖_{i=1}^{⌈klen/32⌉} SM3(Z ‖ CT_i) +//! 其中 CT_i 为 32-bit 大端计数器,从 1 开始。 + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +// Reason: sm3 sub-crate 只导出 Digest API,通过内联包装器使用 +fn sm3_hash(parts: &[&[u8]]) -> [u8; 32] { + use sm3::Digest; + let mut h = sm3::Sm3::new(); + for part in parts { + h.update(part); + } + h.finalize().into() +} + +/// SM2/SM9 KDF 密钥派生函数 +/// +/// # 参数 +/// - `z`: 输入密钥材料(共享点坐标等) +/// - `klen`: 期望输出字节数 +/// +/// # 返回 +/// 长度为 `klen` 的派生密钥字节序列(`alloc` feature 下可用) +#[cfg(feature = "alloc")] +pub fn kdf(z: &[u8], klen: usize) -> Vec { + let mut result = Vec::with_capacity(klen + 32); + let mut counter: u32 = 1; + while result.len() < klen { + // 每轮: SM3(Z || CT_i) + result.extend_from_slice(&sm3_hash(&[z, &counter.to_be_bytes()])); + counter += 1; + } + result.truncate(klen); + result +} + +#[cfg(test)] +#[cfg(feature = "alloc")] +mod tests { + use super::*; + + #[test] + fn test_kdf_length() { + let z = b"test input"; + assert_eq!(kdf(z, 32).len(), 32); + assert_eq!(kdf(z, 48).len(), 48); + assert_eq!(kdf(z, 1).len(), 1); + } + + #[test] + fn test_kdf_deterministic() { + let z = b"shared secret"; + assert_eq!(kdf(z, 32), kdf(z, 32)); + } + + #[test] + fn test_kdf_different_lengths() { + // 64 字节输出应与两次 32 字节拼接一致(即第一块完全相同) + let z = b"input"; + let k32 = kdf(z, 32); + let k64 = kdf(z, 64); + assert_eq!(&k64[..32], &k32[..]); + } +} diff --git a/sm2/src/key_exchange.rs b/sm2/src/key_exchange.rs new file mode 100644 index 0000000..bb84dd5 --- /dev/null +++ b/sm2/src/key_exchange.rs @@ -0,0 +1,628 @@ +//! SM2 密钥交换协议(GB/T 32918.3-2016) +//! +//! 提供两种密钥交换方式: +//! - `ecdh`: 简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls) +//! - `exchange_a` / `exchange_b`: 完整 GB/T 32918.3 密钥交换协议(带确认哈希) + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crypto_bigint::{Zero, U256}; +use rand_core::RngCore; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::error::Error; +use crate::ec::{AffinePoint, JacobianPoint}; +use crate::field::{fn_add, fn_mul, fp_to_bytes, Fn, GROUP_ORDER_MINUS_1}; +use crate::get_z; +use sm3::Digest as _; + +// Reason: sm3 sub-crate 只导出 Digest API;通过此包装器实现流式哈希 +struct Sm3H(sm3::Sm3); +impl Sm3H { + fn new() -> Self { Sm3H(sm3::Sm3::new()) } + fn update(&mut self, data: &[u8]) { self.0.update(data); } + fn finalize(self) -> [u8; 32] { self.0.finalize().into() } +} + +// ── x̄ 辅助函数(GB/T 32918.3 核心运算)───────────────────────────────────────── + +/// 计算 x̄ = 2^w + (x & (2^w - 1)),其中 w = ⌈(⌈log2(n)⌉ / 2)⌉ - 1 = 127 +/// +/// 对 SM2 256 位群阶,w=127。在大端 32 字节表示中: +/// - 清除高 128 位(bytes[0..16]),保留低 128 位 +/// - 设 bytes[16] 的 bit7 = 1(即加 2^127) +fn x_bar(x_bytes: &[u8; 32]) -> U256 { + let mut buf = [0u8; 32]; + // Reason: 保留 x 的低 128 位(bytes[16..32]),高 128 位清零 + buf[16..32].copy_from_slice(&x_bytes[16..32]); + // 设 bit 127(bytes[16] 的最高位) + buf[16] |= 0x80; + U256::from_be_slice(&buf) +} + +// ── EphemeralKey(临时密钥对)──────────────────────────────────────────────────── + +/// SM2 密钥交换临时密钥对(离开作用域自动清零) +/// +/// 用于密钥交换协议中的临时私钥和对应公钥。 +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct EphemeralKey { + r_bytes: [u8; 32], + #[zeroize(skip)] + r_point: [u8; 65], +} + +impl EphemeralKey { + /// 生成临时密钥对 + pub fn generate(rng: &mut R) -> Self { + loop { + let mut r_bytes = [0u8; 32]; + rng.fill_bytes(&mut r_bytes); + let r = U256::from_be_slice(&r_bytes); + if bool::from(r.is_zero()) || r >= GROUP_ORDER_MINUS_1 { + r_bytes.zeroize(); + continue; + } + let r_jac = JacobianPoint::scalar_mul_g(&r); + // Reason: r 在合法范围内,scalar_mul_g 不会产生无穷远点 + let r_aff = r_jac.to_affine().expect("valid r produces valid point"); + return EphemeralKey { + r_bytes, + r_point: r_aff.to_bytes(), + }; + } + } + + /// 从指定标量创建临时密钥对(测试用) + pub fn from_scalar(r: &U256) -> Result { + if bool::from(r.is_zero()) || *r >= GROUP_ORDER_MINUS_1 { + return Err(Error::InvalidPrivateKey); + } + let r_jac = JacobianPoint::scalar_mul_g(r); + let r_aff = r_jac.to_affine().map_err(|_| Error::InvalidPrivateKey)?; + Ok(EphemeralKey { + r_bytes: r.to_be_bytes(), + r_point: r_aff.to_bytes(), + }) + } + + /// 获取临时公钥(发送给对方) + pub fn public_key(&self) -> &[u8; 65] { + &self.r_point + } +} + +// ── 简单 ECDH ────────────────────────────────────────────────────────────────── + +/// 简单 SM2-ECDH 共享密钥计算 +/// +/// 计算 shared = my_priv · peer_pub,返回共享点的 x 坐标(32 字节)。 +/// 适用于 TLS/rustls 等只需要原始 ECDH 共享密钥的场景。 +/// +/// # 参数 +/// - `my_priv`: 己方私钥 +/// - `peer_pub`: 对方公钥(65 字节,04||x||y) +/// +/// # 错误 +/// - `InvalidPublicKey`: 公钥格式错误或不在曲线上 +/// - `PointAtInfinity`: 共享点为无穷远(不应发生于合法输入) +pub fn ecdh(my_priv: &crate::PrivateKey, peer_pub: &[u8; 65]) -> Result<[u8; 32], Error> { + let peer = AffinePoint::from_bytes(peer_pub)?; + let d = U256::from_be_slice(my_priv.as_bytes()); + let peer_jac = JacobianPoint::from_affine(&peer); + let shared = JacobianPoint::scalar_mul(&d, &peer_jac); + let shared_aff = shared.to_affine()?; + Ok(fp_to_bytes(&shared_aff.x)) +} + +/// 从变长切片执行 SM2-ECDH(rustls `ActiveKeyExchange::complete` 适配) +/// +/// 等同于 `ecdh`,但接受 `&[u8]` 而非 `&[u8; 65]`,省去调用方的长度转换。 +/// +/// # 错误 +/// - `InvalidInputLength`: peer_pub 长度不等于 65 +/// - `InvalidPublicKey` / `PointAtInfinity`: 同 `ecdh` +pub fn ecdh_from_slice( + my_priv: &crate::PrivateKey, + peer_pub: &[u8], +) -> Result<[u8; 32], Error> { + let pub_fixed: &[u8; 65] = peer_pub.try_into().map_err(|_| Error::InvalidInputLength)?; + ecdh(my_priv, pub_fixed) +} + +// ── 完整密钥交换协议(GB/T 32918.3)────────────────────────────────────────────── + +/// 密钥交换结果 +#[cfg(feature = "alloc")] +pub struct ExchangeResult { + /// 协商出的共享密钥 + pub key: Vec, + /// 己方确认哈希(发给对方验证) + pub s_self: [u8; 32], + /// 对方确认哈希(用于验证对方发来的值) + pub s_peer: [u8; 32], +} + +/// 发起方 A 执行密钥交换 +/// +/// # 参数 +/// - `klen`: 期望密钥长度(字节) +/// - `id_a`: 发起方用户 ID +/// - `id_b`: 响应方用户 ID +/// - `pri_key_a`: 发起方私钥 +/// - `pub_key_a`: 发起方公钥(65 字节) +/// - `pub_key_b`: 响应方公钥(65 字节) +/// - `eph_key_a`: 发起方临时密钥 +/// - `r_b`: 响应方临时公钥(65 字节) +#[cfg(feature = "alloc")] +#[allow(clippy::too_many_arguments)] +pub fn exchange_a( + klen: usize, + id_a: &[u8], + id_b: &[u8], + pri_key_a: &crate::PrivateKey, + pub_key_a: &[u8; 65], + pub_key_b: &[u8; 65], + eph_key_a: &EphemeralKey, + r_b: &[u8; 65], +) -> Result { + compute_shared( + true, klen, id_a, id_b, pri_key_a, pub_key_a, pub_key_b, eph_key_a, r_b, + ) +} + +/// 响应方 B 执行密钥交换 +/// +/// # 参数 +/// - `klen`: 期望密钥长度(字节) +/// - `id_a`: 发起方用户 ID +/// - `id_b`: 响应方用户 ID +/// - `pri_key_b`: 响应方私钥 +/// - `pub_key_a`: 发起方公钥(65 字节) +/// - `pub_key_b`: 响应方公钥(65 字节) +/// - `eph_key_b`: 响应方临时密钥 +/// - `r_a`: 发起方临时公钥(65 字节) +#[cfg(feature = "alloc")] +#[allow(clippy::too_many_arguments)] +pub fn exchange_b( + klen: usize, + id_a: &[u8], + id_b: &[u8], + pri_key_b: &crate::PrivateKey, + pub_key_a: &[u8; 65], + pub_key_b: &[u8; 65], + eph_key_b: &EphemeralKey, + r_a: &[u8; 65], +) -> Result { + compute_shared( + false, klen, id_a, id_b, pri_key_b, pub_key_a, pub_key_b, eph_key_b, r_a, + ) +} + +/// 内部共享计算 +/// +/// `is_initiator`: true 表示发起方 A,false 表示响应方 B +#[cfg(feature = "alloc")] +#[allow(clippy::too_many_arguments)] +fn compute_shared( + is_initiator: bool, + klen: usize, + id_a: &[u8], + id_b: &[u8], + pri_key_self: &crate::PrivateKey, + pub_key_a: &[u8; 65], + pub_key_b: &[u8; 65], + eph_key_self: &EphemeralKey, + r_peer: &[u8; 65], +) -> Result { + // 计算 ZA、ZB + let z_a = get_z(id_a, pub_key_a); + let z_b = get_z(id_b, pub_key_b); + + // 解析临时公钥坐标 + let r_self_aff = AffinePoint::from_bytes(eph_key_self.public_key())?; + let r_peer_aff = AffinePoint::from_bytes(r_peer)?; + + // 计算 x̄_self 和 x̄_peer + let x_self_bytes = fp_to_bytes(&r_self_aff.x); + let x_peer_bytes = fp_to_bytes(&r_peer_aff.x); + let x_bar_self = x_bar(&x_self_bytes); + let x_bar_peer = x_bar(&x_peer_bytes); + + // t = (d_self + x̄_self · r_self) mod n + let d_self = U256::from_be_slice(pri_key_self.as_bytes()); + let r_self = U256::from_be_slice(&eph_key_self.r_bytes); + let t_fn = fn_add( + &Fn::new(&d_self), + &fn_mul(&Fn::new(&x_bar_self), &Fn::new(&r_self)), + ); + + // V/U = t · (peer_pub + x̄_peer · R_peer) + // Reason: 先计算 x̄_peer · R_peer(标量乘),再加 peer_pub(仿射点) + let peer_pub_bytes = if is_initiator { pub_key_b } else { pub_key_a }; + let peer_pub_aff = AffinePoint::from_bytes(peer_pub_bytes)?; + let peer_pub_jac = JacobianPoint::from_affine(&peer_pub_aff); + let r_peer_jac = JacobianPoint::from_affine(&r_peer_aff); + let x_bar_peer_r = JacobianPoint::scalar_mul(&x_bar_peer, &r_peer_jac); + let combined = JacobianPoint::add(&peer_pub_jac, &x_bar_peer_r); + let t = t_fn.retrieve(); + let v_point = JacobianPoint::scalar_mul(&t, &combined); + let v_aff = v_point.to_affine().map_err(|_| Error::KeyExchangeFailed)?; + + let xv = fp_to_bytes(&v_aff.x); + let yv = fp_to_bytes(&v_aff.y); + + // K = KDF(xV || yV || ZA || ZB, klen) + let mut kdf_input = Vec::with_capacity(32 + 32 + 32 + 32); + kdf_input.extend_from_slice(&xv); + kdf_input.extend_from_slice(&yv); + kdf_input.extend_from_slice(&z_a); + kdf_input.extend_from_slice(&z_b); + let key = crate::kdf::kdf(&kdf_input, klen); + + // KDF 输出全零时返回错误(防弱密钥) + if key.iter().all(|&b| b == 0) { + return Err(Error::KeyExchangeFailed); + } + + // 确认哈希 + // (x1,y1) 始终是 RA(发起方),(x2,y2) 始终是 RB(响应方) + let (x1, y1, x2, y2) = if is_initiator { + ( + fp_to_bytes(&r_self_aff.x), + fp_to_bytes(&r_self_aff.y), + fp_to_bytes(&r_peer_aff.x), + fp_to_bytes(&r_peer_aff.y), + ) + } else { + ( + fp_to_bytes(&r_peer_aff.x), + fp_to_bytes(&r_peer_aff.y), + fp_to_bytes(&r_self_aff.x), + fp_to_bytes(&r_self_aff.y), + ) + }; + + // 内部哈希 hash_v = SM3(xV || ZA || ZB || x1 || y1 || x2 || y2) + let mut h = Sm3H::new(); + h.update(&xv); + h.update(&z_a); + h.update(&z_b); + h.update(&x1); + h.update(&y1); + h.update(&x2); + h.update(&y2); + let hash_v = h.finalize(); + + // S1 = SM3(0x02 || yV || hash_v) — 己方若为 B,则 S1 是己方确认值 + let s1 = { + let mut h = Sm3H::new(); + h.update(&[0x02]); + h.update(&yv); + h.update(&hash_v); + h.finalize() + }; + + // SA = SM3(0x03 || yV || hash_v) + let sa = { + let mut h = Sm3H::new(); + h.update(&[0x03]); + h.update(&yv); + h.update(&hash_v); + h.finalize() + }; + + // Reason: 发起方 A 的确认哈希是 SA(0x03),响应方 B 的确认哈希是 S1(0x02) + let (s_self, s_peer) = if is_initiator { + (sa, s1) // A 发送 SA 给 B 验证,A 验证 B 发来的 S1 + } else { + (s1, sa) // B 发送 S1 给 A 验证,B 验证 A 发来的 SA + }; + + Ok(ExchangeResult { + key, + s_self, + s_peer, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::PrivateKey; + + #[allow(dead_code)] + struct FakeRng(#[allow(dead_code)] [u8; 32]); + impl RngCore for FakeRng { + fn next_u32(&mut self) -> u32 { + 0 + } + fn next_u64(&mut self) -> u64 { + 0 + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + for (i, b) in dest.iter_mut().enumerate() { + *b = self.0[i % 32]; + } + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } + } + + #[test] + fn test_x_bar() { + // x = 1(低 128 位只有 bit 0) + let mut x_bytes = [0u8; 32]; + x_bytes[31] = 0x01; + let result = x_bar(&x_bytes); + // 期望 2^127 + 1 + let mut expected = [0u8; 32]; + expected[16] = 0x80; + expected[31] = 0x01; + assert_eq!(result, U256::from_be_slice(&expected)); + } + + #[test] + fn test_x_bar_high_bits_cleared() { + // x 有高 128 位数据,应被清除 + let x_bytes = [0xFFu8; 32]; + let result = x_bar(&x_bytes); + // 低 128 位全 1 + 2^127 设置位 = 0x80 FF...FF 后 16 字节加上 2^127 + // 高 16 字节应为 0,bytes[16] = 0xFF | 0x80 = 0xFF + let mut expected = [0u8; 32]; + expected[16..32].copy_from_slice(&[0xFF; 16]); + expected[16] |= 0x80; // 已经是 0xFF,不变 + assert_eq!(result, U256::from_be_slice(&expected)); + } + + #[test] + fn test_ecdh_roundtrip() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let d_b: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + let pri_b = PrivateKey::from_bytes(&d_b).unwrap(); + let pub_a = pri_a.public_key(); + let pub_b = pri_b.public_key(); + + // A 用 B 的公钥算 ECDH,B 用 A 的公钥算 ECDH,结果应一致 + let shared_a = ecdh(&pri_a, &pub_b).unwrap(); + let shared_b = ecdh(&pri_b, &pub_a).unwrap(); + assert_eq!(shared_a, shared_b); + } + + #[test] + fn test_ecdh_invalid_pubkey() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + + // 无效公钥(全零 y 坐标不在曲线上) + let mut bad_pub = [0x04u8; 65]; + bad_pub[1] = 0x01; + assert!(ecdh(&pri_a, &bad_pub).is_err()); + } + + #[test] + fn test_ecdh_from_slice_length_check() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + + // 长度不对应报 InvalidInputLength + assert!(ecdh_from_slice(&pri_a, &[0x04u8; 64]).is_err()); + assert!(ecdh_from_slice(&pri_a, &[0x04u8; 66]).is_err()); + } + + #[test] + fn test_ecdh_from_slice_equals_ecdh() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let d_b: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + let pri_b = PrivateKey::from_bytes(&d_b).unwrap(); + let pub_b = pri_b.public_key(); + + let r1 = ecdh(&pri_a, &pub_b).unwrap(); + let r2 = ecdh_from_slice(&pri_a, &pub_b).unwrap(); + assert_eq!(r1, r2); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_exchange_roundtrip() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let d_b: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + let pri_b = PrivateKey::from_bytes(&d_b).unwrap(); + let pub_a = pri_a.public_key(); + let pub_b = pri_b.public_key(); + + let id_a = b"Alice@test.com"; + let id_b = b"Bob@test.com"; + + // 生成临时密钥 + let ra_scalar = + U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563"); + let rb_scalar = + U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80"); + let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap(); + let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap(); + + let result_a = exchange_a( + 16, + id_a, + id_b, + &pri_a, + &pub_a, + &pub_b, + &eph_a, + eph_b.public_key(), + ) + .unwrap(); + + let result_b = exchange_b( + 16, + id_a, + id_b, + &pri_b, + &pub_a, + &pub_b, + &eph_b, + eph_a.public_key(), + ) + .unwrap(); + + // 协商出的密钥应相同 + assert_eq!(result_a.key, result_b.key); + assert!(!result_a.key.is_empty()); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_exchange_confirmation() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let d_b: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + let pri_b = PrivateKey::from_bytes(&d_b).unwrap(); + let pub_a = pri_a.public_key(); + let pub_b = pri_b.public_key(); + + let id_a = b"1234567812345678"; + let id_b = b"1234567812345678"; + + let ra_scalar = + U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563"); + let rb_scalar = + U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80"); + let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap(); + let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap(); + + let result_a = exchange_a( + 16, + id_a, + id_b, + &pri_a, + &pub_a, + &pub_b, + &eph_a, + eph_b.public_key(), + ) + .unwrap(); + + let result_b = exchange_b( + 16, + id_a, + id_b, + &pri_b, + &pub_a, + &pub_b, + &eph_b, + eph_a.public_key(), + ) + .unwrap(); + + // 确认哈希交叉验证:A.s_peer == B.s_self,B.s_peer == A.s_self + assert_eq!(result_a.s_peer, result_b.s_self); + assert_eq!(result_b.s_peer, result_a.s_self); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_exchange_different_ids() { + let d_a: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + let d_b: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + + let pri_a = PrivateKey::from_bytes(&d_a).unwrap(); + let pri_b = PrivateKey::from_bytes(&d_b).unwrap(); + let pub_a = pri_a.public_key(); + let pub_b = pri_b.public_key(); + + let ra_scalar = + U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563"); + let rb_scalar = + U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80"); + + // 使用不同 ID 组合 + let eph_a1 = EphemeralKey::from_scalar(&ra_scalar).unwrap(); + let eph_b1 = EphemeralKey::from_scalar(&rb_scalar).unwrap(); + let result_1 = exchange_a( + 16, + b"ID_A_1", + b"ID_B_1", + &pri_a, + &pub_a, + &pub_b, + &eph_a1, + eph_b1.public_key(), + ) + .unwrap(); + + let eph_a2 = EphemeralKey::from_scalar(&ra_scalar).unwrap(); + let eph_b2 = EphemeralKey::from_scalar(&rb_scalar).unwrap(); + let result_2 = exchange_a( + 16, + b"ID_A_2", + b"ID_B_2", + &pri_a, + &pub_a, + &pub_b, + &eph_a2, + eph_b2.public_key(), + ) + .unwrap(); + + // 不同 ID 应产生不同密钥 + assert_ne!(result_1.key, result_2.key); + } +} diff --git a/sm2/src/lib.rs b/sm2/src/lib.rs new file mode 100644 index 0000000..02f0adb --- /dev/null +++ b/sm2/src/lib.rs @@ -0,0 +1,796 @@ +//! SM2 椭圆曲线公钥密码算法(GB/T 32918.1-5-2016) +//! +//! 本 crate 提供符合 GB/T 32918-2016 的纯 Rust、`no_std` 实现: +//! +//! - **密钥生成**:[`generate_keypair`] +//! - **数字签名/验签**:[`SigningKey`] / [`VerifyingKey`](实现 `signature::Signer/Verifier`) +//! - **公钥加密/解密**:[`encrypt`] / [`decrypt`](需 `alloc` feature) +//! - **密钥交换**:[`key_exchange::ecdh`] / [`key_exchange::exchange_a`] +//! - **DER 编解码**:[`der`] +//! +//! ## 安全性声明 +//! +//! - 所有私钥操作均为常量时间(Montgomery 域算术 + `subtle::ConditionallySelectable`) +//! - 私钥离开作用域后自动清零([`ZeroizeOnDrop`]) +//! - 标量乘法固定迭代 256 位,不跳过前导零 +//! - `sign_with_k` 危险接口需启用 `hazmat` feature +//! +//! ## 快速开始 +//! +//! ```rust +//! use sm2::{SigningKey, VerifyingKey, DEFAULT_ID}; +//! use sm2::signature::{Signer, Verifier}; +//! use rand_core::OsRng; +//! +//! // 生成密钥对 +//! let (pri, pub_bytes) = sm2::generate_keypair(&mut OsRng); +//! let signing = SigningKey::new(pri, DEFAULT_ID); +//! let verifying = VerifyingKey::new(pub_bytes, DEFAULT_ID); +//! +//! // 签名 +//! let msg = b"hello SM2"; +//! let sig = signing.sign(msg); +//! +//! // 验签 +//! verifying.verify(msg, &sig).expect("验签应通过"); +//! ``` +//! +//! --- +//! +//! SM2 elliptic curve public-key cryptography (GB/T 32918.1-5-2016). +//! +//! This crate provides a pure-Rust, `no_std` implementation of: +//! +//! - **Key generation**: [`generate_keypair`] +//! - **Signing / Verification**: [`SigningKey`] / [`VerifyingKey`] +//! (implement `signature::Signer` / `Verifier`) +//! - **Public-key encryption / decryption**: [`encrypt`] / [`decrypt`] +//! (requires `alloc` feature) +//! - **Key exchange**: [`key_exchange::ecdh`] / [`key_exchange::exchange_a`] +//! - **DER encoding / decoding**: [`der`] +//! +//! ## Security +//! +//! - All secret-dependent operations are constant-time +//! - Private keys are zeroized on drop ([`ZeroizeOnDrop`]) +//! - Scalar multiplication iterates all 256 bits regardless of scalar value +//! - `sign_with_k` (dangerous raw-k API) requires the `hazmat` feature + +#![no_std] +#![forbid(unsafe_code)] +#![warn(missing_docs)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +pub mod der; +pub mod ec; +pub mod error; +pub mod field; +pub mod kdf; +pub mod key_exchange; +mod rfc6979; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crypto_bigint::{Zero, U256}; +use rand_core::RngCore; +use subtle::ConstantTimeEq; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub use error::Error; +pub use signature; + +use crate::ec::{multi_scalar_mul, AffinePoint, JacobianPoint}; +use crate::field::{ + fn_add, fn_inv, fn_mul, fn_sub, fp_to_bytes, Fn, CURVE_A, CURVE_B, GROUP_ORDER, + GROUP_ORDER_MINUS_1, GX, GY, +}; + +// ── 内部 SM3 包装 ───────────────────────────────────────────────────────────── + +/// 内部轻量哈希上下文(包装 sm3::Sm3 的 Digest API) +/// +/// Thin wrapper around `sm3::Sm3` exposing a streaming `update`/`finalize` API +/// identical to the original `Sm3Hasher`, so callers need no refactoring. +struct Sm3H(sm3::Sm3); + +impl Sm3H { + fn new() -> Self { + use sm3::Digest; + Sm3H(sm3::Sm3::new()) + } + fn update(&mut self, data: &[u8]) { + use sm3::Digest; + self.0.update(data); + } + fn finalize(self) -> [u8; 32] { + use sm3::Digest; + self.0.finalize().into() + } +} + +// ── 常量 ────────────────────────────────────────────────────────────────────── + +/// SM2 默认用户可辨别标识(GB/T 32918.2-2016 §A.2 示例值) +/// +/// Default user distinguishable identifier (example from GB/T 32918.2-2016 §A.2). +pub const DEFAULT_ID: &[u8] = b"1234567812345678"; + +// ── 私钥类型 ────────────────────────────────────────────────────────────────── + +/// SM2 私钥(32 字节,离开作用域自动清零) +/// +/// SM2 private key (32 bytes). Automatically zeroized on drop. +#[derive(Clone, Zeroize, ZeroizeOnDrop)] +pub struct PrivateKey { + bytes: [u8; 32], +} + +impl PrivateKey { + /// 从字节构造私钥(验证 d ∈ [1, n-2]) + /// + /// Construct from bytes, validating d ∈ [1, n-2]. + pub fn from_bytes(bytes: &[u8; 32]) -> Result { + let d = U256::from_be_slice(bytes); + if bool::from(d.is_zero()) || d >= GROUP_ORDER_MINUS_1 { + return Err(Error::InvalidPrivateKey); + } + Ok(PrivateKey { bytes: *bytes }) + } + + /// 以字节引用访问私钥 + /// + /// Access the private key bytes by reference. + pub fn as_bytes(&self) -> &[u8; 32] { + &self.bytes + } + + /// 计算对应公钥(65 字节,04||x||y) + /// + /// Derive the corresponding public key (65 bytes, uncompressed: 04||x||y). + pub fn public_key(&self) -> [u8; 65] { + let d = U256::from_be_slice(&self.bytes); + let pub_jac = JacobianPoint::scalar_mul_g(&d); + // Reason: 私钥合法性已在构造时验证,scalar_mul_g 结果不会是无穷远点 + let pub_aff = pub_jac + .to_affine() + .expect("valid private key produces valid public key"); + pub_aff.to_bytes() + } +} + +// ── 密钥生成 ────────────────────────────────────────────────────────────────── + +/// 生成 SM2 密钥对(私钥 + 公钥 65 字节) +/// +/// Generate a SM2 key pair (private key + 65-byte public key). +/// Conforms to GB/T 32918.1-2016 §6.1. +pub fn generate_keypair(rng: &mut R) -> (PrivateKey, [u8; 65]) { + loop { + let mut d_bytes = [0u8; 32]; + rng.fill_bytes(&mut d_bytes); + let d = U256::from_be_slice(&d_bytes); + if bool::from(d.is_zero()) || d >= GROUP_ORDER_MINUS_1 { + d_bytes.zeroize(); + continue; + } + // Reason: 私钥满足范围约束,不会失败 + let priv_key = PrivateKey { bytes: d_bytes }; + let pub_key = priv_key.public_key(); + return (priv_key, pub_key); + } +} + +// ── Z 值计算(GB/T 32918.2-2016 §5.5)──────────────────────────────────────── + +/// 计算用户标识的 Z 值 +/// +/// Z = SM3(ENTL || ID || a || b || Gx || Gy || Px || Py) +/// +/// Compute Z-value for the user identity. Conforms to GB/T 32918.2-2016 §5.5. +pub fn get_z(id: &[u8], pub_key: &[u8; 65]) -> [u8; 32] { + let entl = (id.len() * 8) as u16; + let mut h = Sm3H::new(); + h.update(&entl.to_be_bytes()); + h.update(id); + h.update(&fp_to_bytes(&CURVE_A)); + h.update(&fp_to_bytes(&CURVE_B)); + h.update(&fp_to_bytes(&GX)); + h.update(&fp_to_bytes(&GY)); + h.update(&pub_key[1..33]); // Px + h.update(&pub_key[33..65]); // Py + h.finalize() +} + +/// 计算消息摘要 e = SM3(Z || M) +/// +/// Compute message digest e = SM3(Z || M). Conforms to GB/T 32918.2-2016 §5.5. +pub fn get_e(z: &[u8; 32], msg: &[u8]) -> [u8; 32] { + let mut h = Sm3H::new(); + h.update(z); + h.update(msg); + h.finalize() +} + +// ── 数字签名(GB/T 32918.2-2016 §6.2)─────────────────────────────────────── + +/// SM2 签名(使用指定随机数 k) +/// +/// Sign using a specified nonce k. **Only expose under `hazmat` feature — misusing k leaks the private key.** +/// +/// Sign with a fixed nonce k (for test vectors / hazmat use only). +/// Requires the `hazmat` feature gate. +#[cfg(feature = "hazmat")] +pub fn sign_with_k(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8; 64], Error> { + sign_with_k_inner(e, pri_key, k) +} + +/// 内部签名实现(供 `sign` 和 `hazmat::sign_with_k` 共用) +fn sign_with_k_inner(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8; 64], Error> { + let d = U256::from_be_slice(pri_key.as_bytes()); + + let kg_aff = JacobianPoint::scalar_mul_g(k) + .to_affine() + .map_err(|_| Error::InvalidSignature)?; + let x1 = fp_to_bytes(&kg_aff.x); + + let e_val = U256::from_be_slice(e); + let x1_val = U256::from_be_slice(&x1); + let r_fn = fn_add(&Fn::new(&e_val), &Fn::new(&x1_val)); + let r = r_fn.retrieve(); + + if bool::from(r.is_zero()) { + return Err(Error::InvalidSignature); + } + if fn_add(&r_fn, &Fn::new(k)).retrieve().is_zero().into() { + return Err(Error::InvalidSignature); + } + + let d_fn = Fn::new(&d); + let one_plus_d = fn_add(&Fn::ONE, &d_fn); + let inv = fn_inv(&one_plus_d).ok_or(Error::InvalidPrivateKey)?; + let rd = fn_mul(&r_fn, &d_fn); + let s_fn = fn_mul(&inv, &fn_sub(&Fn::new(k), &rd)); + let s = s_fn.retrieve(); + + if bool::from(s.is_zero()) { + return Err(Error::InvalidSignature); + } + + let mut sig = [0u8; 64]; + sig[..32].copy_from_slice(&r.to_be_bytes()); + sig[32..].copy_from_slice(&s.to_be_bytes()); + Ok(sig) +} + +/// SM2 签名(随机 k,标准接口) +/// +/// Sign with random nonce k. Accepts pre-computed digest `e = SM3(Z||M)`. +pub fn sign(e: &[u8; 32], pri_key: &PrivateKey, rng: &mut R) -> [u8; 64] { + loop { + let mut k_bytes = [0u8; 32]; + rng.fill_bytes(&mut k_bytes); + let k = U256::from_be_slice(&k_bytes); + k_bytes.zeroize(); + if bool::from(k.is_zero()) || k >= GROUP_ORDER { + continue; + } + if let Ok(sig) = sign_with_k_inner(e, pri_key, &k) { + return sig; + } + } +} + +/// SM2 确定性签名(RFC 6979,使用 HMAC-SM3 生成 k) +/// +/// Sign with deterministic nonce k derived via RFC 6979. +/// Accepts pre-computed digest `e = SM3(Z||M)`. +/// +/// # 安全关键点 +/// +/// 此函数不依赖外部 RNG,消除了 RNG 故障或偏差导致私钥泄露的风险。 +/// 对于相同的 (私钥, 消息摘要) 对,签名结果完全确定。 +pub fn sign_deterministic(e: &[u8; 32], pri_key: &PrivateKey) -> [u8; 64] { + // Reason: RFC 6979 保证生成的 k 总是满足 0 < k < n, + // 并且对于合法私钥,sign_with_k_inner 总会成功(极罕见的 r=0/s=0 情况由 RFC 6979 循环避免)。 + // 如果 sign_with_k_inner 失败(理论上极罕见),我们使用不同输入再试。 + let k = rfc6979::generate_k(pri_key.as_bytes(), e); + // RFC 6979 生成的 k 在几乎所有情况下都有效,直接调用 + if let Ok(sig) = sign_with_k_inner(e, pri_key, &k) { + return sig; + } + // 极罕见的 fallback:用 e+pri_key 的不同组合再生成一个 k + // (实际上 RFC 6979 的循环设计保证不会到这里) + let mut alt_input = [0u8; 32]; + for (i, (&a, &b)) in e.iter().zip(pri_key.as_bytes().iter()).enumerate() { + alt_input[i] = a.wrapping_add(b).wrapping_add(1); + } + let k2 = rfc6979::generate_k(pri_key.as_bytes(), &alt_input); + sign_with_k_inner(e, pri_key, &k2).expect("RFC 6979 fallback must succeed") +} + +/// SM2 签名(便捷接口,自动计算 Z 值与消息摘要) +/// +/// Convenience signing: auto-computes Z = SM3(ENTL||ID||...) and e = SM3(Z||M). +pub fn sign_message( + msg: &[u8], + id: &[u8], + pri_key: &PrivateKey, + rng: &mut R, +) -> [u8; 64] { + let pub_key = pri_key.public_key(); + let z = get_z(id, &pub_key); + let e = get_e(&z, msg); + sign(&e, pri_key, rng) +} + +/// SM2 确定性签名便捷接口(RFC 6979,无需 RNG) +/// +/// Convenience deterministic signing: auto-computes Z and e, then uses RFC 6979. +pub fn sign_message_deterministic(msg: &[u8], id: &[u8], pri_key: &PrivateKey) -> [u8; 64] { + let pub_key = pri_key.public_key(); + let z = get_z(id, &pub_key); + let e = get_e(&z, msg); + sign_deterministic(&e, pri_key) +} + +// ── 签名验证(GB/T 32918.2-2016 §6.3)─────────────────────────────────────── + +/// SM2 验签 +/// +/// Verify a SM2 signature. Accepts pre-computed digest `e = SM3(Z||M)`. +pub fn verify(e: &[u8; 32], pub_key: &[u8; 65], sig: &[u8; 64]) -> Result<(), Error> { + let r = U256::from_be_slice(&sig[..32]); + let s = U256::from_be_slice(&sig[32..]); + let n = GROUP_ORDER; + + if bool::from(r.is_zero()) || r >= n || bool::from(s.is_zero()) || s >= n { + return Err(Error::InvalidSignature); + } + + let t_fn = fn_add(&Fn::new(&r), &Fn::new(&s)); + let t = t_fn.retrieve(); + if bool::from(t.is_zero()) { + return Err(Error::VerifyFailed); + } + + let pa = AffinePoint::from_bytes(pub_key)?; + let point = multi_scalar_mul(&s, &t, &pa)?; + + let e_val = U256::from_be_slice(e); + let px_val = U256::from_be_slice(&fp_to_bytes(&point.x)); + let r_check = fn_add(&Fn::new(&e_val), &Fn::new(&px_val)).retrieve(); + + // Reason: 常量时间比较,防时序侧信道 + if r.to_be_bytes().ct_eq(&r_check.to_be_bytes()).unwrap_u8() != 1 { + return Err(Error::VerifyFailed); + } + Ok(()) +} + +/// SM2 验签(便捷接口,自动计算 Z 值与消息��要) +/// +/// Convenience verification: auto-computes Z and e. +pub fn verify_message( + msg: &[u8], + id: &[u8], + pub_key: &[u8; 65], + sig: &[u8; 64], +) -> Result<(), Error> { + let z = get_z(id, pub_key); + let e = get_e(&z, msg); + verify(&e, pub_key, sig) +} + +// ── 公钥加密(GB/T 32918.4-2016 §7.1)────────────────────────────────────── + +/// SM2 公钥加密 +/// +/// SM2 public-key encryption. Output format: C1||C3||C2 (GB/T 32918.4-2016 §6.1). +/// Requires `alloc` feature. +#[cfg(feature = "alloc")] +pub fn encrypt( + pub_key: &[u8; 65], + message: &[u8], + rng: &mut R, +) -> Result, Error> { + let pa = AffinePoint::from_bytes(pub_key)?; + + loop { + let mut k_bytes = [0u8; 32]; + rng.fill_bytes(&mut k_bytes); + let k = U256::from_be_slice(&k_bytes); + k_bytes.zeroize(); + if bool::from(k.is_zero()) || k >= GROUP_ORDER { + continue; + } + + let c1_aff = match JacobianPoint::scalar_mul_g(&k).to_affine() { + Ok(p) => p, + Err(_) => continue, + }; + let c1 = c1_aff.to_bytes(); + + let pa_jac = JacobianPoint::from_affine(&pa); + let kpa_aff = match JacobianPoint::scalar_mul(&k, &pa_jac).to_affine() { + Ok(p) => p, + Err(_) => continue, + }; + let x2 = fp_to_bytes(&kpa_aff.x); + let y2 = fp_to_bytes(&kpa_aff.y); + + let mut z_input = [0u8; 64]; + z_input[..32].copy_from_slice(&x2); + z_input[32..].copy_from_slice(&y2); + let t = kdf::kdf(&z_input, message.len()); + + if t.iter().all(|&b| b == 0) { + continue; + } + + let c2: Vec = message.iter().zip(t.iter()).map(|(&m, &k)| m ^ k).collect(); + + let mut h = Sm3H::new(); + h.update(&x2); + h.update(message); + h.update(&y2); + let c3 = h.finalize(); + + let mut output = Vec::with_capacity(65 + 32 + message.len()); + output.extend_from_slice(&c1); + output.extend_from_slice(&c3); + output.extend_from_slice(&c2); + return Ok(output); + } +} + +// ── 公钥解密(GB/T 32918.4-2016 §7.2)────────────────────────────────────── + +/// SM2 公钥解密(新格式 C1||C3||C2) +/// +/// SM2 public-key decryption (format C1||C3||C2). Requires `alloc` feature. +#[cfg(feature = "alloc")] +pub fn decrypt(pri_key: &PrivateKey, ciphertext: &[u8]) -> Result, Error> { + if ciphertext.len() < 97 { + return Err(Error::InvalidInputLength); + } + + let d = U256::from_be_slice(pri_key.as_bytes()); + + let c1_bytes: [u8; 65] = ciphertext[0..65].try_into().unwrap(); + let c1 = AffinePoint::from_bytes(&c1_bytes)?; + let c3_expected: [u8; 32] = ciphertext[65..97].try_into().unwrap(); + let c2 = &ciphertext[97..]; + + let c1_jac = JacobianPoint::from_affine(&c1); + let dc1_aff = JacobianPoint::scalar_mul(&d, &c1_jac).to_affine()?; + let x2 = fp_to_bytes(&dc1_aff.x); + let y2 = fp_to_bytes(&dc1_aff.y); + + let mut z_input = [0u8; 64]; + z_input[..32].copy_from_slice(&x2); + z_input[32..].copy_from_slice(&y2); + let t = kdf::kdf(&z_input, c2.len()); + + if t.iter().all(|&b| b == 0) { + return Err(Error::DecryptFailed); + } + + let m: Vec = c2.iter().zip(t.iter()).map(|(&c, &k)| c ^ k).collect(); + + let mut h = Sm3H::new(); + h.update(&x2); + h.update(&m); + h.update(&y2); + let c3_computed = h.finalize(); + + // Reason: 先验证 C3 再返回明文,防止 chosen-ciphertext 攻击 + if c3_expected.ct_eq(&c3_computed).unwrap_u8() != 1 { + return Err(Error::DecryptFailed); + } + Ok(m) +} + +// ── signature::Signer / Verifier trait 实现 ────────────────────────────────── + +/// SM2 签名结果(r||s,64 字节) +/// +/// SM2 signature (r||s, 64 bytes). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Sm2Signature { + bytes: [u8; 64], +} + +impl Sm2Signature { + /// 从 64 字节原始 r||s 构造签名 + /// + /// Construct from raw 64-byte r||s. + pub fn from_bytes(bytes: [u8; 64]) -> Self { + Sm2Signature { bytes } + } + + /// 以字节切片返回签名 + /// + /// Return the signature as a byte slice. + pub fn as_bytes(&self) -> &[u8; 64] { + &self.bytes + } +} + +impl AsRef<[u8]> for Sm2Signature { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +// ── SigningKey ──────────────────────────────────────────────────────────────── + +/// SM2 签名密钥(私钥 + 用户标识) +/// +/// SM2 signing key: wraps a private key with a user identity string. +/// Implements [`signature::Signer`]. +pub struct SigningKey<'id> { + private_key: PrivateKey, + /// 用户可辨别标识 / User distinguishable identifier + pub id: &'id [u8], +} + +impl<'id> SigningKey<'id> { + /// 构造签名密钥 + /// + /// Construct a signing key from a private key and user ID. + pub fn new(private_key: PrivateKey, id: &'id [u8]) -> Self { + SigningKey { private_key, id } + } + + /// 获取对应的公钥字节(65 字节,04||x||y) + /// + /// Derive the corresponding public key bytes (65 bytes, uncompressed). + pub fn public_key_bytes(&self) -> [u8; 65] { + self.private_key.public_key() + } +} + +impl<'id> signature::Signer for SigningKey<'id> { + fn try_sign(&self, msg: &[u8]) -> Result { + // Reason: sign_message 需要 RngCore;此处用 OsRng 退化实现 + // 在 no_std 环境中,若无 OsRng 可用,调用方应直接调用 sign/sign_message + use rand_core::OsRng; + let sig_bytes = sign_message(msg, self.id, &self.private_key, &mut OsRng); + Ok(Sm2Signature { bytes: sig_bytes }) + } +} + +// ── VerifyingKey ────────────────────────────────────────────────────────────── + +/// SM2 验证密钥(公钥 + 用户标识) +/// +/// SM2 verifying key: wraps a public key with a user identity string. +/// Implements [`signature::Verifier`]. +pub struct VerifyingKey<'id> { + public_key: [u8; 65], + /// 用户可辨别标识 / User distinguishable identifier + pub id: &'id [u8], +} + +impl<'id> VerifyingKey<'id> { + /// 构造验证密钥 + /// + /// Construct a verifying key from a public key and user ID. + pub fn new(public_key: [u8; 65], id: &'id [u8]) -> Self { + VerifyingKey { public_key, id } + } + + /// 验证公钥是否在 SM2 曲线上 + /// + /// Returns `Ok(())` if the public key is a valid SM2 curve point. + pub fn validate(&self) -> Result<(), Error> { + AffinePoint::from_bytes(&self.public_key).map(|_| ()) + } +} + +impl<'id> signature::Verifier for VerifyingKey<'id> { + fn verify(&self, msg: &[u8], signature: &Sm2Signature) -> Result<(), signature::Error> { + verify_message(msg, self.id, &self.public_key, &signature.bytes) + .map_err(signature::Error::from) + } +} + +// ── 单元测试 ────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + // 共用测试私钥(来自 libsmx 内部测试) + const D_BYTES: [u8; 32] = [ + 0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, + 0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, + 0x4d, 0xf7, 0xc5, 0xb8, + ]; + + const K_BYTES: [u8; 32] = [ + 0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0, + 0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d, + 0xea, 0xc1, 0xbc, 0x21, + ]; + + // 测试专用的确定性 RNG(使用固定字节池) + struct FakeRng([u8; 32]); + impl rand_core::RngCore for FakeRng { + fn next_u32(&mut self) -> u32 { 0 } + fn next_u64(&mut self) -> u64 { 0 } + fn fill_bytes(&mut self, dest: &mut [u8]) { + for (i, b) in dest.iter_mut().enumerate() { + *b = self.0[i % 32]; + } + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } + } + + #[test] + fn test_private_key_from_bytes_valid() { + PrivateKey::from_bytes(&D_BYTES).expect("合法私钥应成功构造"); + } + + #[test] + fn test_private_key_from_bytes_zero() { + assert!(PrivateKey::from_bytes(&[0u8; 32]).is_err(), "全零私钥应拒绝"); + } + + #[test] + fn test_public_key_on_curve() { + let pri = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_bytes = pri.public_key(); + let point = AffinePoint::from_bytes(&pub_bytes).expect("公钥应在曲线上"); + assert!(point.is_on_curve()); + } + + #[test] + fn test_get_z_deterministic() { + let pub_key = [0x04u8; 65]; + let z1 = get_z(DEFAULT_ID, &pub_key); + let z2 = get_z(DEFAULT_ID, &pub_key); + assert_eq!(z1, z2); + } + + #[test] + fn test_sign_verify_roundtrip() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"hello sm2"; + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, msg); + + let k = U256::from_be_slice(&K_BYTES); + let sig = sign_with_k_inner(&e, &pri_key, &k).expect("签名应成功"); + verify(&e, &pub_key, &sig).expect("验签应通过"); + } + + #[test] + fn test_sign_message_verify_message() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let mut rng = FakeRng(K_BYTES); + + let msg = b"hello sign_message"; + let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng); + verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("便捷验签应通过"); + } + + #[test] + fn test_verify_rejects_tampered_sig() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"hello sm2"; + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, msg); + let k = U256::from_be_slice(&K_BYTES); + let mut sig = sign_with_k_inner(&e, &pri_key, &k).unwrap(); + sig[0] ^= 0x01; + assert!(verify(&e, &pub_key, &sig).is_err()); + } + + #[test] + fn test_verify_rejects_wrong_id() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let mut rng = FakeRng(K_BYTES); + let msg = b"hello sign_message"; + let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng); + assert!(verify_message(msg, b"wrong-id", &pub_key, &sig).is_err()); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_encrypt_decrypt_roundtrip() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"Hello, SM2 encryption!"; + let mut rng = FakeRng(K_BYTES); + + let ciphertext = encrypt(&pub_key, msg, &mut rng).expect("加密应成功"); + let plaintext = decrypt(&pri_key, &ciphertext).expect("解密应成功"); + assert_eq!(plaintext, msg); + } + + #[cfg(feature = "alloc")] + #[test] + fn test_decrypt_rejects_tampered_ciphertext() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let mut rng = FakeRng(K_BYTES); + let mut ct = encrypt(&pub_key, b"test", &mut rng).unwrap(); + ct[70] ^= 0xFF; + assert!(decrypt(&pri_key, &ct).is_err()); + } + + // ── signature trait 测试 ──────────────────────────────────────────────── + + #[test] + fn test_signing_key_verifying_key_roundtrip() { + use signature::{Signer, Verifier}; + + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_bytes = pri_key.public_key(); + + let signing = SigningKey::new(pri_key, DEFAULT_ID); + let verifying = VerifyingKey::new(pub_bytes, DEFAULT_ID); + + let msg = b"signature trait roundtrip"; + let sig = signing.sign(msg); + verifying.verify(msg, &sig).expect("SigningKey/VerifyingKey 验签应通过"); + } + + #[test] + fn test_verifying_key_validate() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_bytes = pri_key.public_key(); + let vk = VerifyingKey::new(pub_bytes, DEFAULT_ID); + assert!(vk.validate().is_ok()); + } + + /// RFC 6979 确定性签名:结果可被 verify 通过 + #[test] + fn test_sign_deterministic_verify() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"RFC 6979 deterministic test"; + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, msg); + + let sig = sign_deterministic(&e, &pri_key); + verify(&e, &pub_key, &sig).expect("deterministic sign must verify"); + } + + /// RFC 6979 确定性签名:相同输入总产生相同签名 + #[test] + fn test_sign_deterministic_reproducible() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"reproducibility test"; + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, msg); + + let sig1 = sign_deterministic(&e, &pri_key); + let sig2 = sign_deterministic(&e, &pri_key); + assert_eq!(sig1, sig2, "RFC 6979 signatures must be reproducible"); + verify(&e, &pub_key, &sig1).expect("must verify"); + } + + /// sign_message_deterministic 便捷接口 + #[test] + fn test_sign_message_deterministic() { + let pri_key = PrivateKey::from_bytes(&D_BYTES).unwrap(); + let pub_key = pri_key.public_key(); + let msg = b"sign_message_deterministic test"; + + let sig = sign_message_deterministic(msg, DEFAULT_ID, &pri_key); + verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("deterministic message sig must verify"); + } +} diff --git a/sm2/src/rfc6979.rs b/sm2/src/rfc6979.rs new file mode 100644 index 0000000..acd3e0a --- /dev/null +++ b/sm2/src/rfc6979.rs @@ -0,0 +1,176 @@ +//! RFC 6979 确定性 k 值生成(使用 HMAC-SM3) +//! +//! 实现 RFC 6979 §3.2 的 HMAC-DRBG,以 SM3 作为哈希函数。 +//! +//! # 安全关键点 +//! +//! 此模块消除了 SM2 签名对外部 RNG 的依赖。对于相同的 (私钥, 消息摘要) 对, +//! 生成的 k 值完全确定,从根本上消除了 RNG 故障或偏差导致私钥泄露的风险。 +//! +//! # 参考 +//! +//! - RFC 6979 §3.2: + +use crypto_bigint::{Zero, U256}; +use sm3::Digest; +use zeroize::Zeroize; + +use crate::field::GROUP_ORDER; + +// SM3 输出长度(字节) +const HASH_LEN: usize = 32; + +/// 内部 HMAC-SM3 实现(仅供 RFC 6979 使用) +/// +/// HMAC(K, m) = SM3((K ^ opad) || SM3((K ^ ipad) || m)) +/// +/// Reason: sm3 sub-crate 没有导出 HMAC,我们在内部实现以避免新增依赖。 +/// 块大小 64 字节,与 SM3 的 BlockSize 一致。 +struct HmacSm3 { + /// 外层密钥 K ^ opad(已预处理) + opad_key: [u8; 64], + /// 内层哈希上下文(已吸收 K ^ ipad) + inner: sm3::Sm3, +} + +impl HmacSm3 { + /// 以 key 初始化 HMAC-SM3 上下文 + fn new(key: &[u8; 32]) -> Self { + let mut ipad_key = [0x36u8; 64]; + let mut opad_key = [0x5cu8; 64]; + // Reason: key 长度 32 字节 < 块大小 64,直接 XOR 前 32 字节 + for (i, &b) in key.iter().enumerate() { + ipad_key[i] ^= b; + opad_key[i] ^= b; + } + let mut inner = sm3::Sm3::new(); + inner.update(&ipad_key); + ipad_key.zeroize(); + HmacSm3 { opad_key, inner } + } + + /// 向内层哈希追加数据 + fn update(&mut self, data: &[u8]) { + self.inner.update(data); + } + + /// 计算 HMAC 值(消耗 self) + fn finalize(self) -> [u8; HASH_LEN] { + // Reason: Drop trait 阻止直接 move self.inner,clone 一次即可 + let inner_hash: [u8; HASH_LEN] = self.inner.clone().finalize().into(); + let mut outer = sm3::Sm3::new(); + outer.update(&self.opad_key); + outer.update(&inner_hash); + outer.finalize().into() + } +} + +impl Drop for HmacSm3 { + fn drop(&mut self) { + self.opad_key.zeroize(); + } +} + +/// HMAC-SM3 一次性计算:`HMAC(key, msg1 || msg2 || ...)` +fn hmac(key: &[u8; 32], parts: &[&[u8]]) -> [u8; HASH_LEN] { + let mut mac = HmacSm3::new(key); + for part in parts { + mac.update(part); + } + mac.finalize() +} + +/// RFC 6979 §3.2 确定性 k 值生成 +/// +/// 输入: +/// - `x`: 私钥字节(32 字节,big-endian) +/// - `h1`: 消息摘要 e(32 字节,已经过 Z||M 预处理的 SM3 输出) +/// +/// 输出:满足 `0 < k < n` 的确定性标量 k +/// +/// # 安全关键点 +/// +/// 对于相同的 (x, h1) 输入,此函数总是返回相同的 k, +/// 从根本上消除了签名过程对 RNG 质量的依赖。 +pub(crate) fn generate_k(x: &[u8; 32], h1: &[u8; 32]) -> U256 { + // RFC 6979 §3.2 步骤 b/c: 初始化 V 和 K + // + // V = 0x01 0x01 ... 0x01 (hlen 个字节) + // K = 0x00 0x00 ... 0x00 (hlen 个字节) + let mut v = [0x01u8; HASH_LEN]; + let mut k = [0x00u8; HASH_LEN]; + + // 步骤 d: K = HMAC_K(V || 0x00 || x || h1) + k = hmac(&k, &[&v, &[0x00u8], x, h1]); + + // 步骤 e: V = HMAC_K(V) + v = hmac(&k, &[&v]); + + // 步骤 f: K = HMAC_K(V || 0x01 || x || h1) + k = hmac(&k, &[&v, &[0x01u8], x, h1]); + + // 步骤 g: V = HMAC_K(V) + v = hmac(&k, &[&v]); + + // 步骤 h: 循环生成候选 k + loop { + // h2: V = HMAC_K(V) + v = hmac(&k, &[&v]); + + // bits2int(V): 直接作为 big-endian 256-bit 整数 + let candidate = U256::from_be_slice(&v); + + // 检查 0 < k < n(group order) + // Reason: CT 比较 — candidate.is_zero() 和 candidate >= GROUP_ORDER + let is_zero: bool = candidate.is_zero().into(); + let ge_n: bool = (candidate >= GROUP_ORDER).into(); + + if !is_zero && !ge_n { + // 找到合法 k,清零临时数据后返回 + v.zeroize(); + k.zeroize(); + return candidate; + } + + // 步骤 h3 更新(不满足时继续) + k = hmac(&k, &[&v, &[0x00u8]]); + v = hmac(&k, &[&v]); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::field::GROUP_ORDER; + + /// 确定性验证:相同输入总产生相同 k + #[test] + fn test_generate_k_deterministic() { + let x = [0x01u8; 32]; + let h1 = [0x02u8; 32]; + let k1 = generate_k(&x, &h1); + let k2 = generate_k(&x, &h1); + assert_eq!(k1, k2, "RFC 6979 k must be deterministic"); + } + + /// k 必须在有效范围 (0, n) + #[test] + fn test_generate_k_range() { + let x = [0x42u8; 32]; + let h1 = [0xABu8; 32]; + let k = generate_k(&x, &h1); + assert!(!bool::from(k.is_zero()), "k must not be zero"); + assert!(k < GROUP_ORDER, "k must be less than group order"); + } + + /// 不同消息产生不同 k + #[test] + fn test_generate_k_different_msgs() { + let x = [0x01u8; 32]; + let h1 = [0x01u8; 32]; + let h2 = [0x02u8; 32]; + let k1 = generate_k(&x, &h1); + let k2 = generate_k(&x, &h2); + assert_ne!(k1, k2, "different messages must produce different k values"); + } +} diff --git a/sm3/Cargo.toml b/sm3/Cargo.toml new file mode 100644 index 0000000..eae84f9 --- /dev/null +++ b/sm3/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sm3" +version = "0.1.0" +edition = "2021" +rust-version = "1.83.0" +license = "Apache-2.0" +description = "SM3 (ShangMi 3) hash function — GB/T 32905-2016. Pure-Rust, no_std, implements digest::Digest." +repository = "https://github.com/kintaiW/libsmx" +documentation = "https://docs.rs/sm3" +categories = ["cryptography", "no-std"] +keywords = ["crypto", "hash", "sm3", "shangmi", "digest"] +readme = "README.md" + +[dependencies] +digest = { workspace = true, features = ["block-api"] } + +[dev-dependencies] +hex-literal = { workspace = true } +digest = { workspace = true, features = ["dev"] } + +[features] +default = [] +oid = ["digest/oid"] diff --git a/sm3/src/block_api.rs b/sm3/src/block_api.rs new file mode 100644 index 0000000..faea8a0 --- /dev/null +++ b/sm3/src/block_api.rs @@ -0,0 +1,98 @@ +//! SM3 block-level core (low-level internal type). +//! +//! Users should use [`Sm3`] from the crate root instead. + +use core::fmt; +use digest::{ + HashMarker, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, + Eager, FixedOutputCore, OutputSizeUser, UpdateCore, + }, + typenum::{U32, U64, Unsigned}, +}; + +use crate::compress; + +// ── Sm3Core ─────────────────────────────────────────────────────────────────── + +/// Low-level SM3 block-processing core. +/// +/// Implements the `digest::block_api` low-level traits so that the +/// [`digest::buffer_fixed!`] macro can wrap it into a fully-featured [`Sm3`]. +#[derive(Clone)] +pub struct Sm3Core { + state: [u32; 8], + /// Number of **complete** 64-byte blocks already compressed. + block_len: u64, +} + +impl HashMarker for Sm3Core {} + +impl BlockSizeUser for Sm3Core { + /// SM3 processes 512-bit (64-byte) blocks. + type BlockSize = U64; +} + +impl BufferKindUser for Sm3Core { + /// Eager: compress each full block immediately. + type BufferKind = Eager; +} + +impl OutputSizeUser for Sm3Core { + /// SM3 produces a 256-bit (32-byte) digest. + type OutputSize = U32; +} + +impl Default for Sm3Core { + fn default() -> Self { + Self { state: compress::IV, block_len: 0 } + } +} + +impl digest::Reset for Sm3Core { + fn reset(&mut self) { + *self = Self::default(); + } +} + +impl UpdateCore for Sm3Core { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + // Reason: only complete blocks are counted here; the partial tail is + // held by the surrounding BlockBuffer and counted in finalize. + self.block_len += blocks.len() as u64; + for block in blocks { + // Reason: hybrid_array::Array implements Deref, + // so we get a &[u8] slice then cast to &[u8; 64] via try_into. + let b: &[u8; 64] = (&**block).try_into().unwrap(); + compress::compress(&mut self.state, b); + } + } +} + +impl FixedOutputCore for Sm3Core { + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut digest::Output) { + // Total bit length = (complete blocks × 64 + partial tail) × 8 + let bs = U64::U64; + let bit_len = 8 * (buffer.get_pos() as u64 + bs * self.block_len); + + // GB/T 32905 §5.3.1 padding: 0x80, zeros, 64-bit big-endian bit count + buffer.len64_padding_be(bit_len, |block| { + let b: &[u8; 64] = (&**block).try_into().unwrap(); + compress::compress(&mut self.state, b); + }); + + // Serialize state as big-endian u32 words + for (chunk, &word) in out.chunks_exact_mut(4).zip(self.state.iter()) { + chunk.copy_from_slice(&word.to_be_bytes()); + } + } +} + +impl AlgorithmName for Sm3Core { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Sm3") + } +} diff --git a/sm3/src/compress.rs b/sm3/src/compress.rs new file mode 100644 index 0000000..4a6b8a7 --- /dev/null +++ b/sm3/src/compress.rs @@ -0,0 +1,81 @@ +//! SM3 compression function (GB/T 32905-2016 §5) + +/// SM3 initial hash values (IV), GB/T 32905 §4.3 +pub(super) const IV: [u32; 8] = [ + 0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, + 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E, +]; + +/// Round constants T_j (GB/T 32905 §4.2), precomputed to avoid runtime branches. +/// +/// Reason: Eliminates the `if j < 16` branch in each round; the compiler +/// embeds these as immediates with zero runtime rotation overhead. +const T: [u32; 64] = { + let mut t = [0u32; 64]; + let mut j = 0usize; + while j < 16 { + t[j] = 0x79CC4519u32.rotate_left(j as u32); + j += 1; + } + while j < 64 { + t[j] = 0x7A879D8Au32.rotate_left((j % 32) as u32); + j += 1; + } + t +}; + +/// Permutation function P0 (GB/T 32905 §4.5) +#[inline(always)] +fn p0(x: u32) -> u32 { + x ^ x.rotate_left(9) ^ x.rotate_left(17) +} + +/// Permutation function P1 (GB/T 32905 §4.5) +#[inline(always)] +fn p1(x: u32) -> u32 { + x ^ x.rotate_left(15) ^ x.rotate_left(23) +} + +/// SM3 compression function: processes one 64-byte block, updates `state`. +/// +/// Reason: Two-segment loop (j=0..15 and j=16..63) eliminates runtime `if` +/// branches inside ff/gg/T; W' is inlined as `w[j] ^ w[j+4]`. +pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { + // ── Message expansion ──────────────────────────────────────────────────── + let mut w = [0u32; 68]; + for i in 0..16 { + w[i] = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap()); + } + for i in 16..68 { + let v = w[i - 16] ^ w[i - 9] ^ w[i - 3].rotate_left(15); + w[i] = p1(v) ^ w[i - 13].rotate_left(7) ^ w[i - 6]; + } + + // ── Compression: 64 rounds ─────────────────────────────────────────────── + let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state; + + // j = 0..15: FF = x^y^z, GG = x^y^z + for j in 0..16 { + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7); + let ss2 = ss1 ^ a.rotate_left(12); + let tt1 = (a ^ b ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]); + let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + } + + // j = 16..63: FF = majority(x,y,z), GG = choice(x,y,z) + for j in 16..64 { + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7); + let ss2 = ss1 ^ a.rotate_left(12); + let tt1 = ((a & b) | (a & c) | (b & c)) + .wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]); + let tt2 = ((e & f) | (!e & g)) + .wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + } + + state[0] ^= a; state[1] ^= b; state[2] ^= c; state[3] ^= d; + state[4] ^= e; state[5] ^= f; state[6] ^= g; state[7] ^= h; +} diff --git a/sm3/src/lib.rs b/sm3/src/lib.rs new file mode 100644 index 0000000..74460a3 --- /dev/null +++ b/sm3/src/lib.rs @@ -0,0 +1,124 @@ +//! SM3 cryptographic hash function (GB/T 32905-2016). +//! +//! This crate provides a [`Digest`]-compatible SM3 implementation suitable for +//! use anywhere in the RustCrypto ecosystem. +//! +//! ## Security +//! +//! SM3 is standardised by the Chinese National Standard (GB/T 32905-2016) and +//! provides a 256-bit (32-byte) digest. It has a similar structure to SHA-256 +//! but uses different constants, mixing functions, and message scheduling. +//! +//! ## Usage +//! +//! ```rust +//! use sm3::{Sm3, Digest}; +//! +//! let hash = Sm3::digest(b"abc"); +//! assert_eq!( +//! hash[..], +//! hex_literal::hex!("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"), +//! ); +//! ``` + +#![no_std] +#![forbid(unsafe_code)] +#![warn(missing_docs)] + +mod compress; + +/// Block-level SM3 core — low-level building block, not for direct use. +/// +/// Prefer the top-level [`Sm3`] type which provides the full `Digest` API. +pub mod block_api; + +pub use digest::{self, Digest}; + +// Re-export the core type for users who need low-level access (e.g. HMAC cores). +pub use block_api::Sm3Core; + +// Generate the buffered `Sm3` wrapper using the digest crate macro. +// BaseFixedTraits provides: Debug, BlockSizeUser, OutputSizeUser, CoreProxy, Update, FixedOutput. +// We also add AlgorithmName, Default, Clone, HashMarker, Reset, FixedOutputReset explicitly. +// Reason: FixedHashTraits additionally requires SerializableState and ZeroizeOnDrop which +// are non-trivial to implement safely; BaseFixedTraits is sufficient for the Digest interface. +digest::buffer_fixed!( + /// SM3 hash function (GB/T 32905-2016). + /// + /// Implements [`Digest`] and is a drop-in for SHA-256 in generic protocols. + pub struct Sm3(block_api::Sm3Core); + impl: BaseFixedTraits AlgorithmName Default Clone HashMarker Reset FixedOutputReset; +); + +#[cfg(test)] +mod tests { + use super::*; + use digest::Digest; + use hex_literal::hex; + + /// GB/T 32905-2016 Appendix A, Example 1: SM3("abc") + #[test] + fn test_vector_abc() { + let hash = Sm3::digest(b"abc"); + let expected = hex!("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"); + assert_eq!(hash[..], expected[..]); + } + + /// GB/T 32905-2016 Appendix A, Example 2: 64-byte repeated string + #[test] + fn test_vector_64bytes() { + let msg = b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"; + let hash = Sm3::digest(msg); + let expected = hex!("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732"); + assert_eq!(hash[..], expected[..]); + } + + /// Empty input + #[test] + fn test_vector_empty() { + let hash = Sm3::digest(b""); + let expected = hex!("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b"); + assert_eq!(hash[..], expected[..]); + } + + /// Cross-block boundary: 65 bytes (one full block + 1 byte tail) + #[test] + fn test_cross_block_boundary() { + let data = [0x61u8; 65]; // 65 x 'a' + let once = Sm3::digest(&data); + + // Streaming 1 byte at a time must match one-shot + let mut h = Sm3::new(); + for b in &data { + h.update(&[*b]); + } + assert_eq!(once, h.finalize()); + } + + /// Streaming must match one-shot for an arbitrary input + #[test] + fn test_streaming_matches_oneshot() { + let data = b"hello world, streaming SM3 test"; + let once = Sm3::digest(data); + + let mut h = Sm3::new(); + for chunk in data.chunks(7) { + h.update(chunk); + } + assert_eq!(once, h.finalize()); + } + + /// Clone mid-stream must produce the same result + #[test] + fn test_clone_midstream() { + let mut h1 = Sm3::new(); + h1.update(b"hello"); + let h2 = h1.clone(); + h1.update(b" world"); + + let mut h3 = h2; + h3.update(b" world"); + + assert_eq!(h1.finalize(), h3.finalize()); + } +} diff --git a/sm4/Cargo.toml b/sm4/Cargo.toml new file mode 100644 index 0000000..4361b5a --- /dev/null +++ b/sm4/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "sm4" +version = "0.1.0" +edition = "2021" +rust-version = "1.83.0" +license = "Apache-2.0" +description = "SM4 (ShangMi 4) block cipher with constant-time bitslice S-box — GB/T 32907-2016" +repository = "https://github.com/kintaiW/libsmx" +categories = ["cryptography", "no-std::no-alloc"] +keywords = ["crypto", "cipher", "sm4", "shangmi", "block-cipher"] + +[dependencies] +cipher = { workspace = true } +zeroize = { workspace = true } + +[dev-dependencies] +hex-literal = { workspace = true } +cipher = { workspace = true, features = ["dev"] } + +[features] +default = [] +zeroize = [] diff --git a/sm4/src/consts.rs b/sm4/src/consts.rs new file mode 100644 index 0000000..b502891 --- /dev/null +++ b/sm4/src/consts.rs @@ -0,0 +1,286 @@ +//! SM4 S-box constants and internal cipher functions (GB/T 32907-2016). +//! +//! All operations are constant-time boolean-circuit implementations. + +// ── System constants ────────────────────────────────────────────────────────── + +/// System parameter FK (GB/T 32907 §A.1) +pub(super) const FK: [u32; 4] = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]; + +/// Constant key CK (GB/T 32907 §A.1) +#[rustfmt::skip] +pub(super) const CK: [u32; 32] = [ + 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, + 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, + 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, + 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, + 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, + 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, + 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, + 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279, +]; + +// ── Boolean-circuit S-box ───────────────────────────────────────────────────── + +/// SM4 S-box — pure boolean circuit (zero memory access, cache-timing immune). +/// +/// Input/output linear layers + GF(2^4) inversion in boolean circuit form. +/// Source: emmansun/sm4bs (sbox64), extracted and validated against all 256 values. +/// +/// ⚠️ Security: Uses only AND/XOR/OR/NOT. No table lookups, no memory reads. +#[allow(dead_code)] +#[inline] +pub(super) fn sbox_ct(x: u8) -> u8 { + let b0 = x & 1; + let b1 = (x >> 1) & 1; + let b2 = (x >> 2) & 1; + let b3 = (x >> 3) & 1; + let b4 = (x >> 4) & 1; + let b5 = (x >> 5) & 1; + let b6 = (x >> 6) & 1; + let b7 = (x >> 7) & 1; + + let t1 = b7 ^ b5; + let t2 = 1 ^ (b5 ^ b1); + let g5 = 1 ^ b0; + let t3 = 1 ^ (b0 ^ t2); + let t4 = b6 ^ b2; + let t5 = b3 ^ t3; + let t6 = b4 ^ t1; + let t7 = b1 ^ t5; + let t8 = b1 ^ t4; + let t9 = t6 ^ t8; + let t10 = t6 ^ t7; + let t11 = 1 ^ (b3 ^ t1); + let t12 = 1 ^ (b6 ^ t9); + + let g0 = t10; + let g1 = t7; + let g2 = t4 ^ t10; + let g3 = t5; + let g4 = t2; + let g6 = t11 ^ t2; + let g7 = t12 ^ (t11 ^ t2); + let m0 = t6; let m1 = t3; let m2 = t8; + let m3 = t3 ^ t12; let m4 = t4; let m5 = t11; + let m6 = b1; let m7 = t11 ^ m3; let m8 = t9; let m9 = t12; + + let t2t = m0 & m1; let t3t = g0 & g4; let t4t = g3 & g7; + let t7t = g3 | g7; let t11t = m4 & m5; let t10t = m3 & m2; + let t12t = m3 | m2; let t6t = g6 | g2; let t9t = m6 | m7; + let t5t = m8 & m9; let t8t = m8 | m9; + let t14t = t3t ^ t2t; let t16t = t5t ^ t14t; + let t20t = t16t ^ t7t; let t17t = t9t ^ t10t; + let t18t = t11t ^ t12t; + let p2 = t20t ^ t18t; let p0 = t6t ^ t16t; + let t1t = g5 & g1; let t13t = t1t ^ t2t; + let t15t = t13t ^ t4t; + let p3 = (t6t ^ t15t) ^ t17t; let p1 = t8t ^ t15t; + + let t0m = p1 & p2; let t1m = p3 & p0; let t2m = p0 & p2; + let t3m = p1 & p3; let t4m = t0m & t2m; + let t5m = t1m ^ t3m; let t6m = t5m | p0; let t7m = t2m | p3; + let l3 = t4m ^ t6m; let t9m = t7m ^ t3m; + let l0 = t0m ^ t9m; let t11m = p2 | t5m; + let l1 = t11m ^ t1m; let t12m = p1 | t2m; let l2 = t12m ^ t5m; + + let k4 = l2 ^ l3; let k3 = l1 ^ l3; let k2 = l0 ^ l2; + let k0 = l0 ^ l1; let k1 = k2 ^ k3; + + let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1; + let e2 = g4 & l0; let r1 = e2 ^ e1; + let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4; + let e5 = m3 & k1; let r3 = e5 ^ e4; + let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7; + let e8 = g6 & l2; let r5 = e8 ^ e7; + let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10; + let e11 = g0 & l0; let r7 = e11 ^ e10; + let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13; + let e14 = m2 & k1; let r9 = e14 ^ e13; + let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16; + let e17 = g2 & l2; let r11 = e17 ^ e16; + + let t1o = r7 ^ r9; let t2o = r1 ^ t1o; let t3o = r3 ^ t2o; + let t4o = r5 ^ r3; let t5o = r4 ^ t4o; let t6o = r0 ^ r4; + let t7o = r11 ^ r7; + let b5o = t1o ^ t4o; let b2o = t1o ^ t6o; + let t10o = r2 ^ t5o; let b3o = r10 ^ r8; + let b1o = 1 ^ (t3o ^ b3o); let b6o = t10o ^ b1o; + let b4o = 1 ^ (t3o ^ t7o); let b0o = t6o ^ b4o; + let b7o = 1 ^ (r10 ^ r6); + + b0o | (b1o << 1) | (b2o << 2) | (b3o << 3) | (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7) +} + +/// SM4 τ transform: 4-byte u32 bitslice S-box (constant-time, 4-way parallel). +/// +/// Packs 4 bytes' bit-planes into 4 u32 lanes, runs the boolean circuit once, +/// then unpacks — equivalent to ~3-4x speedup over 4 independent `sbox_ct` calls. +/// +/// ⚠️ Security: Inherits full constant-time properties of `sbox_ct`. +#[inline] +pub(super) fn tau(a: u32) -> u32 { + let bytes = a.to_be_bytes(); + + // Pack: bits[i] low 4 = bit-i of [byte0, byte1, byte2, byte3] + let mut bits = [0u32; 8]; + for (i, bit) in bits.iter_mut().enumerate() { + *bit = ((bytes[0] >> i) & 1) as u32 + | (((bytes[1] >> i) & 1) as u32) << 1 + | (((bytes[2] >> i) & 1) as u32) << 2 + | (((bytes[3] >> i) & 1) as u32) << 3; + } + let [b0, b1, b2, b3, b4, b5, b6, b7] = bits; + + // Boolean circuit (identical to sbox_ct, but NOT uses 0xF instead of 1) + let t1 = b7 ^ b5; + let t2 = 0xF ^ (b5 ^ b1); + let g5 = 0xF ^ b0; + let t3 = 0xF ^ (b0 ^ t2); + let t4 = b6 ^ b2; + let t5 = b3 ^ t3; + let t6 = b4 ^ t1; + let t7 = b1 ^ t5; + let t8 = b1 ^ t4; + let t9 = t6 ^ t8; + let t10 = t6 ^ t7; + let t11 = 0xF ^ (b3 ^ t1); + let t12 = 0xF ^ (b6 ^ t9); + + let g0 = t10; let g1 = t7; let g2 = t4 ^ t10; let g3 = t5; + let g4 = t2; let g6 = t11 ^ t2; let g7 = t12 ^ (t11 ^ t2); + let m0 = t6; let m1 = t3; let m2 = t8; let m3 = t3 ^ t12; + let m4 = t4; let m5 = t11; let m6 = b1; let m7 = t11 ^ m3; + let m8 = t9; let m9 = t12; + + let t2t = m0 & m1; let t3t = g0 & g4; let t4t = g3 & g7; + let t7t = g3 | g7; let t11t = m4 & m5; let t10t = m3 & m2; + let t12t = m3 | m2; let t6t = g6 | g2; let t9t = m6 | m7; + let t5t = m8 & m9; let t8t = m8 | m9; + let t14t = t3t ^ t2t; let t16t = t5t ^ t14t; + let t20t = t16t ^ t7t; let t17t = t9t ^ t10t; + let t18t = t11t ^ t12t; + let p2 = t20t ^ t18t; let p0 = t6t ^ t16t; + let t1t = g5 & g1; let t13t = t1t ^ t2t; + let t15t = t13t ^ t4t; + let p3 = (t6t ^ t15t) ^ t17t; let p1 = t8t ^ t15t; + + let t0m = p1 & p2; let t1m = p3 & p0; let t2m = p0 & p2; + let t3m = p1 & p3; let t4m = t0m & t2m; + let t5m = t1m ^ t3m; let t6m = t5m | p0; let t7m = t2m | p3; + let l3 = t4m ^ t6m; let t9m = t7m ^ t3m; + let l0 = t0m ^ t9m; let t11m = p2 | t5m; + let l1 = t11m ^ t1m; let t12m = p1 | t2m; let l2 = t12m ^ t5m; + + let k4 = l2 ^ l3; let k3 = l1 ^ l3; let k2 = l0 ^ l2; + let k0 = l0 ^ l1; let k1 = k2 ^ k3; + + let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1; + let e2 = g4 & l0; let r1 = e2 ^ e1; + let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4; + let e5 = m3 & k1; let r3 = e5 ^ e4; + let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7; + let e8 = g6 & l2; let r5 = e8 ^ e7; + let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10; + let e11 = g0 & l0; let r7 = e11 ^ e10; + let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13; + let e14 = m2 & k1; let r9 = e14 ^ e13; + let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16; + let e17 = g2 & l2; let r11 = e17 ^ e16; + + let t1o = r7 ^ r9; let t2o = r1 ^ t1o; let t3o = r3 ^ t2o; + let t4o = r5 ^ r3; let t5o = r4 ^ t4o; let t6o = r0 ^ r4; + let t7o = r11 ^ r7; + let b5o = t1o ^ t4o; let b2o = t1o ^ t6o; + let t10o = r2 ^ t5o; let b3o = r10 ^ r8; + let b1o = 0xF ^ (t3o ^ b3o); let b6o = t10o ^ b1o; + let b4o = 0xF ^ (t3o ^ t7o); let b0o = t6o ^ b4o; + let b7o = 0xF ^ (r10 ^ r6); + + // Unpack: 8 u32 low-4 bits -> 4 output bytes + let ob = [b0o, b1o, b2o, b3o, b4o, b5o, b6o, b7o]; + let mut out = [0u8; 4]; + for (i, &v) in ob.iter().enumerate() { + out[0] |= ((v & 1) as u8) << i; + out[1] |= (((v >> 1) & 1) as u8) << i; + out[2] |= (((v >> 2) & 1) as u8) << i; + out[3] |= (((v >> 3) & 1) as u8) << i; + } + u32::from_be_bytes(out) +} + +// ── Round functions ─────────────────────────────────────────────────────────── + +/// Encryption round function T (GB/T 32907 §6.2.1) +#[inline] +pub(super) fn t_enc(a: u32) -> u32 { + let b = tau(a); + b ^ b.rotate_left(2) ^ b.rotate_left(10) ^ b.rotate_left(18) ^ b.rotate_left(24) +} + +/// Key expansion round function T' (GB/T 32907 §6.2.2) +#[inline] +fn t_key(a: u32) -> u32 { + let b = tau(a); + b ^ b.rotate_left(13) ^ b.rotate_left(23) +} + +// ── Key expansion ───────────────────────────────────────────────────────────── + +/// SM4 key expansion (GB/T 32907 §6.2.2) +pub(super) fn expand_key(key: &[u8; 16], rk: &mut [u32; 32]) { + let mk = [ + u32::from_be_bytes(key[0..4].try_into().unwrap()), + u32::from_be_bytes(key[4..8].try_into().unwrap()), + u32::from_be_bytes(key[8..12].try_into().unwrap()), + u32::from_be_bytes(key[12..16].try_into().unwrap()), + ]; + let mut k = [mk[0] ^ FK[0], mk[1] ^ FK[1], mk[2] ^ FK[2], mk[3] ^ FK[3]]; + for i in 0..32 { + let tmp = k[(i + 1) % 4] ^ k[(i + 2) % 4] ^ k[(i + 3) % 4] ^ CK[i]; + rk[i] = k[i % 4] ^ t_key(tmp); + k[i % 4] = rk[i]; + } +} + +// ── Block load/store ────────────────────────────────────────────────────────── + +#[inline] +pub(super) fn load_block(b: &[u8; 16]) -> [u32; 4] { + [ + u32::from_be_bytes(b[0..4].try_into().unwrap()), + u32::from_be_bytes(b[4..8].try_into().unwrap()), + u32::from_be_bytes(b[8..12].try_into().unwrap()), + u32::from_be_bytes(b[12..16].try_into().unwrap()), + ] +} + +#[inline] +pub(super) fn store_block(b: &mut [u8; 16], x: &[u32; 4]) { + b[0..4].copy_from_slice(&x[0].to_be_bytes()); + b[4..8].copy_from_slice(&x[1].to_be_bytes()); + b[8..12].copy_from_slice(&x[2].to_be_bytes()); + b[12..16].copy_from_slice(&x[3].to_be_bytes()); +} + +// ── Encryption / Decryption rounds ──────────────────────────────────────────── + +/// 32-round SM4 encryption (round keys in forward order) +pub(super) fn encrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) { + for &rk_i in rk.iter() { + let tmp = x[1] ^ x[2] ^ x[3] ^ rk_i; + let next = x[0] ^ t_enc(tmp); + x[0] = x[1]; x[1] = x[2]; x[2] = x[3]; x[3] = next; + } + x.reverse(); // GB/T 32907 §6.2.1: output = (X35, X34, X33, X32) +} + +/// 32-round SM4 decryption (round keys in reverse order) +pub(super) fn decrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) { + for i in (0..32).rev() { + let tmp = x[1] ^ x[2] ^ x[3] ^ rk[i]; + let next = x[0] ^ t_enc(tmp); + x[0] = x[1]; x[1] = x[2]; x[2] = x[3]; x[3] = next; + } + x.reverse(); +} diff --git a/sm4/src/lib.rs b/sm4/src/lib.rs new file mode 100644 index 0000000..42fba09 --- /dev/null +++ b/sm4/src/lib.rs @@ -0,0 +1,254 @@ +//! SM4 block cipher (GB/T 32907-2016). +//! +//! This crate provides a [`cipher::BlockCipherEncrypt`] / [`cipher::BlockCipherDecrypt`] +//! compatible SM4 implementation suitable for use in the RustCrypto ecosystem. +//! +//! ## Security +//! +//! SM4 is standardised by the Chinese National Standard (GB/T 32907-2016). +//! This implementation uses a **boolean-circuit bitslice S-box** (zero memory +//! accesses) rather than a lookup table, making it immune to cache-timing +//! side-channel attacks. +//! +//! ## Usage +//! +//! ```rust +//! use sm4::Sm4; +//! use sm4::cipher::{BlockCipherEncrypt, BlockCipherDecrypt, KeyInit}; +//! +//! let key = [0u8; 16]; +//! let plaintext = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, +//! 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10]; +//! let expected = [0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, +//! 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e, 0x42, 0x46]; +//! +//! let cipher = Sm4::new(&plaintext.into()); +//! let mut block = plaintext.into(); +//! cipher.encrypt_block(&mut block); +//! assert_eq!(block[..], expected[..]); +//! ``` + +#![no_std] +#![forbid(unsafe_code)] +#![warn(missing_docs)] + +mod consts; + +pub use cipher::{self, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; + +use cipher::{ + AlgorithmName, Block, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend, + BlockCipherEncClosure, BlockSizeUser, InOut, KeySizeUser, ParBlocksSizeUser, + consts::{U1, U16}, +}; +use core::fmt; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +// ── Key type alias ──────────────────────────────────────────────────────────── + +/// SM4 key type: 128 bits (16 bytes). +pub type Sm4Key = cipher::Key; + +// ── Sm4 struct ──────────────────────────────────────────────────────────────── + +/// SM4 block cipher (GB/T 32907-2016). +/// +/// Implements [`BlockCipherEncrypt`] and [`BlockCipherDecrypt`] from the +/// `cipher` crate. Construct with [`KeyInit::new`]. +#[derive(Clone)] +pub struct Sm4 { + /// 32 round keys derived from the 128-bit master key. + rk: [u32; 32], +} + +impl Drop for Sm4 { + fn drop(&mut self) { + self.rk.zeroize(); + } +} + +impl ZeroizeOnDrop for Sm4 {} + +// ── KeySizeUser / BlockSizeUser / ParBlocksSizeUser ─────────────────────────── + +impl KeySizeUser for Sm4 { + /// SM4 requires a 128-bit (16-byte) key. + type KeySize = U16; +} + +impl BlockSizeUser for Sm4 { + /// SM4 operates on 128-bit (16-byte) blocks. + type BlockSize = U16; +} + +impl ParBlocksSizeUser for Sm4 { + /// No parallel blocks: each block is processed independently. + type ParBlocksSize = U1; +} + +// ── KeyInit ─────────────────────────────────────────────────────────────────── + +impl KeyInit for Sm4 { + fn new(key: &Sm4Key) -> Self { + let mut rk = [0u32; 32]; + // Reason: Sm4Key = Array which Derefs to [u8; 16] via as_slice(). + consts::expand_key(<&[u8; 16]>::try_from(key.as_slice()).unwrap(), &mut rk); + Self { rk } + } +} + +// ── AlgorithmName / Debug ───────────────────────────────────────────────────── + +impl AlgorithmName for Sm4 { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Sm4") + } +} + +impl fmt::Debug for Sm4 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Sm4 { ... }") + } +} + +// ── BlockCipherEncrypt ──────────────────────────────────────────────────────── + +impl BlockCipherEncrypt for Sm4 { + fn encrypt_with_backend(&self, f: impl BlockCipherEncClosure) { + f.call(&Sm4EncBackend(self)); + } +} + +// ── BlockCipherDecrypt ──────────────────────────────────────────────────────── + +impl BlockCipherDecrypt for Sm4 { + fn decrypt_with_backend(&self, f: impl BlockCipherDecClosure) { + f.call(&Sm4DecBackend(self)); + } +} + +// ── Encryption backend ──────────────────────────────────────────────────────── + +struct Sm4EncBackend<'a>(&'a Sm4); + +impl BlockSizeUser for Sm4EncBackend<'_> { + type BlockSize = U16; +} + +impl ParBlocksSizeUser for Sm4EncBackend<'_> { + type ParBlocksSize = U1; +} + +impl BlockCipherEncBackend for Sm4EncBackend<'_> { + #[inline] + fn encrypt_block(&self, mut block: InOut<'_, '_, Block>) { + let mut x = + consts::load_block(<&[u8; 16]>::try_from(block.get_in().as_slice()).unwrap()); + consts::encrypt_rounds(&mut x, &self.0.rk); + consts::store_block( + <&mut [u8; 16]>::try_from(block.get_out().as_mut_slice()).unwrap(), + &x, + ); + } +} + +// ── Decryption backend ──────────────────────────────────────────────────────── + +struct Sm4DecBackend<'a>(&'a Sm4); + +impl BlockSizeUser for Sm4DecBackend<'_> { + type BlockSize = U16; +} + +impl ParBlocksSizeUser for Sm4DecBackend<'_> { + type ParBlocksSize = U1; +} + +impl BlockCipherDecBackend for Sm4DecBackend<'_> { + #[inline] + fn decrypt_block(&self, mut block: InOut<'_, '_, Block>) { + let mut x = + consts::load_block(<&[u8; 16]>::try_from(block.get_in().as_slice()).unwrap()); + consts::decrypt_rounds(&mut x, &self.0.rk); + consts::store_block( + <&mut [u8; 16]>::try_from(block.get_out().as_mut_slice()).unwrap(), + &x, + ); + } +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; + use hex_literal::hex; + + /// GB/T 32907-2016 Appendix A, Example 1 + /// Key = 0123456789ABCDEFFEDCBA9876543210 + /// Plaintext = 0123456789ABCDEFFEDCBA9876543210 + /// Ciphertext = 681EDF34D206965E86B3E94F536E4246 + #[test] + fn test_vector_appendix_a() { + let key = hex!("0123456789ABCDEFFEDCBA9876543210"); + let plaintext = hex!("0123456789ABCDEFFEDCBA9876543210"); + let ciphertext = hex!("681EDF34D206965E86B3E94F536E4246"); + + let cipher = Sm4::new(&key.into()); + + let mut block: Block = plaintext.into(); + cipher.encrypt_block(&mut block); + assert_eq!(block[..], ciphertext[..], "encryption mismatch"); + + cipher.decrypt_block(&mut block); + assert_eq!(block[..], plaintext[..], "decryption mismatch"); + } + + /// GB/T 32907-2016 Appendix A, Example 2: 1 000 000 iterations + /// Repeatedly encrypt the same plaintext 10^6 times. + /// Result must be 595298C7C6FD271F0402F804C33D3F66. + #[test] + #[ignore = "slow (1M iterations)"] + fn test_vector_1m_iterations() { + let key = hex!("0123456789ABCDEFFEDCBA9876543210"); + let expected = hex!("595298C7C6FD271F0402F804C33D3F66"); + + let cipher = Sm4::new(&key.into()); + let mut block: Block = hex!("0123456789ABCDEFFEDCBA9876543210").into(); + + for _ in 0..1_000_000 { + cipher.encrypt_block(&mut block); + } + assert_eq!(block[..], expected[..]); + } + + /// All-zero key, all-zero block: encrypt then decrypt must restore plaintext. + #[test] + fn test_all_zeros_roundtrip() { + let key = [0u8; 16]; + let plaintext = [0u8; 16]; + + let cipher = Sm4::new(&key.into()); + let mut block: Block = plaintext.into(); + cipher.encrypt_block(&mut block); + cipher.decrypt_block(&mut block); + assert_eq!(block[..], plaintext[..]); + } + + /// Arbitrary key/plaintext: roundtrip must restore the original. + #[test] + fn test_roundtrip() { + let key = hex!("FEDCBA98765432100123456789ABCDEF"); + let plaintext = hex!("AABBCCDDEEFF00112233445566778899"); + + let cipher = Sm4::new(&key.into()); + let mut block: Block = plaintext.into(); + + cipher.encrypt_block(&mut block); + assert_ne!(block[..], plaintext[..], "ciphertext must differ from plaintext"); + + cipher.decrypt_block(&mut block); + assert_eq!(block[..], plaintext[..], "roundtrip must restore plaintext"); + } +} diff --git a/src/rustls_provider/aead.rs b/src/rustls_provider/aead.rs new file mode 100644 index 0000000..c20d26b --- /dev/null +++ b/src/rustls_provider/aead.rs @@ -0,0 +1,310 @@ +//! SM4-GCM / SM4-CCM AEAD 实现(rustls TLS 1.3 专用) +//! +//! 此模块仅供 `rustls_provider` 内部使用,不对外公开。 +//! GCM/CCM 是 TLS 1.3 密码套件的必要组成,与通用工作模式不同, +//! 它们与 TLS 记录层协议强耦合,因此保留在 rustls_provider 内。 + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use subtle::ConstantTimeEq; + +use crate::sm4::cipher::{encrypt_block_raw, Sm4Key}; +use crate::error::Error; + +// ── GF(2^128) 乘法 ──────────────────────────────────────────────────────────── + +/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化) +/// +/// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;使用掩码算术替代 +/// 条件分支,消除 cache-timing 和 branch-timing 侧信道。 +fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] { + let mut z = [0u64; 2]; + let mut v = [ + u64::from_be_bytes(y[0..8].try_into().unwrap()), + u64::from_be_bytes(y[8..16].try_into().unwrap()), + ]; + + for &byte_xi in x.iter() { + for bit_idx in (0..8).rev() { + let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64); + z[0] ^= v[0] & mask; + z[1] ^= v[1] & mask; + + let lsb = v[1] & 1; + let carry = v[0] & 1; + v[0] >>= 1; + v[1] = (v[1] >> 1) | (carry << 63); + let reduce_mask = 0u64.wrapping_sub(lsb); + v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask; + } + } + + let mut out = [0u8; 16]; + out[0..8].copy_from_slice(&z[0].to_be_bytes()); + out[8..16].copy_from_slice(&z[1].to_be_bytes()); + out +} + +/// GHASH 认证函数(NIST SP 800-38D §6.4) +fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] { + let mut y = [0u8; 16]; + for chunk in aad.chunks(16) { + let mut block = [0u8; 16]; + block[..chunk.len()].copy_from_slice(chunk); + for i in 0..16 { y[i] ^= block[i]; } + y = gf128_mul(&y, h); + } + for chunk in ciphertext.chunks(16) { + let mut block = [0u8; 16]; + block[..chunk.len()].copy_from_slice(chunk); + for i in 0..16 { y[i] ^= block[i]; } + y = gf128_mul(&y, h); + } + let mut len_block = [0u8; 16]; + len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes()); + len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes()); + for i in 0..16 { y[i] ^= len_block[i]; } + gf128_mul(&y, h) +} + +/// GCM 计数器递增(仅最后 4 字节,GCM 标准) +#[inline] +fn gcm_ctr_inc(counter: &mut [u8; 16]) { + // Reason: GCM 规范中计数器字段只占最后 4 字节(大端 32 位) + for i in (12..16).rev() { + counter[i] = counter[i].wrapping_add(1); + if counter[i] != 0 { break; } + } +} + +// ── GCM ─────────────────────────────────────────────────────────────────────── + +/// SM4-GCM 加密(AEAD),返回 `(密文, 16字节认证标签)` +#[cfg(feature = "alloc")] +pub fn sm4_encrypt_gcm( + key: &[u8; 16], + nonce: &[u8; 12], + aad: &[u8], + plaintext: &[u8], +) -> (Vec, [u8; 16]) { + let sm4 = Sm4Key::new(key); + let rk = sm4.round_keys(); + + let h = encrypt_block_raw(rk, &[0u8; 16]); + + let mut j0 = [0u8; 16]; + j0[..12].copy_from_slice(nonce); + j0[15] = 1; + + let mut ctr = j0; + gcm_ctr_inc(&mut ctr); + + let ciphertext: Vec = { + let mut out = Vec::with_capacity(plaintext.len()); + let mut counter = ctr; + for chunk in plaintext.chunks(16) { + let ks = encrypt_block_raw(rk, &counter); + for (i, &b) in chunk.iter().enumerate() { + out.push(b ^ ks[i]); + } + gcm_ctr_inc(&mut counter); + } + out + }; + + let ghash_val = ghash(&h, aad, &ciphertext); + let ej0 = encrypt_block_raw(rk, &j0); + let mut tag = [0u8; 16]; + for i in 0..16 { tag[i] = ghash_val[i] ^ ej0[i]; } + + (ciphertext, tag) +} + +/// SM4-GCM 解密(AEAD),先验证 tag 再解密 +#[cfg(feature = "alloc")] +pub fn sm4_decrypt_gcm( + key: &[u8; 16], + nonce: &[u8; 12], + aad: &[u8], + ciphertext: &[u8], + tag: &[u8; 16], +) -> Result, Error> { + let sm4 = Sm4Key::new(key); + let rk = sm4.round_keys(); + + let h = encrypt_block_raw(rk, &[0u8; 16]); + + let mut j0 = [0u8; 16]; + j0[..12].copy_from_slice(nonce); + j0[15] = 1; + + // Reason: 先验证 tag 再解密,防止 padding oracle 和选择密文攻击 + let ghash_val = ghash(&h, aad, ciphertext); + let ej0 = encrypt_block_raw(rk, &j0); + let mut expected_tag = [0u8; 16]; + for i in 0..16 { expected_tag[i] = ghash_val[i] ^ ej0[i]; } + + if expected_tag.ct_eq(tag).unwrap_u8() == 0 { + return Err(Error::AuthTagMismatch); + } + + let mut ctr = j0; + gcm_ctr_inc(&mut ctr); + + let mut plaintext = Vec::with_capacity(ciphertext.len()); + let mut counter = ctr; + for chunk in ciphertext.chunks(16) { + let ks = encrypt_block_raw(rk, &counter); + for (i, &b) in chunk.iter().enumerate() { + plaintext.push(b ^ ks[i]); + } + gcm_ctr_inc(&mut counter); + } + Ok(plaintext) +} + +// ── CCM ─────────────────────────────────────────────────────────────────────── + +/// 构造 CCM CBC-MAC(RFC 3610) +fn ccm_cbc_mac( + rk: &[u32; 32], + nonce: &[u8; 12], + aad: &[u8], + message: &[u8], + tag_len: usize, +) -> Result<[u8; 16], Error> { + let q = 3usize; + let has_aad = !aad.is_empty(); + let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1); + + let mut b0 = [0u8; 16]; + b0[0] = flags; + b0[1..13].copy_from_slice(nonce); + let msg_len = message.len() as u32; + b0[13] = (msg_len >> 16) as u8; + b0[14] = (msg_len >> 8) as u8; + b0[15] = msg_len as u8; + + let mut x = encrypt_block_raw(rk, &b0); + + if has_aad { + let aad_len = aad.len(); + let prefix_len = 2 + aad_len; + let padded_len = prefix_len.div_ceil(16) * 16; + let mut aad_buf = [0u8; 512]; + + // Reason: 超过 510 字节需要 4 字节长度编码(RFC 3610 §2.2), + // 当前实现仅支持 2 字节编码,超限时必须拒绝而非静默跳过 AAD。 + if prefix_len > aad_buf.len() { + return Err(Error::InvalidInputLength); + } + + aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes()); + aad_buf[2..2 + aad_len].copy_from_slice(aad); + for chunk in aad_buf[..padded_len].chunks(16) { + let block: [u8; 16] = chunk.try_into().unwrap(); + for i in 0..16 { x[i] ^= block[i]; } + x = encrypt_block_raw(rk, &x); + } + } + + for chunk in message.chunks(16) { + let mut block = [0u8; 16]; + block[..chunk.len()].copy_from_slice(chunk); + for i in 0..16 { x[i] ^= block[i]; } + x = encrypt_block_raw(rk, &x); + } + Ok(x) +} + +/// SM4-CCM 加密(AEAD),输出 `密文 || tag` +#[cfg(feature = "alloc")] +pub fn sm4_encrypt_ccm( + key: &[u8; 16], + nonce: &[u8; 12], + aad: &[u8], + plaintext: &[u8], + tag_len: usize, +) -> Result, Error> { + assert!( + (4..=16).contains(&tag_len) && tag_len % 2 == 0, + "CCM tag_len 须为 4~16 的偶数" + ); + + let sm4 = Sm4Key::new(key); + let rk = sm4.round_keys(); + + let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len)?; + + let mut a0 = [0u8; 16]; + a0[0] = 2u8; + a0[1..13].copy_from_slice(nonce); + let s0 = encrypt_block_raw(rk, &a0); + + let mut enc_tag = [0u8; 16]; + for i in 0..tag_len { enc_tag[i] = t[i] ^ s0[i]; } + + let mut out = Vec::with_capacity(plaintext.len() + tag_len); + for (block_idx, chunk) in plaintext.chunks(16).enumerate() { + let mut a_i = a0; + let ctr_val = (block_idx as u32) + 1; + a_i[13] = (ctr_val >> 16) as u8; + a_i[14] = (ctr_val >> 8) as u8; + a_i[15] = ctr_val as u8; + let ks = encrypt_block_raw(rk, &a_i); + for (i, &b) in chunk.iter().enumerate() { + out.push(b ^ ks[i]); + } + } + out.extend_from_slice(&enc_tag[..tag_len]); + Ok(out) +} + +/// SM4-CCM 解密(AEAD),先验证 tag 再返回明文 +#[cfg(feature = "alloc")] +pub fn sm4_decrypt_ccm( + key: &[u8; 16], + nonce: &[u8; 12], + aad: &[u8], + ciphertext_with_tag: &[u8], + tag_len: usize, +) -> Result, Error> { + if ciphertext_with_tag.len() < tag_len { + return Err(Error::InvalidInputLength); + } + let ct = &ciphertext_with_tag[..ciphertext_with_tag.len() - tag_len]; + let received_tag = &ciphertext_with_tag[ciphertext_with_tag.len() - tag_len..]; + + let sm4 = Sm4Key::new(key); + let rk = sm4.round_keys(); + + let mut a0 = [0u8; 16]; + a0[0] = 2u8; + a0[1..13].copy_from_slice(nonce); + let s0 = encrypt_block_raw(rk, &a0); + + let mut plaintext = Vec::with_capacity(ct.len()); + for (block_idx, chunk) in ct.chunks(16).enumerate() { + let mut a_i = a0; + let ctr_val = (block_idx as u32) + 1; + a_i[13] = (ctr_val >> 16) as u8; + a_i[14] = (ctr_val >> 8) as u8; + a_i[15] = ctr_val as u8; + let ks = encrypt_block_raw(rk, &a_i); + for (i, &b) in chunk.iter().enumerate() { + plaintext.push(b ^ ks[i]); + } + } + + let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len)?; + let mut expected_tag = [0u8; 16]; + for i in 0..tag_len { expected_tag[i] = t[i] ^ s0[i]; } + + // Reason: 先验证后解密,防止选择密文攻击 + if expected_tag[..tag_len].ct_eq(received_tag).unwrap_u8() == 0 { + return Err(Error::AuthTagMismatch); + } + + Ok(plaintext) +} diff --git a/src/rustls_provider/mod.rs b/src/rustls_provider/mod.rs index 11d0aa3..0c11d14 100644 --- a/src/rustls_provider/mod.rs +++ b/src/rustls_provider/mod.rs @@ -18,6 +18,7 @@ pub mod hmac; pub mod kx; pub mod sign; pub mod tls13; +pub(crate) mod aead; pub mod verify; /// 构造国密 `CryptoProvider` diff --git a/src/rustls_provider/tls13.rs b/src/rustls_provider/tls13.rs index 5578114..162fed6 100644 --- a/src/rustls_provider/tls13.rs +++ b/src/rustls_provider/tls13.rs @@ -14,7 +14,7 @@ use rustls::error::Error; use rustls::version::TLS13_VERSION; use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite}; -use crate::sm4::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm}; +use super::aead::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm}; // ── HKDF(零代码复用 rustls 内置 HkdfUsingHmac)────────────────────────────── diff --git a/src/sm3/compress.rs b/src/sm3/compress.rs index a8b8c82..e2755d3 100644 --- a/src/sm3/compress.rs +++ b/src/sm3/compress.rs @@ -44,6 +44,8 @@ fn p1(x: u32) -> u32 { /// - 轮函数分两段(j=0..15 和 j=16..63),消除 ff/gg 中的 `if j < 16` 运行时分支 /// - T_j 常量使用预计算表,消除旋转运算 /// - W' 数组内联为 w[j] ^ w[j+4],避免额外分配 +/// - `#[inline(always)]` 允许调用方(通常是循环)内联后做跨块优化 +#[inline(always)] pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { // ── 消息扩展 ───────────────────────────────────────────────────────────── // W[0..15]: 直接从块加载(大端) @@ -61,58 +63,50 @@ pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state; // Reason: 将 64 轮分两段展开,消除 ff/gg/T 中的 if 分支。 - // j = 0..15:FF = x^y^z,GG = x^y^z - for j in 0..16 { - let ss1 = a - .rotate_left(12) - .wrapping_add(e) - .wrapping_add(T[j]) - .rotate_left(7); - let ss2 = ss1 ^ a.rotate_left(12); - let tt1 = (a ^ b ^ c) - .wrapping_add(d) - .wrapping_add(ss2) - .wrapping_add(w[j] ^ w[j + 4]); - let tt2 = (e ^ f ^ g) - .wrapping_add(h) - .wrapping_add(ss1) - .wrapping_add(w[j]); - d = c; - c = b.rotate_left(9); - b = a; - a = tt1; - h = g; - g = f.rotate_left(19); - f = e; - e = p0(tt2); + // 使用宏在编译期展开,避免循环计数器开销,使编译器能进行跨轮常量折叠和指令调度。 + + macro_rules! round_xor { + ($j:expr) => {{ + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[$j]).rotate_left(7); + let ss2 = ss1 ^ a.rotate_left(12); + let tt1 = (a ^ b ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[$j] ^ w[$j + 4]); + let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[$j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + }}; } - // j = 16..63:FF = majority(x,y,z),GG = choice(x,y,z) - for j in 16..64 { - let ss1 = a - .rotate_left(12) - .wrapping_add(e) - .wrapping_add(T[j]) - .rotate_left(7); - let ss2 = ss1 ^ a.rotate_left(12); - let tt1 = ((a & b) | (a & c) | (b & c)) - .wrapping_add(d) - .wrapping_add(ss2) - .wrapping_add(w[j] ^ w[j + 4]); - let tt2 = ((e & f) | (!e & g)) - .wrapping_add(h) - .wrapping_add(ss1) - .wrapping_add(w[j]); - d = c; - c = b.rotate_left(9); - b = a; - a = tt1; - h = g; - g = f.rotate_left(19); - f = e; - e = p0(tt2); + macro_rules! round_maj { + ($j:expr) => {{ + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[$j]).rotate_left(7); + let ss2 = ss1 ^ a.rotate_left(12); + let tt1 = ((a & b) | (a & c) | (b & c)).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[$j] ^ w[$j + 4]); + let tt2 = ((e & f) | (!e & g)).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[$j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + }}; } + // j = 0..15:FF = x^y^z,GG = x^y^z + round_xor!(0); round_xor!(1); round_xor!(2); round_xor!(3); + round_xor!(4); round_xor!(5); round_xor!(6); round_xor!(7); + round_xor!(8); round_xor!(9); round_xor!(10); round_xor!(11); + round_xor!(12); round_xor!(13); round_xor!(14); round_xor!(15); + + // j = 16..63:FF = majority(x,y,z),GG = choice(x,y,z) + round_maj!(16); round_maj!(17); round_maj!(18); round_maj!(19); + round_maj!(20); round_maj!(21); round_maj!(22); round_maj!(23); + round_maj!(24); round_maj!(25); round_maj!(26); round_maj!(27); + round_maj!(28); round_maj!(29); round_maj!(30); round_maj!(31); + round_maj!(32); round_maj!(33); round_maj!(34); round_maj!(35); + round_maj!(36); round_maj!(37); round_maj!(38); round_maj!(39); + round_maj!(40); round_maj!(41); round_maj!(42); round_maj!(43); + round_maj!(44); round_maj!(45); round_maj!(46); round_maj!(47); + round_maj!(48); round_maj!(49); round_maj!(50); round_maj!(51); + round_maj!(52); round_maj!(53); round_maj!(54); round_maj!(55); + round_maj!(56); round_maj!(57); round_maj!(58); round_maj!(59); + round_maj!(60); round_maj!(61); round_maj!(62); round_maj!(63); + state[0] ^= a; state[1] ^= b; state[2] ^= c; diff --git a/src/sm3/mod.rs b/src/sm3/mod.rs index d8db8ad..7f3bbdc 100644 --- a/src/sm3/mod.rs +++ b/src/sm3/mod.rs @@ -39,13 +39,13 @@ pub const DIGEST_LEN: usize = 32; #[derive(Clone)] pub struct Sm3Hasher { /// 当前状态(8 × u32) - state: [u32; 8], + pub(super) state: [u32; 8], /// 未处理的字节缓冲区(最多 64 字节) - buffer: [u8; 64], + pub(super) buffer: [u8; 64], /// 缓冲区已填充字节数 - buf_len: usize, + pub(super) buf_len: usize, /// 已处理的总位数(用于最终填充) - bit_len: u64, + pub(super) bit_len: u64, } impl Sm3Hasher { @@ -285,7 +285,12 @@ impl HmacSm3 { impl zeroize::Zeroize for HmacSm3 { fn zeroize(&mut self) { self.opad_key.zeroize(); - // inner 的 Sm3Hasher 不含密钥材料,无需特殊清零 + // Reason: inner 在 new() 中已处理含密钥的 ipad 前缀, + // 其 state/buffer 字段间接含密钥材料,必须一并清零。 + self.inner.state.zeroize(); + self.inner.buffer.zeroize(); + self.inner.buf_len = 0; + self.inner.bit_len = 0; } } diff --git a/src/sm4/cipher.rs b/src/sm4/cipher.rs index 8a89b4d..ed768df 100644 --- a/src/sm4/cipher.rs +++ b/src/sm4/cipher.rs @@ -425,7 +425,8 @@ impl Sm4Key { store_block(block, &x); } - /// 获取轮密钥引用(仅供 modes 子模块使用) + /// 获取轮密钥引用(仅供 rustls_provider::aead 使用) + #[cfg(feature = "rustls-provider")] pub(crate) fn round_keys(&self) -> &[u32; 32] { &self.rk } @@ -496,7 +497,8 @@ fn decrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) { x.reverse(); } -/// 辅助:加密独立块(不缓存轮密钥,供 modes 一次性使用) +/// 辅助:加密独立块(供 rustls_provider::aead 使用) +#[cfg(feature = "rustls-provider")] pub(crate) fn encrypt_block_raw(rk: &[u32; 32], block: &[u8; 16]) -> [u8; 16] { let mut x = load_block(block); encrypt_rounds(&mut x, rk); diff --git a/src/sm4/mod.rs b/src/sm4/mod.rs index 9b89f8e..fa11679 100644 --- a/src/sm4/mod.rs +++ b/src/sm4/mod.rs @@ -1,8 +1,8 @@ //! SM4 分组密码(GB/T 32907-2016) -//! 实现见各子模块。 +//! +//! 提供核心块加密功能。工作模式(CBC/CTR/GCM 等)请使用 RustCrypto 生态的 +//! `cbc`、`ctr`、`aes-gcm` 等 crate 与 [`Sm4Key`] 组合使用。 -mod cipher; -mod modes; +pub(crate) mod cipher; pub use cipher::Sm4Key; -pub use modes::*; diff --git a/src/sm4/modes.rs b/src/sm4/modes.rs deleted file mode 100644 index d230285..0000000 --- a/src/sm4/modes.rs +++ /dev/null @@ -1,860 +0,0 @@ -//! SM4 分组模式(GB/T 32907-2016,GB/T 17964-2021) -//! -//! 支持:ECB、CBC、OFB、CFB、CTR、GCM(AEAD)、CCM(AEAD)、XTS -//! -//! # 安全说明 -//! -//! - GCM/CCM 认证标签比较使用 `subtle::ConstantTimeEq`,防止时序侧信道 -//! - CCM 严格遵循"先验证后解密"原则(Encrypt-then-MAC 的接收端验证) -//! - 所有密钥材料通过 [`Sm4Key`] 在 Drop 时自动清零 - -#[cfg(feature = "alloc")] -use alloc::vec::Vec; - -use subtle::ConstantTimeEq; - -use super::cipher::{encrypt_block_raw, Sm4Key}; - -// ── ECB ────────────────────────────────────────────────────────────────────── - -/// SM4-ECB 加密(无填充,`data` 必须为 16 字节整倍数) -/// -/// # 参数 -/// - `key`: 16 字节密钥 -/// - `data`: 明文(长度须为 16 的倍数) -/// -/// # 返回 -/// 密文字节向量 -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - data.chunks(16) - .flat_map(|chunk| { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - sm4.encrypt_block(&mut block); - block - }) - .collect() -} - -/// SM4-ECB 解密(无填充,`data` 必须为 16 字节整倍数) -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - data.chunks(16) - .flat_map(|chunk| { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - sm4.decrypt_block(&mut block); - block - }) - .collect() -} - -// ── CBC ────────────────────────────────────────────────────────────────────── - -/// SM4-CBC 加密(`plaintext.len()` 须为 16 字节整倍数) -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_cbc(key: &[u8; 16], iv: &[u8; 16], plaintext: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut prev = *iv; - plaintext - .chunks(16) - .flat_map(|chunk| { - let mut block = [0u8; 16]; - let len = chunk.len().min(16); - block[..len].copy_from_slice(&chunk[..len]); - for i in 0..16 { - block[i] ^= prev[i]; - } - sm4.encrypt_block(&mut block); - prev = block; - block - }) - .collect() -} - -/// SM4-CBC 解密(`ciphertext.len()` 须为 16 字节整倍数) -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_cbc(key: &[u8; 16], iv: &[u8; 16], ciphertext: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut prev = *iv; - ciphertext - .chunks(16) - .flat_map(|chunk| { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - let ct = block; - sm4.decrypt_block(&mut block); - for i in 0..16 { - block[i] ^= prev[i]; - } - prev = ct; - block - }) - .collect() -} - -// ── OFB ────────────────────────────────────────────────────────────────────── - -/// SM4-OFB 加密/解密(自反模式,加解密逻辑相同) -#[cfg(feature = "alloc")] -pub fn sm4_crypt_ofb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut feedback = *iv; - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - sm4.encrypt_block(&mut feedback); - for (i, &b) in chunk.iter().enumerate() { - out.push(b ^ feedback[i]); - } - } - out -} - -// ── CFB ────────────────────────────────────────────────────────────────────── - -/// SM4-CFB 加密 -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut feedback = *iv; - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - let mut ks = feedback; - sm4.encrypt_block(&mut ks); - let mut ct_block = [0u8; 16]; - for (i, &b) in chunk.iter().enumerate() { - ct_block[i] = b ^ ks[i]; - } - feedback = ct_block; - out.extend_from_slice(&ct_block[..chunk.len()]); - } - out -} - -/// SM4-CFB 解密 -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut feedback = *iv; - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - let mut ks = feedback; - sm4.encrypt_block(&mut ks); - let mut ct_block = [0u8; 16]; - ct_block[..chunk.len()].copy_from_slice(chunk); - // Reason: CFB 解密中 feedback 使用密文块,而非明文块 - feedback = ct_block; - for (i, &b) in chunk.iter().enumerate() { - out.push(b ^ ks[i]); - } - } - out -} - -// ── CTR ────────────────────────────────────────────────────────────────────── - -/// CTR 计数器递增(全 128 位大端) -#[inline] -fn ctr_inc(counter: &mut [u8; 16]) { - for i in (0..16).rev() { - counter[i] = counter[i].wrapping_add(1); - if counter[i] != 0 { - break; - } - } -} - -/// SM4-CTR 加密/解密(自反模式) -#[cfg(feature = "alloc")] -pub fn sm4_crypt_ctr(key: &[u8; 16], nonce: &[u8; 16], data: &[u8]) -> Vec { - let sm4 = Sm4Key::new(key); - let mut counter = *nonce; - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - let mut ks = counter; - sm4.encrypt_block(&mut ks); - for (i, &b) in chunk.iter().enumerate() { - out.push(b ^ ks[i]); - } - ctr_inc(&mut counter); - } - out -} - -// ── GCM ────────────────────────────────────────────────────────────────────── - -/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化) -/// -/// # 安全性 -/// 使用掩码算术替代秘密依赖的条件分支,消除时序侧信道: -/// - `mask_xi`:由当前标量位生成的 u64 全掩码,替代 `if bit == 1` -/// - `reduce_mask`:由 LSB 生成的 u64 全掩码,替代 `if lsb == 1` -/// -/// # 性能优化 -/// 将内部状态从 `[u8; 16]` 改为 `[u64; 2]`(大端),使每次迭代的 -/// XOR/移位/规约从 16 次字节操作降至 ~6 次 64 位操作,约 4-6× 提速。 -/// -/// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;原条件分支泄露 H 的汉明重量, -/// 是 cache-timing 和 branch-timing 攻击的经典目标(参见 Bricout 等 2016)。 -/// u64 向量化保持完全常量时间,同时大幅减少指令数。 -fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] { - // Reason: 将 16 字节表示为 2 个大端 u64,便于用 64 位操作替代逐字节循环, - // XOR/移位从 16 次字节操作缩减至 2 次 u64 操作,指令数降低约 8×。 - let mut z = [0u64; 2]; - let mut v = [ - u64::from_be_bytes(y[0..8].try_into().unwrap()), - u64::from_be_bytes(y[8..16].try_into().unwrap()), - ]; - - for &byte_xi in x.iter() { - for bit_idx in (0..8).rev() { - // Reason: 0u64.wrapping_sub(1) = 0xFFFF...,wrapping_sub(0) = 0x0000... - // 单次 u64 掩码覆盖原来 16 次 u8 掩码操作 - let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64); - z[0] ^= v[0] & mask; - z[1] ^= v[1] & mask; - - // GF(2^128) 右移 1 位(= 乘以 x),带规约多项式 x^128+x^7+x^2+x+1 - // Reason: v[0] 的 bit 0(= 大端第 64 位)移入 v[1] 的 bit 63, - // v[1] 的 bit 0(= GF 元素 x^0 系数)移出后触发规约。 - let lsb = v[1] & 1; - let carry = v[0] & 1; - v[0] >>= 1; - v[1] = (v[1] >> 1) | (carry << 63); - // Reason: 规约项 0xE1_00...00 对应 x^7+x^2+x+1 写入最高字节(v[0] MSB 端), - // 掩码替代 if lsb,执行路径完全相同 - let reduce_mask = 0u64.wrapping_sub(lsb); - v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask; - } - } - - let mut out = [0u8; 16]; - out[0..8].copy_from_slice(&z[0].to_be_bytes()); - out[8..16].copy_from_slice(&z[1].to_be_bytes()); - out -} - -/// GHASH 认证函数(NIST SP 800-38D §6.4) -fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] { - let mut y = [0u8; 16]; - for chunk in aad.chunks(16) { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - for i in 0..16 { - y[i] ^= block[i]; - } - y = gf128_mul(&y, h); - } - for chunk in ciphertext.chunks(16) { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - for i in 0..16 { - y[i] ^= block[i]; - } - y = gf128_mul(&y, h); - } - let mut len_block = [0u8; 16]; - len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes()); - len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes()); - for i in 0..16 { - y[i] ^= len_block[i]; - } - gf128_mul(&y, h) -} - -/// GCM 计数器递增(仅最后 4 字节,GCM 标准) -#[inline] -fn gcm_ctr_inc(counter: &mut [u8; 16]) { - // Reason: GCM 规范中 J0 的计数器字段只占最后 4 字节(大端 32 位) - for i in (12..16).rev() { - counter[i] = counter[i].wrapping_add(1); - if counter[i] != 0 { - break; - } - } -} - -/// SM4-GCM 加密(AEAD) -/// -/// # 参数 -/// - `key`: 16 字节密钥 -/// - `nonce`: 12 字节 nonce(GCM 标准推荐) -/// - `aad`: 附加认证数据(不加密,但参与认证) -/// - `plaintext`: 明文 -/// -/// # 返回 -/// `(密文, 16字节认证标签)` -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_gcm( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - plaintext: &[u8], -) -> (Vec, [u8; 16]) { - let sm4 = Sm4Key::new(key); - let rk = sm4.round_keys(); - - let h = encrypt_block_raw(rk, &[0u8; 16]); - - let mut j0 = [0u8; 16]; - j0[..12].copy_from_slice(nonce); - j0[15] = 1; - - let mut ctr = j0; - gcm_ctr_inc(&mut ctr); - - let ciphertext: Vec = { - let mut out = Vec::with_capacity(plaintext.len()); - let mut counter = ctr; - for chunk in plaintext.chunks(16) { - let ks = encrypt_block_raw(rk, &counter); - for (i, &b) in chunk.iter().enumerate() { - out.push(b ^ ks[i]); - } - gcm_ctr_inc(&mut counter); - } - out - }; - - let ghash_val = ghash(&h, aad, &ciphertext); - let ej0 = encrypt_block_raw(rk, &j0); - let mut tag = [0u8; 16]; - for i in 0..16 { - tag[i] = ghash_val[i] ^ ej0[i]; - } - - (ciphertext, tag) -} - -/// SM4-GCM 解密(AEAD) -/// -/// **先验证认证标签,验证通过后才解密。** -/// -/// # 错误 -/// 返回 [`crate::error::Error::AuthTagMismatch`] 当标签验证失败。 -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_gcm( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - ciphertext: &[u8], - tag: &[u8; 16], -) -> Result, crate::error::Error> { - let sm4 = Sm4Key::new(key); - let rk = sm4.round_keys(); - - let h = encrypt_block_raw(rk, &[0u8; 16]); - - let mut j0 = [0u8; 16]; - j0[..12].copy_from_slice(nonce); - j0[15] = 1; - - // Reason: 先验证 tag 再解密,防止 padding oracle 和选择密文攻击 - let ghash_val = ghash(&h, aad, ciphertext); - let ej0 = encrypt_block_raw(rk, &j0); - let mut expected_tag = [0u8; 16]; - for i in 0..16 { - expected_tag[i] = ghash_val[i] ^ ej0[i]; - } - - // 常量时间 tag 比较,防止时序侧信道 - if expected_tag.ct_eq(tag).unwrap_u8() == 0 { - return Err(crate::error::Error::AuthTagMismatch); - } - - let mut ctr = j0; - gcm_ctr_inc(&mut ctr); - - let mut plaintext = Vec::with_capacity(ciphertext.len()); - let mut counter = ctr; - for chunk in ciphertext.chunks(16) { - let ks = encrypt_block_raw(rk, &counter); - for (i, &b) in chunk.iter().enumerate() { - plaintext.push(b ^ ks[i]); - } - gcm_ctr_inc(&mut counter); - } - Ok(plaintext) -} - -// ── CCM ────────────────────────────────────────────────────────────────────── - -/// 构造 CCM CBC-MAC(RFC 3610) -/// -/// # 错误 -/// `aad` 超过 510 字节时返回 `Error::InvalidInputLength`(当前实现仅支持 2 字节长度编码)。 -fn ccm_cbc_mac( - rk: &[u32; 32], - nonce: &[u8; 12], - aad: &[u8], - message: &[u8], - tag_len: usize, -) -> Result<[u8; 16], crate::error::Error> { - let q = 3usize; // nonce=12B 时 q=15-12=3 - let has_aad = !aad.is_empty(); - let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1); - - let mut b0 = [0u8; 16]; - b0[0] = flags; - b0[1..13].copy_from_slice(nonce); - let msg_len = message.len() as u32; - b0[13] = (msg_len >> 16) as u8; - b0[14] = (msg_len >> 8) as u8; - b0[15] = msg_len as u8; - - let mut x = encrypt_block_raw(rk, &b0); - - if has_aad { - let aad_len = aad.len(); - // Reason: CCM AAD 前缀 2 字节长度 + AAD 数据,补零至 16 字节对齐 - let prefix_len = 2 + aad_len; - let padded_len = prefix_len.div_ceil(16) * 16; - let mut aad_buf = [0u8; 512]; // 足够大的栈缓冲区(支持 AAD ≤ 510 字节) - - // Reason: 超过 510 字节需要 4 字节长度编码(RFC 3610 §2.2), - // 当前实现仅支持 2 字节编码,超限时必须拒绝而非静默跳过 AAD。 - // 静默跳过会导致认证标签不包含 AAD,攻击者可随意篡改 AAD 而不被检测。 - if prefix_len > aad_buf.len() { - return Err(crate::error::Error::InvalidInputLength); - } - - aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes()); - aad_buf[2..2 + aad_len].copy_from_slice(aad); - for chunk in aad_buf[..padded_len].chunks(16) { - let block: [u8; 16] = chunk.try_into().unwrap(); - for i in 0..16 { - x[i] ^= block[i]; - } - x = encrypt_block_raw(rk, &x); - } - } - - for chunk in message.chunks(16) { - let mut block = [0u8; 16]; - block[..chunk.len()].copy_from_slice(chunk); - for i in 0..16 { - x[i] ^= block[i]; - } - x = encrypt_block_raw(rk, &x); - } - Ok(x) -} - -/// SM4-CCM 加密(AEAD) -/// -/// # 参数 -/// - `nonce`: 12 字节 -/// - `tag_len`: 认证标签长度,须为 4/6/8/10/12/14/16 之一 -/// -/// # 返回 -/// 密文 || 认证标签(`tag_len` 字节) -/// -/// # 错误 -/// - `aad` 超过 510 字节时返回 `Error::InvalidInputLength` -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_ccm( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - plaintext: &[u8], - tag_len: usize, -) -> Result, crate::error::Error> { - assert!( - (4..=16).contains(&tag_len) && tag_len % 2 == 0, - "CCM tag_len 须为 4~16 的偶数" - ); - - let sm4 = Sm4Key::new(key); - let rk = sm4.round_keys(); - - let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len)?; - - let mut a0 = [0u8; 16]; - a0[0] = 2u8; // q-1 = 3-1 = 2 - a0[1..13].copy_from_slice(nonce); - let s0 = encrypt_block_raw(rk, &a0); - - let mut enc_tag = [0u8; 16]; - for i in 0..tag_len { - enc_tag[i] = t[i] ^ s0[i]; - } - - let mut out = Vec::with_capacity(plaintext.len() + tag_len); - for (block_idx, chunk) in plaintext.chunks(16).enumerate() { - let mut a_i = a0; - let ctr_val = (block_idx as u32) + 1; - a_i[13] = (ctr_val >> 16) as u8; - a_i[14] = (ctr_val >> 8) as u8; - a_i[15] = ctr_val as u8; - let ks = encrypt_block_raw(rk, &a_i); - for (i, &b) in chunk.iter().enumerate() { - out.push(b ^ ks[i]); - } - } - out.extend_from_slice(&enc_tag[..tag_len]); - Ok(out) -} - -/// SM4-CCM 解密(AEAD) -/// -/// **先验证认证标签,验证通过后才解密。** -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_ccm( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - ciphertext_with_tag: &[u8], - tag_len: usize, -) -> Result, crate::error::Error> { - if ciphertext_with_tag.len() < tag_len { - return Err(crate::error::Error::InvalidInputLength); - } - let ct = &ciphertext_with_tag[..ciphertext_with_tag.len() - tag_len]; - let received_tag = &ciphertext_with_tag[ciphertext_with_tag.len() - tag_len..]; - - let sm4 = Sm4Key::new(key); - let rk = sm4.round_keys(); - - let mut a0 = [0u8; 16]; - a0[0] = 2u8; - a0[1..13].copy_from_slice(nonce); - let s0 = encrypt_block_raw(rk, &a0); - - // Step 1: CTR 解密密文(得到候选明文) - let mut plaintext = Vec::with_capacity(ct.len()); - for (block_idx, chunk) in ct.chunks(16).enumerate() { - let mut a_i = a0; - let ctr_val = (block_idx as u32) + 1; - a_i[13] = (ctr_val >> 16) as u8; - a_i[14] = (ctr_val >> 8) as u8; - a_i[15] = ctr_val as u8; - let ks = encrypt_block_raw(rk, &a_i); - for (i, &b) in chunk.iter().enumerate() { - plaintext.push(b ^ ks[i]); - } - } - - // Step 2: 对候选明文重新计算 CBC-MAC - let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len)?; - let mut expected_tag = [0u8; 16]; - for i in 0..tag_len { - expected_tag[i] = t[i] ^ s0[i]; - } - - // Step 3: 常量时间比较,验证通过才返回明文 - // Reason: 先验证后解密,防止选择密文攻击 - if expected_tag[..tag_len].ct_eq(received_tag).unwrap_u8() == 0 { - return Err(crate::error::Error::AuthTagMismatch); - } - - Ok(plaintext) -} - -// ── GCM/CCM 合并格式(TLS 适配)──────────────────────────────────────────────── - -/// SM4-GCM 加密(合并输出格式:`ciphertext || tag`) -/// -/// TLS 记录层要求 AEAD 输出为单一缓冲区,此函数将密文和 16 字节 tag 合并返回。 -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_gcm_combined( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - plaintext: &[u8], -) -> Vec { - let (mut ct, tag) = sm4_encrypt_gcm(key, nonce, aad, plaintext); - ct.extend_from_slice(&tag); - ct -} - -/// SM4-GCM 解密(合并输入格式:`ciphertext || tag`) -/// -/// 输入必须至少 16 字节(tag 长度);先验证 tag 再解密。 -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_gcm_combined( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - ciphertext_with_tag: &[u8], -) -> Result, crate::error::Error> { - if ciphertext_with_tag.len() < 16 { - return Err(crate::error::Error::InvalidInputLength); - } - let ct_len = ciphertext_with_tag.len() - 16; - let ct = &ciphertext_with_tag[..ct_len]; - let tag: &[u8; 16] = ciphertext_with_tag[ct_len..].try_into().unwrap(); - sm4_decrypt_gcm(key, nonce, aad, ct, tag) -} - -/// SM4-CCM 加密(tag_len = 16,合并输出格式) -/// -/// TLS 1.3 `TLS_SM4_CCM_SM3` 使用 16 字节 tag。 -/// 等同于 `sm4_encrypt_ccm`(其输出已是 ciphertext||tag 合并格式)。 -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_ccm_combined( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - plaintext: &[u8], -) -> Result, crate::error::Error> { - sm4_encrypt_ccm(key, nonce, aad, plaintext, 16) -} - -/// SM4-CCM 解密(tag_len = 16,合并输入格式) -/// -/// 等同于 `sm4_decrypt_ccm(..., 16)`。 -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_ccm_combined( - key: &[u8; 16], - nonce: &[u8; 12], - aad: &[u8], - ciphertext_with_tag: &[u8], -) -> Result, crate::error::Error> { - sm4_decrypt_ccm(key, nonce, aad, ciphertext_with_tag, 16) -} - -// ── XTS ────────────────────────────────────────────────────────────────────── - -/// GF(2^128) 乘以 α(XTS tweak 更新) -fn xts_mul_alpha(tweak: &mut [u8; 16]) { - // Reason: XTS 使用反射位序的 GF(2^128),对应右移 + 0xE1 规约 - let carry = tweak[15] & 1; - for i in (1..16).rev() { - tweak[i] = (tweak[i] >> 1) | ((tweak[i - 1] & 1) << 7); - } - tweak[0] >>= 1; - if carry == 1 { - tweak[0] ^= 0xE1; - } -} - -/// SM4-XTS 加密(磁盘加密模式,GB/T 17964-2021) -/// -/// # 参数 -/// - `key1`: 数据加密密钥(16 字节) -/// - `key2`: tweak 加密密钥(16 字节) -/// - `tweak_sector`: 扇区号(16 字节,通常为扇区编号的小端表示) -/// - `data`: 明文(须为 16 字节整倍数,不支持非对齐输入) -/// -/// # 错误 -/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。 -/// -/// # 注意 -/// XTS 的 ciphertext stealing(非对齐末尾块处理)超出本实现范围, -/// 调用方须保证输入对齐;非对齐时须先在应用层填充后再调用。 -#[cfg(feature = "alloc")] -pub fn sm4_encrypt_xts( - key1: &[u8; 16], - key2: &[u8; 16], - tweak_sector: &[u8; 16], - data: &[u8], -) -> Result, crate::error::Error> { - // Reason: 非对齐输入在旧实现中被静默丢弃(最后不足 16 字节块跳过), - // 导致密文比明文短而调用方无感知。拒绝非对齐输入防止数据静默丢失。 - if data.is_empty() || data.len() % 16 != 0 { - return Err(crate::error::Error::InvalidInputLength); - } - - let sm4_1 = Sm4Key::new(key1); - let sm4_2 = Sm4Key::new(key2); - let mut tweak = *tweak_sector; - sm4_2.encrypt_block(&mut tweak); - - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - let mut block = [0u8; 16]; - for i in 0..16 { - block[i] = chunk[i] ^ tweak[i]; - } - sm4_1.encrypt_block(&mut block); - for i in 0..16 { - out.push(block[i] ^ tweak[i]); - } - xts_mul_alpha(&mut tweak); - } - Ok(out) -} - -/// SM4-XTS 解密(磁盘加密模式,GB/T 17964-2021) -/// -/// # 错误 -/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。 -#[cfg(feature = "alloc")] -pub fn sm4_decrypt_xts( - key1: &[u8; 16], - key2: &[u8; 16], - tweak_sector: &[u8; 16], - data: &[u8], -) -> Result, crate::error::Error> { - // Reason: 同 sm4_encrypt_xts,拒绝非对齐输入防止数据静默丢失。 - if data.is_empty() || data.len() % 16 != 0 { - return Err(crate::error::Error::InvalidInputLength); - } - - let sm4_1 = Sm4Key::new(key1); - let sm4_2 = Sm4Key::new(key2); - let mut tweak = *tweak_sector; - sm4_2.encrypt_block(&mut tweak); - - let mut out = Vec::with_capacity(data.len()); - for chunk in data.chunks(16) { - let mut block = [0u8; 16]; - for i in 0..16 { - block[i] = chunk[i] ^ tweak[i]; - } - sm4_1.decrypt_block(&mut block); - for i in 0..16 { - out.push(block[i] ^ tweak[i]); - } - xts_mul_alpha(&mut tweak); - } - Ok(out) -} - -// ── 测试 ────────────────────────────────────────────────────────────────────── - -#[cfg(test)] -#[cfg(feature = "alloc")] -mod tests { - use super::*; - - /// GB/T 32907-2016 附录 B:CBC 模式测试向量 - #[test] - fn test_cbc_vector() { - let key = [ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10, - ]; - let iv = [ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10, - ]; - let plain = [ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10, - ]; - let ct = sm4_encrypt_cbc(&key, &iv, &plain); - let pt = sm4_decrypt_cbc(&key, &iv, &ct); - assert_eq!(pt, plain, "CBC 往返解密失败"); - } - - /// GCM 加解密往返测试 - #[test] - fn test_gcm_roundtrip() { - let key = [0u8; 16]; - let nonce = [1u8; 12]; - let aad = b"additional data"; - let plain = b"hello gcm world!"; - - let (ct, tag) = sm4_encrypt_gcm(&key, &nonce, aad, plain); - let pt = sm4_decrypt_gcm(&key, &nonce, aad, &ct, &tag).unwrap(); - assert_eq!(pt, plain, "GCM 往返解密失败"); - } - - /// GCM tag 篡改检测 - #[test] - fn test_gcm_tag_tamper() { - let key = [0u8; 16]; - let nonce = [0u8; 12]; - let (ct, mut tag) = sm4_encrypt_gcm(&key, &nonce, b"", b"secret"); - tag[0] ^= 1; - assert!( - sm4_decrypt_gcm(&key, &nonce, b"", &ct, &tag).is_err(), - "篡改 tag 后应返回错误" - ); - } - - /// CCM 加解密往返测试 - #[test] - fn test_ccm_roundtrip() { - let key = [0u8; 16]; - let nonce = [2u8; 12]; - let aad = b"ccm aad"; - let plain = b"ccm plaintext!!!"; - - let ct = sm4_encrypt_ccm(&key, &nonce, aad, plain, 16).unwrap(); - let pt = sm4_decrypt_ccm(&key, &nonce, aad, &ct, 16).unwrap(); - assert_eq!(pt, plain, "CCM 往返解密失败"); - } - - /// CCM tag 篡改检测(先验证后解密原则验证) - #[test] - fn test_ccm_tag_tamper() { - let key = [0u8; 16]; - let nonce = [0u8; 12]; - let mut ct = sm4_encrypt_ccm(&key, &nonce, b"", b"secret data here", 16).unwrap(); - // 篡改 tag(最后 16 字节) - let last = ct.len() - 1; - ct[last] ^= 1; - assert!( - sm4_decrypt_ccm(&key, &nonce, b"", &ct, 16).is_err(), - "篡改 CCM tag 后应返回错误" - ); - } - - /// CCM AAD 超限应返回错误(而非静默跳过) - #[test] - fn test_ccm_aad_too_long() { - let key = [0u8; 16]; - let nonce = [0u8; 12]; - let big_aad = [0u8; 511]; // 超过 510 字节限制 - assert!( - sm4_encrypt_ccm(&key, &nonce, &big_aad, b"data", 16).is_err(), - "AAD 超过 510 字节时应返回 InvalidInputLength" - ); - } - - /// XTS 加解密往返测试 - #[test] - fn test_xts_roundtrip() { - let key1 = [0x11u8; 16]; - let key2 = [0x22u8; 16]; - let tweak = [0u8; 16]; - let plain = [0x42u8; 32]; // 2 个 16 字节块 - - let ct = sm4_encrypt_xts(&key1, &key2, &tweak, &plain).unwrap(); - let pt = sm4_decrypt_xts(&key1, &key2, &tweak, &ct).unwrap(); - assert_eq!(pt, plain, "XTS 往返解密失败"); - } - - /// XTS 非对齐数据应返回错误 - #[test] - fn test_xts_non_aligned_rejected() { - let key1 = [0u8; 16]; - let key2 = [0u8; 16]; - let tweak = [0u8; 16]; - - // 空输入 - assert!( - sm4_encrypt_xts(&key1, &key2, &tweak, b"").is_err(), - "空输入应返回 InvalidInputLength" - ); - // 非 16 倍数 - assert!( - sm4_encrypt_xts(&key1, &key2, &tweak, b"not-aligned-data").is_ok(), - "正好 16 字节不应返回错误" - ); - assert!( - sm4_encrypt_xts(&key1, &key2, &tweak, &[0u8; 17]).is_err(), - "17 字节应返回 InvalidInputLength" - ); - assert!( - sm4_decrypt_xts(&key1, &key2, &tweak, &[0u8; 15]).is_err(), - "15 字节应返回 InvalidInputLength" - ); - } - - /// OFB 自反性验证 - #[test] - fn test_ofb_self_inverse() { - let key = [0xABu8; 16]; - let iv = [0x12u8; 16]; - let plain = b"ofb test message"; - let ct = sm4_crypt_ofb(&key, &iv, plain); - let pt = sm4_crypt_ofb(&key, &iv, &ct); - assert_eq!(pt, plain, "OFB 应为自反模式"); - } -} diff --git a/tests/sm2_proptest.rs b/tests/sm2_proptest.rs new file mode 100644 index 0000000..f10a6d5 --- /dev/null +++ b/tests/sm2_proptest.rs @@ -0,0 +1,87 @@ +//! SM2 属性测试 / Property-based tests for SM2 +//! +//! 使用 proptest 验证:任意随机私钥生成的签名均可被对应公钥验证。 +//! Tests that for arbitrary random key bytes, sign-then-verify always succeeds. + +use libsmx::sm2::{get_e, get_z, sign, verify, PrivateKey, DEFAULT_ID}; +use proptest::prelude::*; + +proptest! { + /// 任意合法私钥:签名后验签必须通过 + /// + /// Reason: 使用原始字节数组作为策略输入(proptest 只需字节组具有 Debug), + /// 在测试体内调用 from_bytes 过滤非法值,合法时执行验证逻辑。 + #[test] + fn prop_sign_verify_roundtrip( + key_bytes in prop::array::uniform32(1u8..=0xFFu8), + msg in prop::collection::vec(any::(), 0..256), + ) { + let pri_key = match PrivateKey::from_bytes(&key_bytes) { + Ok(k) => k, + Err(_) => return Ok(()), // 非法私钥直接跳过 + }; + + let pub_key = pri_key.public_key(); + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, &msg); + + let mut rng = rand::thread_rng(); + let sig = sign(&e, &pri_key, &mut rng); + + prop_assert!(verify(&e, &pub_key, &sig).is_ok(), + "sign-then-verify failed for a valid key"); + } + + /// 不同消息的签名不能交叉验证 + #[test] + fn prop_different_msg_rejected( + key_bytes in prop::array::uniform32(1u8..=0xFFu8), + msg1 in prop::collection::vec(any::(), 1..64), + msg2 in prop::collection::vec(any::(), 1..64), + ) { + prop_assume!(msg1 != msg2); + + let pri_key = match PrivateKey::from_bytes(&key_bytes) { + Ok(k) => k, + Err(_) => return Ok(()), + }; + + let pub_key = pri_key.public_key(); + let z = get_z(DEFAULT_ID, &pub_key); + let e1 = get_e(&z, &msg1); + let e2 = get_e(&z, &msg2); + + let mut rng = rand::thread_rng(); + let sig1 = sign(&e1, &pri_key, &mut rng); + + // 用 msg1 的签名验证 msg2 应失败 + prop_assert!(verify(&e2, &pub_key, &sig1).is_err(), + "signature for msg1 must not verify msg2"); + } + + /// 篡改签名任意字节后验签应失败(或恰好产生另一合法签名,极罕见) + #[test] + fn prop_tampered_sig_no_panic( + key_bytes in prop::array::uniform32(1u8..=0xFFu8), + msg in prop::collection::vec(any::(), 1..128), + tamper_idx in 0usize..64, + tamper_xor in 1u8..=0xFFu8, + ) { + let pri_key = match PrivateKey::from_bytes(&key_bytes) { + Ok(k) => k, + Err(_) => return Ok(()), + }; + + let pub_key = pri_key.public_key(); + let z = get_z(DEFAULT_ID, &pub_key); + let e = get_e(&z, &msg); + + let mut rng = rand::thread_rng(); + let mut sig = sign(&e, &pri_key, &mut rng); + + sig[tamper_idx] ^= tamper_xor; + + // 仅断言不 panic,不断言一定失败(极罕见情况下可能仍然合法) + let _ = verify(&e, &pub_key, &sig); + } +} diff --git a/tests/sm4_vectors.rs b/tests/sm4_vectors.rs index 4bb3bb3..956590f 100644 --- a/tests/sm4_vectors.rs +++ b/tests/sm4_vectors.rs @@ -1,9 +1,12 @@ //! SM4 国标测试向量(GB/T 32907-2016 附录 A) //! -//! A.1 示例1:单次 ECB 加密 +//! A.1 示例1:单次 ECB 加密(单块) //! A.2 示例2:1,000,000 次迭代 ECB 加密(验证算法迭代正确性) +//! +//! 注:原来使用 `sm4_encrypt_ecb` 的向量测试已迁移为直接使用 `Sm4Key::encrypt_block`, +//! 与 RustCrypto 生态的 `sm4` 子 crate 行为一致。 -use libsmx::sm4::{sm4_decrypt_ecb, sm4_encrypt_ecb}; +use libsmx::sm4::Sm4Key; /// GB/T 32907-2016 附录 A.1 /// 密钥:0123456789abcdeffedcba9876543210 @@ -12,15 +15,20 @@ use libsmx::sm4::{sm4_decrypt_ecb, sm4_encrypt_ecb}; #[test] fn test_sm4_ecb_vector_a1_single() { let key = hex::decode("0123456789abcdeffedcba9876543210").unwrap(); - let plaintext = hex::decode("0123456789abcdeffedcba9876543210").unwrap(); - let expected_ct = hex::decode("681edf34d206965e86b3e94f536e4246").unwrap(); - let key_arr: [u8; 16] = key.try_into().unwrap(); - let ct = sm4_encrypt_ecb(&key_arr, &plaintext); - assert_eq!(ct, expected_ct, "GB/T 32907 附录 A.1 加密失败"); + let sm4 = Sm4Key::new(&key_arr); - let pt = sm4_decrypt_ecb(&key_arr, &ct); - assert_eq!(pt, plaintext, "GB/T 32907 附录 A.1 解密失败"); + let mut block = hex::decode("0123456789abcdeffedcba9876543210").unwrap(); + let block_arr: &mut [u8; 16] = block.as_mut_slice().try_into().unwrap(); + + sm4.encrypt_block(block_arr); + + let expected = hex::decode("681edf34d206965e86b3e94f536e4246").unwrap(); + assert_eq!(block_arr, expected.as_slice(), "GB/T 32907 附录 A.1 加密失败"); + + sm4.decrypt_block(block_arr); + let plaintext = hex::decode("0123456789abcdeffedcba9876543210").unwrap(); + assert_eq!(block_arr, plaintext.as_slice(), "GB/T 32907 附录 A.1 解密失败"); } /// GB/T 32907-2016 附录 A.2 @@ -28,28 +36,36 @@ fn test_sm4_ecb_vector_a1_single() { /// 明文:0123456789abcdeffedcba9876543210(反复迭代 1,000,000 次) /// 密文:595298c7c6fd271f0402f804c33d3f66 #[test] +#[ignore = "slow (1M iterations)"] fn test_sm4_ecb_vector_a2_million_iterations() { let key: [u8; 16] = hex::decode("0123456789abcdeffedcba9876543210") .unwrap() .try_into() .unwrap(); + let sm4 = Sm4Key::new(&key); - let mut data: Vec = hex::decode("0123456789abcdeffedcba9876543210").unwrap(); + let mut block: [u8; 16] = hex::decode("0123456789abcdeffedcba9876543210") + .unwrap() + .try_into() + .unwrap(); for _ in 0..1_000_000 { - data = sm4_encrypt_ecb(&key, &data); + sm4.encrypt_block(&mut block); } let expected = hex::decode("595298c7c6fd271f0402f804c33d3f66").unwrap(); - assert_eq!(data, expected, "GB/T 32907 附录 A.2 百万次迭代失败"); + assert_eq!(&block, expected.as_slice(), "GB/T 32907 附录 A.2 百万次迭代失败"); } -/// ECB 解密是加密的逆操作(往返测试) +/// 加解密往返测试 #[test] -fn test_sm4_ecb_roundtrip() { +fn test_sm4_block_roundtrip() { let key = [0x01u8; 16]; - let plaintext = b"SM4 ECB test!!!\x00"; - let ct = sm4_encrypt_ecb(&key, plaintext); - let pt = sm4_decrypt_ecb(&key, &ct); - assert_eq!(pt.as_slice(), plaintext.as_slice()); + let sm4 = Sm4Key::new(&key); + let plaintext = *b"SM4 ECB test!!!!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + let mut block: [u8; 16] = plaintext[..16].try_into().unwrap(); + sm4.encrypt_block(&mut block); + assert_ne!(block, plaintext[..16], "密文应与明文不同"); + sm4.decrypt_block(&mut block); + assert_eq!(block, plaintext[..16], "解密后应恢复原文"); }