diff --git a/CHANGELOG.md b/CHANGELOG.md index f28ca13..61ef407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog: @go/crypto +## v1.5.5 (2026-07-18) +- **确定性密钥修复**: HKDF 首轮耗尽后使用带轮次域分离的新一轮字节流,避免 RSA 密钥生成触发 entropy limit,同时保留原有首轮输出。 +- **依赖校验修复**: 重新整理模块图并补齐 checksum。 + ## v1.5.4 (2026-06-21) - **命名规范**: SHA 缩写全大写,`Sha1`/`Sha256`/`Sha512` → `SHA1`/`SHA256`/`SHA512`,HMAC 系列同步修正为 `HMACSHA1`/`HMACSHA256`/`HMACSHA512`。 diff --git a/TEST.md b/TEST.md index bfc583b..6b6fb2a 100644 --- a/TEST.md +++ b/TEST.md @@ -1,5 +1,8 @@ # Test Report: @go/crypto +## v1.5.5 验证 +- 全量测试与基准测试通过;密码派生非对称密钥回归连续运行 10 次通过。 + ## 📋 测试概览 - **测试时间**: 2026-06-21 - **测试环境**: darwin/amd64 diff --git a/go.mod b/go.mod index 4324790..3457dbd 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,6 @@ require ( ) require ( - apigo.cc/go/rand v1.5.3 + apigo.cc/go/rand v1.5.3 // indirect golang.org/x/sys v0.45.0 // indirect ) diff --git a/go.sum b/go.sum index de7e21c..d0f1cb5 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,14 @@ -apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE= -apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8= -apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I= -apigo.cc/go/encoding v1.5.0/go.mod h1:8++NfZj3hWig0qh2g7GQRw/4LpSvCYMWUZ+8J+x58cA= -apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec= -apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw= -apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E= -apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk= -apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk= -apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM= +apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw= +apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4= +apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w= +apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM= +apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU= +apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw= +apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius= +apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4= +apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg= +apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/password.go b/password.go index e1cb27b..1d074de 100644 --- a/password.go +++ b/password.go @@ -2,6 +2,7 @@ package crypto import ( "crypto/sha256" + "encoding/binary" "io" "apigo.cc/go/safe" @@ -26,5 +27,45 @@ func DeriveKey(password, salt []byte, keyLen uint32) []byte { // NewDeterministicReader creates an io.Reader that produces a deterministic stream of bytes from a seed. // This is used to make RSA/ECDSA key generation deterministic based on a password. func NewDeterministicReader(seed []byte, info []byte) io.Reader { - return hkdf.New(sha256.New, seed, nil, info) + return newExpandingHKDFReader(seed, info) +} + +type expandingHKDFReader struct { + seed []byte + info []byte + round uint64 + current io.Reader +} + +func newExpandingHKDFReader(seed, info []byte) io.Reader { + r := &expandingHKDFReader{ + seed: append([]byte(nil), seed...), + info: append([]byte(nil), info...), + } + r.reset() + return r +} + +func (r *expandingHKDFReader) reset() { + info := r.info + if r.round > 0 { + info = make([]byte, len(r.info)+8) + copy(info, r.info) + binary.BigEndian.PutUint64(info[len(r.info):], r.round) + } + r.current = hkdf.New(sha256.New, r.seed, nil, info) + r.round++ +} + +func (r *expandingHKDFReader) Read(p []byte) (int, error) { + total := 0 + for total < len(p) { + n, err := r.current.Read(p[total:]) + total += n + if err == nil { + continue + } + r.reset() + } + return total, nil }