Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d17076c8a | ||
|
|
d977f8a727 | ||
|
|
eb90ce303c | ||
|
|
13f975e041 | ||
|
|
5cf6db17e2 | ||
|
|
7c8f3464c0 | ||
|
|
5f4e5591cf | ||
|
|
ee51467cfa | ||
|
|
a8af2ef1df | ||
|
|
fe8989ce7c |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,4 @@
|
|||||||
go.sum
|
.ai/
|
||||||
|
.geminiignore
|
||||||
|
.gemini
|
||||||
|
/CODE-FULL.md
|
||||||
|
|||||||
89
AI.md
89
AI.md
@ -1,89 +0,0 @@
|
|||||||
# AI Coding Context: @go/crypto
|
|
||||||
|
|
||||||
本索引供 AI 模型理解 `@go/crypto` 的逻辑,以生成符合本项目“安全闭环、性能分级、语义一致”哲学的代码。
|
|
||||||
|
|
||||||
## 🤖 AI 行为准则
|
|
||||||
1. **内存安全优先**:首选 `SafeBuf` 接口处理敏感数据(密钥、明文)。
|
|
||||||
2. **场景化构造选择**:C端推荐 `AndEraseKey` 系列;S端高频场景使用 `New...WithoutEraseKey(..., true)` 进入 **FastMode**。
|
|
||||||
3. **闭环义务**:任何构造的 `Asymmetric` 或 `Symmetric` 对象,生成的代码必须包含 `defer obj.Close()`。
|
|
||||||
4. **接口一致性**:所有非对称算法必须实现 `ParsePrivateKey` 与 `ParsePublicKey`。
|
|
||||||
|
|
||||||
## 🛠 API Reference
|
|
||||||
|
|
||||||
### 对称加密 (Symmetric)
|
|
||||||
- `func NewSymmetric(cipher SymmetricCipher, safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error)`
|
|
||||||
- `func NewSymmetricAndEraseKey(cipher SymmetricCipher, key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func NewSymmetricWithoutEraseKey(cipher SymmetricCipher, key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func NewAESCBC(safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error)`
|
|
||||||
- `func NewAESCBCAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func NewAESCBCWithoutEraseKey(key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func NewAESGCM(safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error)`
|
|
||||||
- `func NewAESGCMAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func NewAESGCMWithoutEraseKey(key, iv []byte) (*Symmetric, error)`
|
|
||||||
- `func (s *Symmetric) Close()`
|
|
||||||
- `func (s *Symmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error)`
|
|
||||||
- `func (s *Symmetric) EncryptAndErase(data []byte) ([]byte, error)`
|
|
||||||
- `func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error)`
|
|
||||||
- `func (s *Symmetric) MustEncrypt(data []byte) []byte`
|
|
||||||
- `func (s *Symmetric) Decrypt(data []byte) (*safe.SafeBuf, error)`
|
|
||||||
- `func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error)`
|
|
||||||
- `func (s *Symmetric) MustDecrypt(data []byte) []byte`
|
|
||||||
- `func (s *Symmetric) TryDecrypt(data []byte) []byte`
|
|
||||||
|
|
||||||
### 非对称加密 (Asymmetric)
|
|
||||||
- `func NewAsymmetric(algorithm AsymmetricAlgorithm, safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error)`
|
|
||||||
- `func NewAsymmetricAndEraseKey(algorithm AsymmetricAlgorithm, privateKey, publicKey []byte) (*Asymmetric, error)`
|
|
||||||
- `func NewAsymmetricWithoutEraseKey(algorithm AsymmetricAlgorithm, privateKey, publicKey []byte, fastMode bool) (*Asymmetric, error)`
|
|
||||||
- `func NewRSA(priv, pub *safe.SafeBuf) (*Asymmetric, error)` / `NewRSAAndEraseKey(...)` / `NewRSAWithoutEraseKey(...)`
|
|
||||||
- `func NewECDSA(priv, pub *safe.SafeBuf) (*Asymmetric, error)` / `NewECDSAAndEraseKey(...)` / `NewECDSAWithoutEraseKey(...)`
|
|
||||||
- `func NewED25519(priv, pub *safe.SafeBuf) (*Asymmetric, error)` / `NewED25519AndEraseKey(...)` / `NewED25519WithoutEraseKey(...)`
|
|
||||||
- `func NewX25519(priv, pub *safe.SafeBuf) (*Asymmetric, error)` / `NewX25519AndEraseKey(...)` / `NewX25519WithoutEraseKey(...)`
|
|
||||||
- `func (a *Asymmetric) Close()`
|
|
||||||
- `func (a *Asymmetric) Sign(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) SignAndErase(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) MustSign(data []byte, hash ...crypto.Hash) []byte`
|
|
||||||
- `func (a *Asymmetric) Verify(data []byte, signature []byte, hash ...crypto.Hash) (bool, error)`
|
|
||||||
- `func (a *Asymmetric) MustVerify(data []byte, signature []byte, hash ...crypto.Hash) bool`
|
|
||||||
- `func (a *Asymmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) EncryptAndErase(data []byte) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) EncryptBytes(data []byte) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) MustEncrypt(data []byte) []byte`
|
|
||||||
- `func (a *Asymmetric) Decrypt(data []byte) (*safe.SafeBuf, error)`
|
|
||||||
- `func (a *Asymmetric) DecryptBytes(data []byte) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) MustDecrypt(data []byte) []byte`
|
|
||||||
- `func (a *Asymmetric) TryDecrypt(data []byte) []byte`
|
|
||||||
|
|
||||||
### 密钥对生成
|
|
||||||
- `func GenerateRSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
|
||||||
- `func GenerateECDSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
|
||||||
- `func GenerateEd25519KeyPair() (priv, pub []byte, err error)`
|
|
||||||
- `func GenerateX25519KeyPair() (priv, pub []byte, err error)`
|
|
||||||
|
|
||||||
### Hash 与填充辅助
|
|
||||||
- `func MD5(data ...[]byte) []byte` / `MD5ToHex(data) string` / `MD5ToBase64(data) string` / `MD5ToUrlBase64(data) string`
|
|
||||||
- `func Sha256(data ...[]byte) []byte` / `Sha256ToHex(data) string` / `Sha256ToBase64(data) string` / `Sha256ToUrlBase64(data) string`
|
|
||||||
- `func Sha512(data ...[]byte) []byte` / `Sha512ToHex(data) string` / `Sha512ToBase64(data) string` / `Sha512ToUrlBase64(data) string`
|
|
||||||
- `func HmacSha256(key []byte, data ...[]byte) []byte`
|
|
||||||
- `func Pkcs5Padding(data []byte, blockSize int) []byte` / `Pkcs5UnPadding(data []byte) []byte`
|
|
||||||
- `func AnsiX923Padding(data []byte, blockSize int) []byte` / `AnsiX923UnPadding(data []byte) []byte`
|
|
||||||
|
|
||||||
## 🧩 典型模式 (Best Practices)
|
|
||||||
|
|
||||||
* **✅ 安全传输 (SafeBuf 优先)**:
|
|
||||||
```go
|
|
||||||
// 对敏感数据进行加密
|
|
||||||
sb := safe.NewSafeBuf(sensitiveData)
|
|
||||||
encrypted, _ := s.Encrypt(sb)
|
|
||||||
// 解密回受保护的 SafeBuf
|
|
||||||
decSb, _ := s.Decrypt(encrypted)
|
|
||||||
defer decSb.Close()
|
|
||||||
```
|
|
||||||
|
|
||||||
* **✅ 高并发签名 (FastMode)**:
|
|
||||||
```go
|
|
||||||
// S端场景使用 FastMode
|
|
||||||
a, _ := crypto.NewAsymmetricWithoutEraseKey(algo, priv, pub, true)
|
|
||||||
defer a.Close()
|
|
||||||
sig := a.MustSign(data)
|
|
||||||
```
|
|
||||||
|
|
||||||
5
CHANGELOG-LATEST.md
Normal file
5
CHANGELOG-LATEST.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
## [v1.1.2] - 2026-05-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **统一配置解密接口 (加固版)**:新增 `OnSetDefaultAES` 与 `SetDefaultAES` 注入接口,支持自动擦除密钥与自动锁定通道。
|
||||||
|
- **基础设施对齐**:同步更新 `redis`, `api`, `db`, `mail` 等项目接入 `crypto` 安全解密体系。
|
||||||
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,5 +1,33 @@
|
|||||||
# Changelog: @go/crypto
|
# Changelog: @go/crypto
|
||||||
|
|
||||||
|
## [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) 支持。
|
||||||
|
- **便捷构造器**:新增 `New...ByPassword` 系列 API,支持通过密码与盐直接创建 AES (GCM/CBC)、RSA、ECDSA、Ed25519 及 X25519 实例。
|
||||||
|
- **确定性生成**:非对称算法(ECDSA, Ed25519, X25519)支持基于密码的确定性密钥对生成,确保多端/多次调用的一致性(注:RSA 因算法特性,其确定性受 Go 版本内部实现影响)。
|
||||||
|
- **强制内存安全**:所有 `ByPassword` 接口均强制执行 `EraseKey` 策略,派生完成后立即擦除原始密码与盐。
|
||||||
|
|
||||||
|
## [v1.0.6] - 2026-05-06
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **设计哲学对齐**:全面废除 `Must` 前缀函数(`MustEncrypt`, `MustDecrypt`, `MustSign`, `MustVerify`),改为配合 `go/cast` 的 `As` 函数。
|
||||||
|
- **鲁棒性增强**:在 `AESCipher.Decrypt` (CBC 模式) 中增加数据长度校验,防止因输入非块大小倍数而导致的 Panic。
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **API 补全**:新增 `TryDecrypt` 方法,在解密失败时返回原始数据。
|
||||||
|
- **依赖对齐**:引入 `apigo.cc/go/cast` 依赖。
|
||||||
|
|
||||||
|
## [v1.0.5] - 2026-05-01
|
||||||
|
- (同步版本号)
|
||||||
|
|
||||||
## [v1.0.4] - 2026-05-01
|
## [v1.0.4] - 2026-05-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
83
README.md
83
README.md
@ -1,69 +1,72 @@
|
|||||||
# 关于本项目
|
# 关于本项目
|
||||||
|
|
||||||
本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。
|
本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。
|
||||||
|
|
||||||
# @go/crypto
|
# @go/crypto
|
||||||
|
|
||||||
`@go/crypto` 是一个为“极速开发、内存级防御、全业务适配”设计的全功能加密工具库。它深度集成内存锁定与物理擦除技术,为金融级和 C 端运行环境提供了底层安全保障,同时为高并发服务端提供极致的性能模式。
|
`@go/crypto` 是一个为“安全闭环、消除摩擦”设计的加密工具库。它强制执行内存安全(基于 `go/safe`)与资源自动回收,并提供语义化的加解密与 Hash API。
|
||||||
|
|
||||||
## 🎯 设计哲学
|
## 🎯 设计哲学
|
||||||
|
|
||||||
* **防御优先 (SafeMode)**:默认采用 `AndEraseKey` 范式,密钥构造即擦除原始明文,杜绝内存残留。
|
* **内存安全闭环**:通过 `safe.SafeBuf` 保护密钥,确保敏感数据在内存中加密存储,并在使用后自动/手动擦除,防止内存 Dump 攻击。
|
||||||
* **性能巅峰 (FastMode)**:针对高并发场景(如 API 签名),提供对象缓存模式,显著降低 CPU 消耗。
|
* **消除摩擦 (Frictionless)**:废除 `Must` 前缀函数,全面结合 `go/cast` 的 `As` 函数。
|
||||||
* **混合加解密**:原生支持非对称混合加密逻辑(ECDH + HKDF + AES),支持任意长度数据加密。
|
* **资源自动回收**:利用 `runtime.SetFinalizer` 确保加密实例在垃圾回收时自动释放密钥资源。
|
||||||
* **混淆适配**:对称加密支持密钥自动截断,允许传入超长 buffer 以混淆内存特征。
|
* **高性能混合体系**:内置 AES (CBC/GCM)、RSA (PSS/OAEP)、ECDSA、Ed25519、X25519,支持 FastMode 缓存解析结果。
|
||||||
|
|
||||||
## 🛠 API Reference
|
## 🛠 API Reference
|
||||||
|
|
||||||
### 对称加密 (AES-CBC/GCM)
|
### 对称加密 (Symmetric)
|
||||||
|
- `func NewAESCBC(safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error)`
|
||||||
- `func NewAESCBCAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
- `func NewAESCBCAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
||||||
- `func NewAESGCMAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
- `func NewAESCBCByPassword(password, salt []byte) (*Symmetric, error)` (Argon2id 驱动)
|
||||||
- `func (s *Symmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error)`
|
- `func NewAESGCMByPassword(password, salt []byte) (*Symmetric, error)` (Argon2id 驱动)
|
||||||
- `func (s *Symmetric) EncryptAndErase(data []byte) ([]byte, error)`
|
|
||||||
- `func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error)`
|
- `func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error)`
|
||||||
- `func (s *Symmetric) MustEncrypt(data []byte) []byte`
|
|
||||||
- `func (s *Symmetric) Decrypt(data []byte) (*safe.SafeBuf, error)`
|
|
||||||
- `func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error)`
|
- `func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error)`
|
||||||
- `func (s *Symmetric) MustDecrypt(data []byte) []byte`
|
- `func (s *Symmetric) TryDecrypt(data []byte) []byte` (解密失败返回原始数据)
|
||||||
- `func (s *Symmetric) TryDecrypt(data []byte) []byte`
|
|
||||||
|
|
||||||
### 非对称加密 (RSA/ECDSA/Ed25519/X25519)
|
### 非对称加密与签名 (Asymmetric)
|
||||||
- `func NewRSAAndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
- `func NewRSA(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error)`
|
||||||
- `func NewECDSAAndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
- `func NewRSAByPassword(password, salt []byte, bitSize ...int) (*Asymmetric, error)` (Argon2id 确定性生成)
|
||||||
- `func NewED25519AndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
- `func NewECDSAByPassword(password, salt []byte, bitSize ...int) (*Asymmetric, error)` (确定性生成)
|
||||||
- `func NewX25519AndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
- `func NewED25519ByPassword(password, salt []byte) (*Asymmetric, error)` (确定性生成)
|
||||||
- `func NewAsymmetricWithoutEraseKey(algo, priv, pub, fastMode) (*Asymmetric, error)`
|
- `func NewX25519ByPassword(password, salt []byte) (*Asymmetric, error)` (确定性生成)
|
||||||
- `func (a *Asymmetric) Sign(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
- `func (a *Asymmetric) Sign(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
||||||
- `func (a *Asymmetric) SignAndErase(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) MustSign(data []byte, hash ...crypto.Hash) []byte`
|
|
||||||
- `func (a *Asymmetric) Verify(data, signature []byte, hash ...crypto.Hash) (bool, error)`
|
- `func (a *Asymmetric) Verify(data, signature []byte, hash ...crypto.Hash) (bool, error)`
|
||||||
- `func (a *Asymmetric) MustVerify(data, signature []byte, hash ...crypto.Hash) bool`
|
- `func (a *Asymmetric) EncryptBytes(data []byte) ([]byte, error)`
|
||||||
- `func (a *Asymmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) EncryptAndErase(data []byte) ([]byte, error)`
|
|
||||||
- `func (a *Asymmetric) MustEncrypt(data []byte) []byte`
|
|
||||||
- `func (a *Asymmetric) Decrypt(data []byte) (*safe.SafeBuf, error)`
|
|
||||||
- `func (a *Asymmetric) DecryptBytes(data []byte) ([]byte, error)`
|
- `func (a *Asymmetric) DecryptBytes(data []byte) ([]byte, error)`
|
||||||
- `func (a *Asymmetric) MustDecrypt(data []byte) []byte`
|
|
||||||
- `func (a *Asymmetric) TryDecrypt(data []byte) []byte`
|
|
||||||
|
|
||||||
### Hash 系列 (MD5/SHA)
|
### 密钥生成
|
||||||
- `func MD5(data ...[]byte) []byte` / `func MD5ToHex(d []byte) string`
|
|
||||||
- `func Sha256ToHex(d []byte) string` / `func Sha256ToBase64(d []byte) string` / `func Sha256ToUrlBase64(d []byte) string`
|
|
||||||
- *其他算法 (Sha1, Sha512) 均支持上述 Hex/Base64/UrlBase64 变体*
|
|
||||||
|
|
||||||
### 密钥对生成
|
|
||||||
- `func GenerateRSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
- `func GenerateRSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
||||||
- `func GenerateECDSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
- `func GenerateECDSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
||||||
- `func GenerateEd25519KeyPair() (priv, pub []byte, err error)`
|
- `func GenerateEd25519KeyPair() (priv, pub []byte, err error)`
|
||||||
- `func GenerateX25519KeyPair() (priv, pub []byte, err error)`
|
- `func GenerateX25519KeyPair() (priv, pub []byte, err error)`
|
||||||
|
|
||||||
## 🚀 FastMode 性能模式
|
### 全局配置解密 (Default AES)
|
||||||
在高并发服务端对接场景中,内存被恶意扫描的风险极低,但 CPU 压力巨大。
|
为实现全项目配置解密的统一管理与生产环境密钥注入,提供以下全局接口:
|
||||||
Ed25519的非FastMode模式下性能也完全无损,强烈建议优先使用该算法。
|
- `var DefaultAES *Symmetric`:全局默认 AES 实例(初始使用内置硬编码密钥)。
|
||||||
**建议:** 使用 `NewAsymmetricWithoutEraseKey(..., true)`。
|
- `func OnSetDefaultAES(h func(*Symmetric))`:注册 `DefaultAES` 变更回调。
|
||||||
* **效果**:缓存解析后的密钥对象。
|
- `func SetDefaultAES(key, iv []byte)`:注入生产环境密钥并触发所有回调更新。
|
||||||
* **性能**:实测签名速度可提升数倍,相比默认模式能支持更高的 QPS。
|
|
||||||
|
### 哈希算法 (Hash)
|
||||||
|
- `func MD5(data []byte) []byte`
|
||||||
|
- `func Sha256(data []byte) []byte`
|
||||||
|
- `func HmacSha256(data, key []byte) []byte`
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get apigo.cc/go/crypto
|
go get apigo.cc/go/crypto
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 💡 示例 (配合 cast.As 消除摩擦)
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"apigo.cc/go/crypto"
|
||||||
|
"apigo.cc/go/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用 cast.As 替代原有的 MustEncrypt 系列
|
||||||
|
cipher, _ := crypto.NewAESCBCAndEraseKey(key, iv)
|
||||||
|
ciphertext := cast.As(cipher.EncryptBytes(plaintext))
|
||||||
|
```
|
||||||
|
|||||||
40
TEST.md
40
TEST.md
@ -1,38 +1,28 @@
|
|||||||
# Test Report: @go/crypto
|
# Test Report: @go/crypto
|
||||||
|
|
||||||
## 📋 测试概览
|
## 📋 测试概览
|
||||||
- **测试时间**: 2026-05-01
|
- **测试时间**: 2026-05-07
|
||||||
- **测试环境**: darwin/amd64
|
- **测试环境**: darwin/amd64
|
||||||
- **Go 版本**: 1.25.0
|
- **Go 版本**: 1.25.0
|
||||||
|
|
||||||
## ✅ 功能测试 (Functional Tests)
|
## ✅ 功能测试 (Functional Tests)
|
||||||
| 场景 | 状态 | 描述 |
|
| 场景 | 状态 | 描述 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `TestRSA_AllModes` | PASS | 覆盖 RSA PSS/OAEP 以及 FastMode 缓存模式。 |
|
| `TestSymmetric` | PASS | AES-CBC/GCM 加解密往返测试。 |
|
||||||
| `TestECDSA_Hybrid` | PASS | 验证 ECDH + HKDF + AESGCM 混合加解密链路。 |
|
| `TestAsymmetric` | PASS | RSA, ECDSA, Ed25519, X25519 签名与加解密测试。 |
|
||||||
| `TestEd25519` | PASS | 纯签名/验签逻辑验证。 |
|
| `TestMustAndTryMethods` | PASS | 配合 `cast.As` 消除摩擦及 `TryDecrypt` 逻辑测试。 |
|
||||||
| `TestX25519_Hybrid` | PASS | 验证 X25519 混合加解密链路。 |
|
| `TestSecurityErase` | PASS | 密钥内存安全擦除验证。 |
|
||||||
| `TestSecurityErase` | PASS | **核心安全测试**:验证 `AndEraseKey` 调用后原始密钥内存确实被物理擦除。 |
|
| `TestPasswordBased` | PASS | 基于密码 (Argon2id) 的对称与非对称密钥派生功能测试。 |
|
||||||
| `TestSymmetricObfuscation` | PASS | 验证传入 64 字节混淆密钥时,自动截断适配 32 字节 AES 密钥的逻辑。 |
|
| `TestDeterministic` | PASS | 非对称密钥基于密码的确定性生成验证。 |
|
||||||
| `TestAnsiX923Padding` | PASS | 验证 ANSI X9.23 填充算法的一致性。 |
|
|
||||||
| `TestConcurrentSafe` | PASS | 验证 `Symmetric` 对象在高并发环境下的数据隔离与安全性。 |
|
|
||||||
| `TestHashCompatibility` | PASS | 确保封装的 Hash API 与标准库 MD5/SHA256/HMAC 结果 1:1 对齐。 |
|
|
||||||
|
|
||||||
## 🛡️ 鲁棒性防御 (Robustness)
|
## 🛡️ 鲁棒性防御 (Robustness)
|
||||||
- **密钥混淆**:支持超长密钥输入以混淆内存特征,内部自动适配 16/24/32 字节核心密钥。
|
- **Panic 防御**:在 CBC 模式解密中强制校验块对齐,拦截底层库可能抛出的 Panic。
|
||||||
- **命名一致性**:修复了所有 `Without` 的拼写错误,确保 API 调用链路语义严谨。
|
- **内存安全**:基于 `go/safe` 的密钥管理与自动释放。
|
||||||
- **填充优化**:使用 `bytes.Repeat` 替代循环填充,降低 GC 压力并提升性能稳定性。
|
|
||||||
|
|
||||||
## ⚡ 性能基准 (Benchmarks)
|
## ⚡ 性能基准 (Benchmarks)
|
||||||
| 算法类型 | 耗时 (ns/op) | 性能倍率 (对比 RSA) | 结论 |
|
| 函数 | 平均耗时 | 性能分析 |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| **Ed25519 签名** | **~25605** | **50.2x** | **性能冠军**,极力推荐。 |
|
| `AES_GCM` | **4854 ns/op** | 性能优异。 |
|
||||||
| **ECDSA 签名** | **~49507** | **26.0x** | 现代 Web 标准,性能卓越。 |
|
| `RSA_Sign` | **1407588 ns/op** | 符合 RSA 标准耗时。 |
|
||||||
| **X25519 混合加密** | **~189939** | **6.8x** | 适合非对称大数据量加密。 |
|
| `Ed25519_Sign` | **28299 ns/op** | 高性能签名。 |
|
||||||
| **RSA-2048 签名** | **~1286459**| **1.0x** | **性能瓶颈**,仅建议用于兼容。 |
|
| `X25519_Encrypt` | **204639 ns/op** | 混合加密模式性能表现。 |
|
||||||
| **AES-GCM** | **~4746** | - | 优于 CBC,首选对称算法。 |
|
|
||||||
|
|
||||||
> **首席架构师建议**:
|
|
||||||
> 1. 云端高并发:优先 Ed25519 签名 + AES-GCM 对称加密。
|
|
||||||
> 2. 传统兼容:使用 RSA-2048 + FastMode。
|
|
||||||
> 3. 混合加密:大数据量直接用 X25519/ECDSA 的 `Encrypt` 方法。
|
|
||||||
|
|||||||
16
aes.go
16
aes.go
@ -3,6 +3,7 @@ package crypto
|
|||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"apigo.cc/go/safe"
|
"apigo.cc/go/safe"
|
||||||
)
|
)
|
||||||
@ -38,6 +39,18 @@ func NewAESGCMWithoutEraseKey(key, iv []byte) (*Symmetric, error) {
|
|||||||
return NewSymmetricWithoutEraseKey(AESGCM, key, iv)
|
return NewSymmetricWithoutEraseKey(AESGCM, key, iv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAESCBCByPassword(password, salt []byte) (*Symmetric, error) {
|
||||||
|
derived := DeriveKey(password, salt, 32+16)
|
||||||
|
defer safe.ZeroMemory(derived)
|
||||||
|
return NewSymmetricAndEraseKey(AESCBC, derived[:32], derived[32:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAESGCMByPassword(password, salt []byte) (*Symmetric, error) {
|
||||||
|
derived := DeriveKey(password, salt, 32+12)
|
||||||
|
defer safe.ZeroMemory(derived)
|
||||||
|
return NewSymmetricAndEraseKey(AESGCM, derived[:32], derived[32:])
|
||||||
|
}
|
||||||
|
|
||||||
func (c *AESCipher) Encrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
|
func (c *AESCipher) Encrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -71,6 +84,9 @@ func (c *AESCipher) Decrypt(data []byte, key []byte, iv []byte) ([]byte, error)
|
|||||||
return aesgcm.Open(nil, iv[:aesgcm.NonceSize()], data, nil)
|
return aesgcm.Open(nil, iv[:aesgcm.NonceSize()], data, nil)
|
||||||
}
|
}
|
||||||
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
|
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
|
||||||
|
if len(data)%block.BlockSize() != 0 {
|
||||||
|
return nil, errors.New("crypto/cipher: input not full blocks")
|
||||||
|
}
|
||||||
origData := make([]byte, len(data))
|
origData := make([]byte, len(data))
|
||||||
blockMode.CryptBlocks(origData, data)
|
blockMode.CryptBlocks(origData, data)
|
||||||
return Pkcs5UnPadding(origData), nil
|
return Pkcs5UnPadding(origData), nil
|
||||||
|
|||||||
@ -94,14 +94,6 @@ func (a *Asymmetric) SignAndErase(data []byte, hash ...crypto.Hash) ([]byte, err
|
|||||||
return a.Sign(data, hash...)
|
return a.Sign(data, hash...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Asymmetric) MustSign(data []byte, hash ...crypto.Hash) []byte {
|
|
||||||
signature, err := a.Sign(data, hash...)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return signature
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Asymmetric) Verify(data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
func (a *Asymmetric) Verify(data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||||
if a.pubCache != nil {
|
if a.pubCache != nil {
|
||||||
return a.algorithm.Verify(a.pubCache, data, signature, hash...)
|
return a.algorithm.Verify(a.pubCache, data, signature, hash...)
|
||||||
@ -118,14 +110,6 @@ func (a *Asymmetric) Verify(data []byte, signature []byte, hash ...crypto.Hash)
|
|||||||
return a.algorithm.Verify(pubKey, data, signature, hash...)
|
return a.algorithm.Verify(pubKey, data, signature, hash...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Asymmetric) MustVerify(data []byte, signature []byte, hash ...crypto.Hash) bool {
|
|
||||||
valid, err := a.Verify(data, signature, hash...)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Asymmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error) {
|
func (a *Asymmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error) {
|
||||||
buf := safeBuf.Open()
|
buf := safeBuf.Open()
|
||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
@ -158,14 +142,6 @@ func (a *Asymmetric) EncryptBytes(data []byte) ([]byte, error) {
|
|||||||
return cipherAlgo.Encrypt(pubKey, data)
|
return cipherAlgo.Encrypt(pubKey, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Asymmetric) MustEncrypt(data []byte) []byte {
|
|
||||||
enc, err := a.EncryptBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return enc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Asymmetric) Decrypt(data []byte) (*safe.SafeBuf, error) {
|
func (a *Asymmetric) Decrypt(data []byte) (*safe.SafeBuf, error) {
|
||||||
buf, err := a.DecryptBytes(data)
|
buf, err := a.DecryptBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -194,14 +170,6 @@ func (a *Asymmetric) DecryptBytes(data []byte) ([]byte, error) {
|
|||||||
return cipherAlgo.Decrypt(privKey, data)
|
return cipherAlgo.Decrypt(privKey, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Asymmetric) MustDecrypt(data []byte) []byte {
|
|
||||||
dec, err := a.DecryptBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return dec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Asymmetric) TryDecrypt(data []byte) []byte {
|
func (a *Asymmetric) TryDecrypt(data []byte) []byte {
|
||||||
dec, err := a.DecryptBytes(data)
|
dec, err := a.DecryptBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
69
default.go
Normal file
69
default.go
Normal file
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
54
default_test.go
Normal file
54
default_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
ecdsa.go
45
ecdsa.go
@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"apigo.cc/go/safe"
|
"apigo.cc/go/safe"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
@ -35,6 +36,50 @@ func NewECDSAWithoutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymm
|
|||||||
return NewAsymmetricWithoutEraseKey(ECDSAGCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
return NewAsymmetricWithoutEraseKey(ECDSAGCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewECDSAByPassword(password, salt []byte, bitSize ...int) (*Asymmetric, error) {
|
||||||
|
size := 256
|
||||||
|
if len(bitSize) > 0 {
|
||||||
|
size = bitSize[0]
|
||||||
|
}
|
||||||
|
var curve elliptic.Curve
|
||||||
|
keyLen := 32
|
||||||
|
switch size {
|
||||||
|
case 256:
|
||||||
|
curve = elliptic.P256()
|
||||||
|
keyLen = 32
|
||||||
|
case 384:
|
||||||
|
curve = elliptic.P384()
|
||||||
|
keyLen = 48
|
||||||
|
default:
|
||||||
|
curve = elliptic.P521()
|
||||||
|
keyLen = 66 // (521+7)/8
|
||||||
|
}
|
||||||
|
|
||||||
|
seed := DeriveKey(password, salt, uint32(keyLen))
|
||||||
|
defer safe.ZeroMemory(seed)
|
||||||
|
|
||||||
|
params := curve.Params()
|
||||||
|
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(ecdsa.PrivateKey)
|
||||||
|
priv.Curve = curve
|
||||||
|
priv.D = d
|
||||||
|
priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(d.Bytes())
|
||||||
|
|
||||||
|
privateKey, err := x509.MarshalECPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
publicKey, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewECDSAAndEraseKey(privateKey, publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
func NewECDSAAlgorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *ECDSAAlgorithm {
|
func NewECDSAAlgorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *ECDSAAlgorithm {
|
||||||
return &ECDSAAlgorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
return &ECDSAAlgorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,14 @@ func NewED25519WithoutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asy
|
|||||||
return NewAsymmetricWithoutEraseKey(ED25519, safePrivateKeyBuf, safePublicKeyBuf, false)
|
return NewAsymmetricWithoutEraseKey(ED25519, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewED25519ByPassword(password, salt []byte) (*Asymmetric, error) {
|
||||||
|
seed := DeriveKey(password, salt, 32)
|
||||||
|
defer safe.ZeroMemory(seed)
|
||||||
|
privKey := ed25519.NewKeyFromSeed(seed)
|
||||||
|
pubKey := privKey.Public().(ed25519.PublicKey)
|
||||||
|
return NewED25519AndEraseKey(privKey, pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateEd25519KeyPair() ([]byte, []byte, error) {
|
func GenerateEd25519KeyPair() ([]byte, []byte, error) {
|
||||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
11
go.mod
11
go.mod
@ -3,12 +3,13 @@ module apigo.cc/go/crypto
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/encoding v1.0.4
|
apigo.cc/go/cast v1.3.3
|
||||||
apigo.cc/go/safe v1.0.4
|
apigo.cc/go/encoding v1.3.1
|
||||||
golang.org/x/crypto v0.50.0
|
apigo.cc/go/safe v1.3.1
|
||||||
|
golang.org/x/crypto v0.51.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/rand v1.0.4 // indirect
|
apigo.cc/go/rand v1.3.1 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
|
||||||
|
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
|
||||||
|
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=
|
||||||
|
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
64
new_test.go
64
new_test.go
@ -4,50 +4,60 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/go/cast"
|
||||||
"apigo.cc/go/crypto"
|
"apigo.cc/go/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMustAndTryMethods(t *testing.T) {
|
func TestMustAndTryMethods(t *testing.T) {
|
||||||
// Setup
|
|
||||||
priv, pub, _ := crypto.GenerateRSAKeyPair(2048)
|
|
||||||
a, _ := crypto.NewRSAWithoutEraseKey(priv, pub)
|
|
||||||
data := []byte("secret")
|
|
||||||
|
|
||||||
// Symmetric
|
|
||||||
key := []byte("1234567890123456")
|
key := []byte("1234567890123456")
|
||||||
iv := []byte("1234567890123456")
|
iv := []byte("1234567890123456")
|
||||||
s, _ := crypto.NewAESGCMWithoutEraseKey(key, iv)
|
data := []byte("hello world")
|
||||||
|
|
||||||
|
s, _ := crypto.NewAESCBCAndEraseKey(key, iv)
|
||||||
encS, _ := s.EncryptBytes(data)
|
encS, _ := s.EncryptBytes(data)
|
||||||
|
|
||||||
// Tests
|
// Test with cast.As
|
||||||
if !bytes.Equal(s.MustEncrypt(data), encS) {
|
if !bytes.Equal(cast.As(s.EncryptBytes(data)), encS) {
|
||||||
t.Error("MustEncrypt mismatch")
|
t.Error("EncryptBytes with cast.As mismatch")
|
||||||
}
|
|
||||||
if !bytes.Equal(s.MustDecrypt(encS), data) {
|
|
||||||
t.Error("MustDecrypt mismatch")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(s.TryDecrypt([]byte("bad")), []byte("bad")) {
|
|
||||||
t.Error("TryDecrypt should return original on error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-encrypt to get fresh ciphertext for asymmetric
|
if !bytes.Equal(cast.As(s.DecryptBytes(encS)), data) {
|
||||||
|
t.Error("DecryptBytes with cast.As mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TryDecrypt
|
||||||
|
if !bytes.Equal(s.TryDecrypt(encS), data) {
|
||||||
|
t.Error("TryDecrypt match failed")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(s.TryDecrypt([]byte("invalid")), []byte("invalid")) {
|
||||||
|
t.Error("TryDecrypt fallback failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asymmetric
|
||||||
|
priv, pub, _ := crypto.GenerateRSAKeyPair(2048)
|
||||||
|
a, _ := crypto.NewRSAAndEraseKey(priv, pub)
|
||||||
encA, _ := a.EncryptBytes(data)
|
encA, _ := a.EncryptBytes(data)
|
||||||
decA := a.MustDecrypt(encA)
|
|
||||||
|
decA := cast.As(a.DecryptBytes(encA))
|
||||||
if !bytes.Equal(decA, data) {
|
if !bytes.Equal(decA, data) {
|
||||||
t.Errorf("Asymmetric MustDecrypt mismatch: got %s, want %s", string(decA), string(data))
|
t.Errorf("Asymmetric DecryptBytes with cast.As mismatch: got %s, want %s", string(decA), string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test MustEncrypt specifically (e.g. check it works at all)
|
// Test cast.As for EncryptBytes
|
||||||
encA2 := a.MustEncrypt(data)
|
encA2 := cast.As(a.EncryptBytes(data))
|
||||||
decA2 := a.MustDecrypt(encA2)
|
decA2 := cast.As(a.DecryptBytes(encA2))
|
||||||
if !bytes.Equal(decA2, data) {
|
if !bytes.Equal(decA2, data) {
|
||||||
t.Errorf("Asymmetric MustEncrypt/Decrypt failed")
|
t.Errorf("Asymmetric Encrypt/Decrypt via cast.As failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(a.TryDecrypt([]byte("bad")), []byte("bad")) {
|
// Test Sign with cast.As
|
||||||
t.Error("Asymmetric TryDecrypt should return original on error")
|
sig := cast.As(a.Sign(data))
|
||||||
|
if len(sig) == 0 {
|
||||||
|
t.Error("Sign with cast.As failed")
|
||||||
}
|
}
|
||||||
if len(a.MustSign(data)) == 0 {
|
|
||||||
t.Error("MustSign failed")
|
valid, _ := a.Verify(data, sig)
|
||||||
|
if !valid {
|
||||||
|
t.Error("Verify failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
password.go
Normal file
30
password.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"apigo.cc/go/safe"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default Argon2id parameters
|
||||||
|
const (
|
||||||
|
Argon2Time = 3
|
||||||
|
Argon2Memory = 64 * 1024 // 64MB
|
||||||
|
Argon2Threads = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeriveKey using Argon2id and automatically erases password and salt
|
||||||
|
func DeriveKey(password, salt []byte, keyLen uint32) []byte {
|
||||||
|
defer safe.ZeroMemory(password)
|
||||||
|
defer safe.ZeroMemory(salt)
|
||||||
|
return argon2.IDKey(password, salt, Argon2Time, Argon2Memory, Argon2Threads, keyLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
139
password_test.go
Normal file
139
password_test.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package crypto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/go/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPasswordBasedAES(t *testing.T) {
|
||||||
|
password := []byte("secret-password")
|
||||||
|
salt := []byte("fixed-salt")
|
||||||
|
data := []byte("hello world password")
|
||||||
|
|
||||||
|
// Test GCM
|
||||||
|
p1 := append([]byte(nil), password...)
|
||||||
|
s1 := append([]byte(nil), salt...)
|
||||||
|
aesGCM, err := crypto.NewAESGCMByPassword(p1, s1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
enc, _ := aesGCM.EncryptBytes(data)
|
||||||
|
dec, _ := aesGCM.DecryptBytes(enc)
|
||||||
|
if !bytes.Equal(data, dec) {
|
||||||
|
t.Error("AES-GCM password-based roundtrip failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test CBC
|
||||||
|
p2 := append([]byte(nil), password...)
|
||||||
|
s2 := append([]byte(nil), salt...)
|
||||||
|
aesCBC, err := crypto.NewAESCBCByPassword(p2, s2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
enc2, _ := aesCBC.EncryptBytes(data)
|
||||||
|
dec2, _ := aesCBC.DecryptBytes(enc2)
|
||||||
|
if !bytes.Equal(data, dec2) {
|
||||||
|
t.Error("AES-CBC password-based roundtrip failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify EraseKey (best effort check - though they are zeroed inside factory)
|
||||||
|
if bytes.Equal(p1, []byte("secret-password")) {
|
||||||
|
t.Error("Password 1 was not erased")
|
||||||
|
}
|
||||||
|
if bytes.Equal(s1, []byte("fixed-salt")) {
|
||||||
|
t.Error("Salt 1 was not erased")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordBasedAsymmetric(t *testing.T) {
|
||||||
|
password := []byte("asymm-password")
|
||||||
|
salt := []byte("asymm-salt")
|
||||||
|
data := []byte("hello asymm")
|
||||||
|
|
||||||
|
// 1. RSA
|
||||||
|
p1 := append([]byte(nil), password...)
|
||||||
|
s1 := append([]byte(nil), salt...)
|
||||||
|
rsa, err := crypto.NewRSAByPassword(p1, s1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
enc, _ := rsa.EncryptBytes(data)
|
||||||
|
dec, _ := rsa.DecryptBytes(enc)
|
||||||
|
if !bytes.Equal(data, dec) {
|
||||||
|
t.Error("RSA password-based roundtrip failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. ECDSA
|
||||||
|
p2 := append([]byte(nil), password...)
|
||||||
|
s2 := append([]byte(nil), salt...)
|
||||||
|
ecdsa, err := crypto.NewECDSAByPassword(p2, s2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sig, _ := ecdsa.Sign(data)
|
||||||
|
if ok, _ := ecdsa.Verify(data, sig); !ok {
|
||||||
|
t.Error("ECDSA password-based sign/verify failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Ed25519
|
||||||
|
p3 := append([]byte(nil), password...)
|
||||||
|
s3 := append([]byte(nil), salt...)
|
||||||
|
ed, err := crypto.NewED25519ByPassword(p3, s3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sig2, _ := ed.Sign(data)
|
||||||
|
if ok, _ := ed.Verify(data, sig2); !ok {
|
||||||
|
t.Error("Ed25519 password-based failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. X25519
|
||||||
|
p4 := append([]byte(nil), password...)
|
||||||
|
s4 := append([]byte(nil), salt...)
|
||||||
|
x, err := crypto.NewX25519ByPassword(p4, s4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
enc2, _ := x.EncryptBytes(data)
|
||||||
|
dec2, _ := x.DecryptBytes(enc2)
|
||||||
|
if !bytes.Equal(data, dec2) {
|
||||||
|
t.Error("X25519 password-based roundtrip failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeterministicGeneration(t *testing.T) {
|
||||||
|
password := []byte("determ-pass")
|
||||||
|
salt := []byte("determ-salt")
|
||||||
|
|
||||||
|
// Two instances with same password/salt should have same public key
|
||||||
|
p1 := append([]byte(nil), password...)
|
||||||
|
s1 := append([]byte(nil), salt...)
|
||||||
|
ed1, _ := crypto.NewED25519ByPassword(p1, s1)
|
||||||
|
|
||||||
|
p2 := append([]byte(nil), password...)
|
||||||
|
s2 := append([]byte(nil), salt...)
|
||||||
|
ed2, _ := crypto.NewED25519ByPassword(p2, s2)
|
||||||
|
|
||||||
|
// Since we don't have GetPublicKeyBytes, we can test by signing with ed1 and verifying with ed2 (if public keys match)
|
||||||
|
data := []byte("test")
|
||||||
|
sig, _ := ed1.Sign(data)
|
||||||
|
if ok, _ := ed2.Verify(data, sig); !ok {
|
||||||
|
t.Error("Deterministic generation failed (Ed25519): public keys do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test ECDSA determinism
|
||||||
|
p3 := append([]byte(nil), password...)
|
||||||
|
s3 := append([]byte(nil), salt...)
|
||||||
|
ec1, _ := crypto.NewECDSAByPassword(p3, s3)
|
||||||
|
|
||||||
|
p4 := append([]byte(nil), password...)
|
||||||
|
s4 := append([]byte(nil), salt...)
|
||||||
|
ec2, _ := crypto.NewECDSAByPassword(p4, s4)
|
||||||
|
|
||||||
|
sig2, _ := ec1.Sign(data)
|
||||||
|
if ok, _ := ec2.Verify(data, sig2); !ok {
|
||||||
|
t.Error("Deterministic generation failed (ECDSA): public keys do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
24
rsa.go
24
rsa.go
@ -33,6 +33,30 @@ func NewRSAWithoutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmet
|
|||||||
return NewAsymmetricWithoutEraseKey(RSA, safePrivateKeyBuf, safePublicKeyBuf, false)
|
return NewAsymmetricWithoutEraseKey(RSA, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRSAByPassword(password, salt []byte, bitSize ...int) (*Asymmetric, error) {
|
||||||
|
size := 2048
|
||||||
|
if len(bitSize) > 0 {
|
||||||
|
size = bitSize[0]
|
||||||
|
}
|
||||||
|
seed := DeriveKey(password, salt, 32)
|
||||||
|
defer safe.ZeroMemory(seed)
|
||||||
|
reader := NewDeterministicReader(seed, []byte("RSA Key Generation"))
|
||||||
|
|
||||||
|
priKey, err := rsa.GenerateKey(reader, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKey, err := x509.MarshalPKCS8PrivateKey(priKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
publicKey, err := x509.MarshalPKIXPublicKey(&priKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewRSAAndEraseKey(privateKey, publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateRSAKeyPair(bitSize int) ([]byte, []byte, error) {
|
func GenerateRSAKeyPair(bitSize int) ([]byte, []byte, error) {
|
||||||
if bitSize < 2048 {
|
if bitSize < 2048 {
|
||||||
bitSize = 2048
|
bitSize = 2048
|
||||||
|
|||||||
20
symmetric.go
20
symmetric.go
@ -81,15 +81,6 @@ func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error) {
|
|||||||
return s.cipher.Encrypt(data, key.Data, iv.Data)
|
return s.cipher.Encrypt(data, key.Data, iv.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustEncrypt 加密失败时返回空字节切片 (静默加密)
|
|
||||||
func (s *Symmetric) MustEncrypt(data []byte) []byte {
|
|
||||||
r, err := s.EncryptBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt 进行解密并返回一个受保护的 SafeBuf
|
// Decrypt 进行解密并返回一个受保护的 SafeBuf
|
||||||
func (s *Symmetric) Decrypt(data []byte) (*safe.SafeBuf, error) {
|
func (s *Symmetric) Decrypt(data []byte) (*safe.SafeBuf, error) {
|
||||||
buf, err := s.DecryptBytes(data)
|
buf, err := s.DecryptBytes(data)
|
||||||
@ -109,16 +100,7 @@ func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error) {
|
|||||||
return s.cipher.Decrypt(data, key.Data, iv.Data)
|
return s.cipher.Decrypt(data, key.Data, iv.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustDecryptBytes 解密失败时返回空字节切片 (静默解密)
|
// TryDecrypt 解密失败时返回原始数据 (静默解密)
|
||||||
func (s *Symmetric) MustDecrypt(data []byte) []byte {
|
|
||||||
r, err := s.DecryptBytes(data)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// TryDecryptBytes 解密失败时返回原始数据 (静默解密)
|
|
||||||
func (s *Symmetric) TryDecrypt(data []byte) []byte {
|
func (s *Symmetric) TryDecrypt(data []byte) []byte {
|
||||||
r, err := s.DecryptBytes(data)
|
r, err := s.DecryptBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
10
x25519.go
10
x25519.go
@ -33,6 +33,16 @@ func NewX25519WithoutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asym
|
|||||||
return NewAsymmetricWithoutEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
return NewAsymmetricWithoutEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewX25519ByPassword(password, salt []byte) (*Asymmetric, error) {
|
||||||
|
seed := DeriveKey(password, salt, 32)
|
||||||
|
defer safe.ZeroMemory(seed)
|
||||||
|
pubKey, err := curve25519.X25519(seed, curve25519.Basepoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewX25519AndEraseKey(seed, pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
func NewX25519Algorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *X25519Algorithm {
|
func NewX25519Algorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *X25519Algorithm {
|
||||||
return &X25519Algorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
return &X25519Algorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user