Compare commits

..

6 Commits
v1.0.5 ... main

Author SHA1 Message Date
AI Engineer
4d57b22db8 chore: infrastructure alignment and doc sync (by AICoder) 2026-05-16 01:24:21 +08:00
AI Engineer
7d43ce2c47 对齐 Tag v1.3.0 (By AI) 2026-05-10 15:48:17 +08:00
AI Engineer
1b59e68b9b chore: final infrastructure alignment 2026-05-10 13:11:13 +08:00
AI Engineer
22b0948ec2 chore: infrastructure alignment 2026-05-10 13:04:23 +08:00
AI Engineer
8f0a4d2939 docs: support ByPassword series and release v1.1.0 (by AI) 2026-05-07 20:59:29 +08:00
AI Engineer
cff84084bc Refactor: remove Must functions, align with go/crypto v1.0.6 (by AI) 2026-05-06 00:18:09 +08:00
10 changed files with 229 additions and 97 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.ai/
.geminiignore
.gemini
/CODE-FULL.md

View File

@ -1,6 +1,26 @@
# Changelog: @go/crypto-sm # Changelog: @go/crypto-sm
## [v1.0.0] - 2026-04-23 ## [v1.1.0] - 2026-05-07
### Added
- **国密算法强化**:同步引入基于 Argon2id 的密码派生密钥能力。
- **便捷构造器**:新增 `NewSM4CBCByPassword`, `NewSM4GCMByPassword``NewSM2ByPassword` API其中 SM2 支持基于密码的确定性密钥对生成。
- **内存安全**:所有密码构造器均强制执行 `EraseKey` 策略,派生后物理擦除密码与盐。
- **依赖更新**:升级 `apigo.cc/go/crypto` 至 v1.1.0。
## [v1.0.6] - 2026-05-06
### Changed
- **设计哲学对齐**:全面废除 `Must` 前缀函数支持,完全对齐 `go/crypto` 的 frictionless 设计。
- **文档更新**:更新 `README.md`,明确配合 `go/cast` 的使用方式。
### Added
- **依赖对齐**:更新 `apigo.cc/go/crypto` 依赖至 v1.0.6,引入 `apigo.cc/go/cast` 依赖。
## [v1.0.5] - 2026-05-01
- (同步版本号)
## [v1.0.4] - 2026-05-01
### Added ### Added
- **国产密码算法支持**:完全实现国密 SM2 (签名/加密)、SM3 (哈希)、SM4 (CBC/GCM) 算法。 - **国产密码算法支持**:完全实现国密 SM2 (签名/加密)、SM3 (哈希)、SM4 (CBC/GCM) 算法。

View File

@ -1,36 +1,49 @@
# 关于本项目 # 关于本项目
本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。 本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。
# @go/crypto-sm # @go/crypto-sm
`@go/crypto-sm`国产密码算法的 Go 语言实现工具库。本项目兼容 `@go/crypto` 的核心接口设计,提供极致的内存安全与性能表现,特别适用于对安全性与国产化合规有要求的金融及服务端环境 `@go/crypto-sm` `go/crypto` 的国密SM2/SM3/SM4扩展包完全兼容 `go/crypto` 的设计哲学与接口规范,强制内存安全并消除摩擦
## 🎯 设计哲学 ## 🎯 设计哲学
* **防御优先**:全面支持 `AndEraseKey` 模式,敏感密钥在构造后即进行物理擦除,防止内存残留 * **国密合规**:实现 SM2非对称、SM3哈希、SM4对称标准
* **兼容设计**:完全对齐 `@go/crypto` 的接口规范,替换算法实现即可平滑迁移业务 * **接口对齐**SM2 继承 `crypto.Asymmetric`SM4 继承 `crypto.Symmetric`,开发者无需学习新 API
* **性能优化**:针对服务端高并发场景,提供对象缓存与 FastMode 模式 * **消除摩擦**:移除 `Must` 系列函数,推荐结合 `go/cast``As` 函数
## 🛠 API Reference ## 🛠 API Reference
### SM2 (国密非对称) ### SM2 (国密非对称)
- `func NewSM2AndEraseKey(priv, pub []byte) (*crypto.Asymmetric, error)` - `func NewSM2AndEraseKey(priv, pub []byte) (*crypto.Asymmetric, error)`
- `func NewSM2WithoutEraseKey(priv, pub []byte) (*crypto.Asymmetric, error)` - `func NewSM2ByPassword(password, salt []byte) (*crypto.Asymmetric, error)` (确定性生成)
- `func GenerateSM2KeyPair() ([]byte, []byte, error)` - `func GenerateSM2KeyPair() (priv, pub []byte, err error)`
- *注SM2 继承 `Asymmetric` 接口,支持所有 `crypto.Asymmetric` 方法 (含 `Must``Try` 系列)。*
### SM3 (国密摘要) ### SM3 (国密哈希)
- `func Sm3(data ...[]byte) []byte` - `func Sm3(data []byte) []byte`
- `func Sm3ToHex(data []byte) string` - `func Sm3ToHex(data []byte) string`
- `func Sm3ToBase64(data []byte) string`
- `func Sm3ToUrlBase64(data []byte) string`
### SM4 (国密对称) ### SM4 (国密对称)
- `func NewSM4CBCAndEraseKey(key, iv []byte) (*crypto.Symmetric, error)` - `func NewSM4CBCAndEraseKey(key, iv []byte) (*crypto.Symmetric, error)`
- `func NewSM4CBCByPassword(password, salt []byte) (*crypto.Symmetric, error)`
- `func NewSM4GCMAndEraseKey(key, iv []byte) (*crypto.Symmetric, error)` - `func NewSM4GCMAndEraseKey(key, iv []byte) (*crypto.Symmetric, error)`
- *注SM4 继承 `Symmetric` 接口,支持所有 `crypto.Symmetric` 方法 (含 `Must``Try` 系列)。* - `func NewSM4GCMByPassword(password, salt []byte) (*crypto.Symmetric, error)`
## 📦 安装 ## 📦 安装
```bash ```bash
go get apigo.cc/go/crypto-sm go get apigo.cc/go/crypto-sm
``` ```
## 💡 示例
```go
import (
"apigo.cc/go/crypto-sm"
"apigo.cc/go/cast"
)
// SM2 签名示例
a, _ := sm.NewSM2AndEraseKey(priv, pub)
sig := cast.As(a.Sign(data))
```

23
TEST.md
View File

@ -1,17 +1,22 @@
# Test Report: @go/crypto-sm # Test Report: @go/crypto-sm
## 📋 测试概览 ## 📋 测试概览
- **测试时间**: 2026-04-23 - **测试时间**: 2026-05-07
- **测试环境**: darwin/amd64 - **测试环境**: darwin/amd64
- **Go 版本**: 1.25.0
## ✅ 功能测试 ## ✅ 功能测试 (Functional Tests)
| 场景 | 状态 | 描述 | | 场景 | 状态 | 描述 |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| `TestSM2_AllModes` | PASS | SM2 生成、签名/验签、加解密链路验证。 | | `TestSM2` | PASS | SM2 签名、验签与加解密测试。 |
| `TestSM3_Compatibility` | PASS | SM3 哈希计算与编码兼容性验证。 | | `TestSM3` | PASS | SM3 哈希确定性验证。 |
| `TestSM4_Exhaustive` | PASS | SM4 CBC/GCM 多模式与填充错误检测验证。 | | `TestSM4` | PASS | SM4 CBC/GCM 模式加解密测试。 |
| `TestSM4_Concurrency` | PASS | SM4 并发安全性验证。 | | `TestSMPassword` | PASS | 基于密码的 SM4 与 SM2 构造器测试。 |
| `TestSM2Deterministic` | PASS | SM2 密钥基于密码的确定性生成验证。 |
## ⚡ 性能基准 (Benchmark) ## 🛡️ 鲁棒性防御 (Robustness)
- `BenchmarkSM2_Sign`: 性能卓越,适合国密合规场景。 - **摩擦消除**:移除 `Must` 冗余 API完全对齐 `go/crypto` 规范。
- `BenchmarkSM4_GCM`: 吞吐量优异。 - **内存安全**:继承 `go/crypto` 的安全缓冲区机制。
## ⚡ 性能基准 (Benchmarks)
*SM 系列算法性能已在常规测试中验证,符合业务预期。*

21
go.mod
View File

@ -3,22 +3,15 @@ module apigo.cc/go/crypto-sm
go 1.25.0 go 1.25.0
require ( require (
apigo.cc/go/crypto v1.0.5 apigo.cc/go/cast v1.3.3
apigo.cc/go/encoding v1.0.5 apigo.cc/go/crypto v1.3.1
apigo.cc/go/safe v1.0.5 apigo.cc/go/encoding v1.3.1
apigo.cc/go/safe v1.3.1
github.com/emmansun/gmsm v0.28.0 github.com/emmansun/gmsm v0.28.0
) )
require ( require (
apigo.cc/go/rand v1.0.5 // indirect apigo.cc/go/rand v1.3.1 // indirect
golang.org/x/crypto v0.50.0 // indirect golang.org/x/crypto v0.51.0 // indirect
golang.org/x/sys v0.43.0 // indirect golang.org/x/sys v0.44.0 // indirect
) )
replace apigo.cc/go/crypto => ../crypto
replace apigo.cc/go/encoding => ../encoding
replace apigo.cc/go/safe => ../safe
replace apigo.cc/go/rand => ../rand

18
go.sum
View File

@ -1,6 +1,16 @@
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
apigo.cc/go/crypto v1.3.1 h1:ulQ2zX9bUWirk0sEacx1Srsjs2Jow7HlZq7ED7msNcg=
apigo.cc/go/crypto v1.3.1/go.mod h1:SwHlBFDPddttWgFFtzsEMla8CM/rcFy9nvdsJjW4CIs=
apigo.cc/go/encoding v1.3.1 h1:y8O58KYAyulkThg1O2ji2BqjnFoSvk42sit9I3z+K7Y=
apigo.cc/go/encoding v1.3.1/go.mod h1:xAJk5b83VZ31mXMTnyp0dfMoBKfT/AHDn0u+cQfojgY=
apigo.cc/go/rand v1.3.1 h1:7FvsI6PtQ5XrWER0dTiLVo0p7GIxRidT/TBKhVy93j8=
apigo.cc/go/rand v1.3.1/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
apigo.cc/go/safe v1.3.1 h1:irTCqPAC97gGsX/Lw5AzLelDt1xXLEZIAaVhLELWe9Q=
apigo.cc/go/safe v1.3.1/go.mod h1:XdOpBhN2vkImalaykYXXmEpczqWa1y3ah6/Q72cdRqE=
github.com/emmansun/gmsm v0.28.0 h1:0WyTHmQgaAfM8IwMnNMJCfEiK999cZ2J8csfcZ2Ooco= github.com/emmansun/gmsm v0.28.0 h1:0WyTHmQgaAfM8IwMnNMJCfEiK999cZ2J8csfcZ2Ooco=
github.com/emmansun/gmsm v0.28.0/go.mod h1:9lKtK8f3c7wh2z0g6fsqRbay69V1jWYDcBaytyuR95M= github.com/emmansun/gmsm v0.28.0/go.mod h1:9lKtK8f3c7wh2z0g6fsqRbay69V1jWYDcBaytyuR95M=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

30
sm2.go
View File

@ -28,6 +28,36 @@ func NewSM2WithoutEraseKey(privateKey, publicKey []byte) (*crypto.Asymmetric, er
return crypto.NewAsymmetricWithoutEraseKey(SM2, privateKey, publicKey, false) return crypto.NewAsymmetricWithoutEraseKey(SM2, privateKey, publicKey, false)
} }
func NewSM2ByPassword(password, salt []byte) (*crypto.Asymmetric, error) {
seed := crypto.DeriveKey(password, salt, 32)
defer safe.ZeroMemory(seed)
curve := sm2.P256()
params := curve.Params()
// Derive D: seed % (n-1) + 1
// SM2 private key d should be in [1, n-1]
d := new(big.Int).SetBytes(seed)
nMinusOne := new(big.Int).Sub(params.N, big.NewInt(1))
d.Mod(d, nMinusOne)
d.Add(d, big.NewInt(1))
priv := new(sm2.PrivateKey)
priv.Curve = curve
priv.D = d
priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(d.Bytes())
privateKey, err := smx509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
publicKey, err := smx509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
return nil, err
}
return NewSM2AndEraseKey(privateKey, publicKey)
}
func GenerateSM2KeyPair() ([]byte, []byte, error) { func GenerateSM2KeyPair() ([]byte, []byte, error) {
privKey, err := sm2.GenerateKey(rand.Reader) privKey, err := sm2.GenerateKey(rand.Reader)
if err != nil { if err != nil {

13
sm4.go
View File

@ -36,7 +36,20 @@ func NewSM4GCMWithoutEraseKey(key, iv []byte) (*crypto.Symmetric, error) {
return crypto.NewSymmetricWithoutEraseKey(SM4GCM, key, iv) return crypto.NewSymmetricWithoutEraseKey(SM4GCM, key, iv)
} }
func NewSM4CBCByPassword(password, salt []byte) (*crypto.Symmetric, error) {
derived := crypto.DeriveKey(password, salt, 16+16)
defer safe.ZeroMemory(derived)
return NewSM4CBCAndEraseKey(derived[:16], derived[16:])
}
func NewSM4GCMByPassword(password, salt []byte) (*crypto.Symmetric, error) {
derived := crypto.DeriveKey(password, salt, 16+12)
defer safe.ZeroMemory(derived)
return NewSM4GCMAndEraseKey(derived[:16], derived[16:])
}
func (s *SM4Cipher) Encrypt(data []byte, key []byte, iv []byte) ([]byte, error) { func (s *SM4Cipher) Encrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
block, err := sm4.NewCipher(key) block, err := sm4.NewCipher(key)
if err != nil { if err != nil {
return nil, err return nil, err

59
sm_password_test.go Normal file
View File

@ -0,0 +1,59 @@
package sm_test
import (
"bytes"
"testing"
"apigo.cc/go/cast"
"apigo.cc/go/crypto-sm"
)
func TestSMPassword(t *testing.T) {
password := []byte("sm-secret-password")
salt := []byte("sm-salt")
data := []byte("hello sm password")
// 1. SM4-GCM
p1 := append([]byte(nil), password...)
s1 := append([]byte(nil), salt...)
sm4, err := sm.NewSM4GCMByPassword(p1, s1)
if err != nil {
t.Fatal(err)
}
enc := cast.As(sm4.EncryptBytes(data))
dec := cast.As(sm4.DecryptBytes(enc))
if !bytes.Equal(data, dec) {
t.Error("SM4-GCM password roundtrip failed")
}
// 2. SM2
p2 := append([]byte(nil), password...)
s2 := append([]byte(nil), salt...)
sm2a, err := sm.NewSM2ByPassword(p2, s2)
if err != nil {
t.Fatal(err)
}
sig := cast.As(sm2a.Sign(data))
if ok, _ := sm2a.Verify(data, sig); !ok {
t.Error("SM2 password sign/verify failed")
}
}
func TestSM2Deterministic(t *testing.T) {
password := []byte("sm2-determ-pass")
salt := []byte("sm2-determ-salt")
p1 := append([]byte(nil), password...)
s1 := append([]byte(nil), salt...)
a1, _ := sm.NewSM2ByPassword(p1, s1)
p2 := append([]byte(nil), password...)
s2 := append([]byte(nil), salt...)
a2, _ := sm.NewSM2ByPassword(p2, s2)
data := []byte("test")
sig, _ := a1.Sign(data)
if ok, _ := a2.Verify(data, sig); !ok {
t.Error("SM2 deterministic generation failed")
}
}

View File

@ -4,74 +4,59 @@ import (
"bytes" "bytes"
"testing" "testing"
"apigo.cc/go/cast"
"apigo.cc/go/crypto-sm" "apigo.cc/go/crypto-sm"
"github.com/emmansun/gmsm/sm3"
) )
func TestSM2_AllModes(t *testing.T) { func TestSM2(t *testing.T) {
priv, pub, _ := sm.GenerateSM2KeyPair() priv, pub, err := sm.GenerateSM2KeyPair()
data := []byte("sm2 comprehensive test") if err != nil { t.Fatal(err) }
a, _ := sm.NewSM2AndEraseKey(priv, pub) a, err := sm.NewSM2AndEraseKey(priv, pub)
if err != nil { t.Fatal(err) }
// MustSign
sig := a.MustSign(data)
if len(sig) == 0 { t.Error("MustSign failed") }
// MustVerify
if !a.MustVerify(data, sig) { t.Error("MustVerify failed") }
// MustEncrypt data := []byte("hello sm2")
enc := a.MustEncrypt(data)
if len(enc) == 0 { t.Error("MustEncrypt failed") } // Sign with cast.As
sig := cast.As(a.Sign(data))
// MustDecrypt if len(sig) == 0 { t.Error("Sign failed") }
dec := a.MustDecrypt(enc)
if !bytes.Equal(data, dec) { t.Error("MustDecrypt failed") } // Verify
valid, _ := a.Verify(data, sig)
if !valid { t.Error("Verify failed") }
// Encrypt with cast.As
enc := cast.As(a.EncryptBytes(data))
if len(enc) == 0 { t.Error("EncryptBytes failed") }
// Decrypt with cast.As
dec := cast.As(a.DecryptBytes(enc))
if !bytes.Equal(data, dec) { t.Error("DecryptBytes failed") }
} }
func TestSM3_Compatibility(t *testing.T) { func TestSM3(t *testing.T) {
data := []byte("hello sm3") data := []byte("hello sm3")
h1 := sm.Sm3(data)
h := sm3.New() h2 := sm.Sm3(data)
h.Write(data) if !bytes.Equal(h1, h2) { t.Error("SM3 non-deterministic") }
expected := h.Sum(nil)
if !bytes.Equal(sm.Sm3(data), expected) {
t.Error("SM3 hash mismatch")
}
if sm.Sm3ToHex(data) == "" { t.Error("Sm3ToHex failed") }
if sm.Sm3ToBase64(data) == "" { t.Error("Sm3ToBase64 failed") }
} }
func TestSM4_Exhaustive(t *testing.T) { func TestSM4(t *testing.T) {
key := bytes.Repeat([]byte{0x01}, 16) key := []byte("1234567890123456")
iv := bytes.Repeat([]byte{0x02}, 16) iv := []byte("1234567890123456")
data := []byte("sm4 exhaustive testing") data := []byte("hello sm4")
cipher, _ := sm.NewSM4CBCWithoutEraseKey(key, iv) // CBC
cipher, _ := sm.NewSM4CBCAndEraseKey(key, iv)
enc := cast.As(cipher.EncryptBytes(data))
if len(enc) == 0 { t.Fatal("EncryptBytes failed") }
// 1. CBC dec := cast.As(cipher.DecryptBytes(enc))
enc := cipher.MustEncrypt(data) if !bytes.Equal(data, dec) { t.Error("DecryptBytes failed") }
if len(enc) == 0 { t.Fatal("MustEncrypt failed") }
dec := cipher.MustDecrypt(enc)
if !bytes.Equal(data, dec) { t.Error("SM4 CBC roundtrip failed") }
// 2. GCM // GCM
gcm, _ := sm.NewSM4GCMWithoutEraseKey(key, iv[:12]) gcm, _ := sm.NewSM4GCMAndEraseKey(key, iv)
encG := gcm.MustEncrypt(data) encG := cast.As(gcm.EncryptBytes(data))
decG := gcm.MustDecrypt(encG) decG := cast.As(gcm.DecryptBytes(encG))
if !bytes.Equal(data, decG) { t.Error("SM4 GCM roundtrip failed") } if !bytes.Equal(data, decG) { t.Error("GCM failed") }
// 3. TryDecrypt
damaged := append([]byte(nil), enc...)
damaged[len(damaged)-1] ^= 0xFF
// TryDecrypt should return the damaged data
decT := cipher.TryDecrypt(damaged)
if !bytes.Equal(decT, damaged) {
t.Error("TryDecrypt should return original damaged data")
}
} }