docs: support ByPassword series and release v1.1.0 (by AI)

This commit is contained in:
AI Engineer 2026-05-07 20:59:29 +08:00
parent cff84084bc
commit 8f0a4d2939
7 changed files with 117 additions and 2 deletions

View File

@ -1,5 +1,13 @@
# Changelog: @go/crypto-sm # Changelog: @go/crypto-sm
## [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 ## [v1.0.6] - 2026-05-06
### Changed ### Changed

View File

@ -16,6 +16,7 @@
### SM2 (国密非对称) ### SM2 (国密非对称)
- `func NewSM2AndEraseKey(priv, pub []byte) (*crypto.Asymmetric, error)` - `func NewSM2AndEraseKey(priv, pub []byte) (*crypto.Asymmetric, error)`
- `func NewSM2ByPassword(password, salt []byte) (*crypto.Asymmetric, error)` (确定性生成)
- `func GenerateSM2KeyPair() (priv, pub []byte, err error)` - `func GenerateSM2KeyPair() (priv, pub []byte, err error)`
### SM3 (国密哈希) ### SM3 (国密哈希)
@ -24,7 +25,9 @@
### 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)`
- `func NewSM4GCMByPassword(password, salt []byte) (*crypto.Symmetric, error)`
## 📦 安装 ## 📦 安装

View File

@ -1,7 +1,7 @@
# Test Report: @go/crypto-sm # Test Report: @go/crypto-sm
## 📋 测试概览 ## 📋 测试概览
- **测试时间**: 2026-05-06 - **测试时间**: 2026-05-07
- **测试环境**: darwin/amd64 - **测试环境**: darwin/amd64
- **Go 版本**: 1.25.0 - **Go 版本**: 1.25.0
@ -11,6 +11,8 @@
| `TestSM2` | PASS | SM2 签名、验签与加解密测试。 | | `TestSM2` | PASS | SM2 签名、验签与加解密测试。 |
| `TestSM3` | PASS | SM3 哈希确定性验证。 | | `TestSM3` | PASS | SM3 哈希确定性验证。 |
| `TestSM4` | PASS | SM4 CBC/GCM 模式加解密测试。 | | `TestSM4` | PASS | SM4 CBC/GCM 模式加解密测试。 |
| `TestSMPassword` | PASS | 基于密码的 SM4 与 SM2 构造器测试。 |
| `TestSM2Deterministic` | PASS | SM2 密钥基于密码的确定性生成验证。 |
## 🛡️ 鲁棒性防御 (Robustness) ## 🛡️ 鲁棒性防御 (Robustness)
- **摩擦消除**:移除 `Must` 冗余 API完全对齐 `go/crypto` 规范。 - **摩擦消除**:移除 `Must` 冗余 API完全对齐 `go/crypto` 规范。

2
go.mod
View File

@ -3,7 +3,7 @@ module apigo.cc/go/crypto-sm
go 1.25.0 go 1.25.0
require ( require (
apigo.cc/go/crypto v1.0.6 apigo.cc/go/crypto v1.1.0
apigo.cc/go/encoding v1.0.5 apigo.cc/go/encoding v1.0.5
apigo.cc/go/safe v1.0.5 apigo.cc/go/safe v1.0.5
github.com/emmansun/gmsm v0.28.0 github.com/emmansun/gmsm v0.28.0

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")
}
}