重构:RustCrypto 兼容接口
主要变更: - 新增 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/
This commit is contained in:
@@ -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"]
|
||||
@@ -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<Self>]) {
|
||||
// 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<u8, U64> implements Deref<Target=[u8]>,
|
||||
// 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<Self>, out: &mut digest::Output<Self>) {
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+124
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user