diff --git a/.gitignore b/.gitignore index ef67e74..edb9f6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .ai/ - .geminiignore +.gemini +/CODE-FULL.md diff --git a/CHANGELOG-LATEST.md b/CHANGELOG-LATEST.md new file mode 100644 index 0000000..4e11ae2 --- /dev/null +++ b/CHANGELOG-LATEST.md @@ -0,0 +1,5 @@ +## [v1.1.2] - 2026-05-12 + +### Added +- **统一配置解密接口 (加固版)**:新增 `OnSetDefaultAES` 与 `SetDefaultAES` 注入接口,支持自动擦除密钥与自动锁定通道。 +- **基础设施对齐**:同步更新 `redis`, `api`, `db`, `mail` 等项目接入 `crypto` 安全解密体系。 diff --git a/CHANGELOG.md b/CHANGELOG.md index be73379..94c9aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog: @go/crypto -## [v1.1.0] - 2026-05-07 +## [v1.1.2] - 2026-05-12 + +### Added +- **统一配置解密接口 (加固版)**:新增 `OnSetDefaultAES` 回调与 `SetDefaultAES` 注入接口。 +- **极致内存安全**:`SetDefaultAES` 注入生产密钥后将**自动擦除** (ZeroMemory) 传入的密钥源字节,并**自动锁定**注册通道。 +- **基础设施对齐**:同步更新 `redis`, `api`, `db`, `mail` 等项目,移除本地 `confAes`,全面接入安全加固后的 `crypto.confAES` 体系。 + +## [v1.1.1] - 2026-05-12 ### Added - **密码学强化**:新增基于 Argon2id 的密码派生密钥 (KDF) 支持。 diff --git a/README.md b/README.md index 7237f70..df3f7b1 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ - `func GenerateEd25519KeyPair() (priv, pub []byte, err error)` - `func GenerateX25519KeyPair() (priv, pub []byte, err error)` +### 全局配置解密 (Default AES) +为实现全项目配置解密的统一管理与生产环境密钥注入,提供以下全局接口: +- `var DefaultAES *Symmetric`:全局默认 AES 实例(初始使用内置硬编码密钥)。 +- `func OnSetDefaultAES(h func(*Symmetric))`:注册 `DefaultAES` 变更回调。 +- `func SetDefaultAES(key, iv []byte)`:注入生产环境密钥并触发所有回调更新。 + ### 哈希算法 (Hash) - `func MD5(data []byte) []byte` - `func Sha256(data []byte) []byte` diff --git a/default.go b/default.go new file mode 100644 index 0000000..01295d0 --- /dev/null +++ b/default.go @@ -0,0 +1,69 @@ +package crypto + +import ( + "sync" +) + +var ( + // defaultAES 用于配置解密的默认 AES 实例 (私有) + defaultAES *Symmetric + + defaultAESHandlers []func(*Symmetric) + defaultAESLock sync.RWMutex + defaultAESOnce sync.Once + isDefaultAESSet bool + isLocked bool +) + +func init() { + // 默认硬编码密钥,仅用于基础防护。 + defaultAES, _ = NewAESGCMWithoutEraseKey( + []byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), + []byte("VFs7@sK61cj^f?HZ"), + ) +} + +// OnSetDefaultAES 注册配置解密 AES 实例变更的回调。 +// 即使在 SetDefaultAES 调用之后,此方法仍然有效,直到调用 LockDefaultAES。 +// 注册时会立即以当前实例(硬编码或已注入的生产密钥)调用一次回调。 +func OnSetDefaultAES(h func(*Symmetric)) { + defaultAESLock.Lock() + defer defaultAESLock.Unlock() + + if isLocked { + // 如果已经锁定,不再允许注册新的回调 + return + } + + defaultAESHandlers = append(defaultAESHandlers, h) + if defaultAES != nil { + h(defaultAES) + } +} + +// SetDefaultAES 设置全局默认 AES 密钥与 IV。 +// 仅允许调用一次。调用后会立即锁定注册通道,后续 OnSetDefaultAES 将不再起作用。 +// 注意:此方法会自动擦除 (ZeroMemory) 传入的 key 和 iv 字节切片。 +func SetDefaultAES(key, iv []byte) { + defaultAESOnce.Do(func() { + // 创建新实例并擦除原始密钥 + newAES, err := NewAESGCMAndEraseKey(key, iv) + if err != nil { + return + } + + defaultAESLock.Lock() + defer defaultAESLock.Unlock() + + defaultAES = newAES + isLocked = true + + // 通知所有在初始化阶段注册的观察者 + for _, h := range defaultAESHandlers { + h(defaultAES) + } + + // 彻底清理并释放引用 + defaultAESHandlers = nil + }) +} diff --git a/default_test.go b/default_test.go new file mode 100644 index 0000000..29f6d35 --- /dev/null +++ b/default_test.go @@ -0,0 +1,54 @@ +package crypto + +import ( + "bytes" + "testing" +) + +func TestDefaultAES(t *testing.T) { + // 1. 测试初始默认值 + var confAES *Symmetric + OnSetDefaultAES(func(aes *Symmetric) { + confAES = aes + }) + + if confAES == nil { + t.Fatal("confAES should be initialized by OnSetDefaultAES") + } + + // 2. 测试 SetDefaultAES 触发更新与锁定 + rawKey := []byte("12345678901234567890123456789012") + newKey := bytes.Clone(rawKey) + newIv := []byte("123456789012") + SetDefaultAES(newKey, newIv) + + // 验证密钥已被擦除 (ZeroMemory 会用随机 junk 覆盖,所以检查是否不再等于原始值) + if bytes.Equal(newKey, rawKey) { + t.Error("newKey should be overwritten after SetDefaultAES") + } + + // 此时 confAES 应该已经被回调更新了 + data := []byte("hello world") + encrypted, err := confAES.EncryptAndErase(bytes.Clone(data)) + if err != nil { + t.Fatalf("Encrypt failed: %v", err) + } + + // 3. 测试安全性:SetDefaultAES 之后不再允许 OnSetDefaultAES + var blockedAES *Symmetric + OnSetDefaultAES(func(aes *Symmetric) { + blockedAES = aes + }) + if blockedAES != nil { + t.Error("OnSetDefaultAES should be blocked after SetDefaultAES (auto-lock)") + } + + // 4. 测试 SetDefaultAES 仅允许一次 + anotherKey := []byte("another key 32 bytes long.......") + SetDefaultAES(anotherKey, newIv) + // 验证密钥没有改变(通过解密验证) + _, err = confAES.DecryptBytes(encrypted) + if err != nil { + t.Errorf("Decryption should still work with the first injected key: %v", err) + } +}