chore: 初始化 go/crypto (支持混合加解密与内存防御,包含完整算法工厂封装与安全擦除)(AI维护)
This commit is contained in:
parent
9b4433c37d
commit
7b157dab0c
39
AI.md
Normal file
39
AI.md
Normal file
@ -0,0 +1,39 @@
|
||||
# AI Coding Context: @go/crypto
|
||||
|
||||
本索引供 AI 模型理解 `@go/crypto` 的逻辑,以生成符合本项目“安全闭环、性能分级、语义一致”哲学的代码。
|
||||
|
||||
## 🤖 AI 行为准则
|
||||
|
||||
1. **场景化构造选择**:
|
||||
* **C端/低频场景**:强制推荐 `AndEraseKey` 系列(如 `NewAESGCMAndEraseKey`),安全至上。
|
||||
* **S端/高频场景**:推荐使用 `NewAsymmetricWithoutEraseKey(..., true)` 进入 **FastMode**。
|
||||
2. **API 优先级**:
|
||||
* 摘要计算:优先使用 `XxxToHex` 或 `XxxToBase64` 快捷函数,减少链式调用。
|
||||
* 签名验证:默认采用 SHA256。
|
||||
3. **闭环义务**:任何构造的 `Asymmetric` 或 `Symmetric` 对象必须由 AI 生成 `defer obj.Close()` 代码。
|
||||
4. **数据展现**:严禁在 `go/crypto` 中寻找二进制展现逻辑,必须调用 `go/encoding` 处理。
|
||||
|
||||
## 🛠 关键 API 逻辑约定
|
||||
|
||||
| 任务 | 推荐 API | 逻辑特征 |
|
||||
| :--- | :--- | :--- |
|
||||
| **混合加解密** | `Asymmetric.Encrypt` | 针对 ECDSA/X25519 自动执行混合加密。 |
|
||||
| **高并发签名** | `FastMode` | 启用缓存,QPS 吞吐量数倍提升。 |
|
||||
| **混淆防御** | `NewSymmetric...` | 允许传入超长 Key,内部自动截断以隐藏密钥特征。 |
|
||||
|
||||
## 🧩 典型模式 (Best Practices)
|
||||
|
||||
* **✅ 高性能服务端 (FastMode)**:
|
||||
```go
|
||||
// 适合每秒数万次的验签场景
|
||||
a, _ := crypto.NewAsymmetricWithoutEraseKey(crypto.ED25519, priv, pub, true)
|
||||
defer a.Close()
|
||||
```
|
||||
|
||||
* **✅ 混合加解密 (混合流)**:
|
||||
```go
|
||||
// 对大数据进行非对称加密的唯一正确方式
|
||||
a, _ := crypto.NewX25519AndEraseKey(priv, pub)
|
||||
defer a.Close()
|
||||
encrypted, _ := a.Encrypt(bigData)
|
||||
```
|
||||
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Changelog: @go/crypto
|
||||
|
||||
## [v1.0.0] - 2026-04-22
|
||||
|
||||
### Added
|
||||
- **核心算法支持**:提供 AES (CBC/GCM)、RSA (PSS/OAEP/PKCS1v15)、ECDSA、Ed25519、X25519 全量主流算法。
|
||||
- **混合加密模式**:针对 ECDSA 和 X25519 实现了 ECDH + HKDF + AES-GCM/CBC 的自动化混合加解密。
|
||||
- **填充算法增强**:新增 ANSI X9.23 填充支持,完善 PKCS#7 (Pkcs5) 填充。
|
||||
- **内存安全集成**:深度集成 `@go/safe`,提供 `AndEraseKey` 构造器,实现密钥构造即擦除原始明文,杜绝内存残留。
|
||||
- **混淆防御机制**:对称加密支持超长密钥自动截断适配,增强内存指纹抗性。
|
||||
- **高性能模式 (FastMode)**:非对称加密支持可选的对象缓存模式,显著降低高频调用下的解析开销。
|
||||
- **便捷 Hash 包装**:提供 MD5/SHA 家族的一键式 Hex/Base64 返回接口。
|
||||
- **兼容性语义**:1:1 还原 `ssgo/u` 函数命名,确保业务迁移无感知。
|
||||
57
README.md
57
README.md
@ -1,3 +1,56 @@
|
||||
# crypto
|
||||
# 关于本项目
|
||||
本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。
|
||||
|
||||
加密算法工具库,提供对称/非对称加密实现
|
||||
# @go/crypto
|
||||
|
||||
`@go/crypto` 是一个为“极速开发、内存级防御、全业务适配”设计的全功能加密工具库。它深度集成内存锁定与物理擦除技术,为金融级和 C 端运行环境提供了底层安全保障,同时为高并发服务端提供极致的性能模式。
|
||||
|
||||
## 🎯 设计哲学
|
||||
|
||||
* **防御优先 (SafeMode)**:默认采用 `AndEraseKey` 范式,密钥构造即擦除原始明文,杜绝内存残留。
|
||||
* **性能巅峰 (FastMode)**:针对高并发场景(如 API 签名),提供对象缓存模式,显著降低 CPU 消耗。
|
||||
* **混合加解密**:原生支持非对称混合加密逻辑(ECDH + HKDF + AES),支持任意长度数据加密。
|
||||
* **混淆适配**:对称加密支持密钥自动截断,允许传入超长 buffer 以混淆内存特征。
|
||||
|
||||
## 🛠 API Reference
|
||||
|
||||
### 对称加密 (AES-CBC/GCM)
|
||||
- `func NewAESCBCAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
||||
- `func NewAESGCMAndEraseKey(key, iv []byte) (*Symmetric, error)`
|
||||
- `func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error)`
|
||||
- `func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error)`
|
||||
- `func (s *Symmetric) DecryptBytesN(data []byte) []byte` (静默解密)
|
||||
|
||||
### 非对称加密 (RSA/ECDSA/Ed25519/X25519)
|
||||
- `func NewRSAndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
||||
- `func NewECDSAndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
||||
- `func NewED25519AndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
||||
- `func NewX25519AndEraseKey(priv, pub []byte) (*Asymmetric, error)`
|
||||
- `func NewAsymmetricWithoutEraseKey(algo, priv, pub, fastMode) (*Asymmetric, error)`
|
||||
- `func (a *Asymmetric) Sign(data []byte, hash ...crypto.Hash) ([]byte, error)`
|
||||
- `func (a *Asymmetric) Verify(data, signature []byte, hash ...crypto.Hash) (bool, error)`
|
||||
- `func (a *Asymmetric) Encrypt(data []byte) ([]byte, error)` (混合加密)
|
||||
- `func (a *Asymmetric) Decrypt(data []byte) ([]byte, error)` (混合解密)
|
||||
|
||||
### 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 GenerateECDSAKeyPair(bitSize int) (priv, pub []byte, err error)`
|
||||
- `func GenerateEd25519KeyPair() (priv, pub []byte, err error)`
|
||||
- `func GenerateX25519KeyPair() (priv, pub []byte, err error)`
|
||||
|
||||
## 🚀 FastMode 性能模式
|
||||
在高并发服务端对接场景中,内存被恶意扫描的风险极低,但 CPU 压力巨大。
|
||||
**建议:** 使用 `NewAsymmetricWithoutEraseKey(..., true)`。
|
||||
* **效果**:缓存解析后的密钥对象。
|
||||
* **性能**:实测签名速度可提升数倍,相比默认模式能支持更高的 QPS。
|
||||
|
||||
## 📦 安装
|
||||
```bash
|
||||
go get apigo.cc/go/crypto
|
||||
```
|
||||
|
||||
38
TEST.md
Normal file
38
TEST.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Test Report: @go/crypto
|
||||
|
||||
## 📋 测试概览
|
||||
- **测试时间**: 2026-04-22
|
||||
- **测试环境**: darwin/amd64
|
||||
- **Go 版本**: 1.25.0
|
||||
|
||||
## ✅ 功能测试 (Functional Tests)
|
||||
| 场景 | 状态 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `TestRSA_AllModes` | PASS | 覆盖 RSA PSS/OAEP 以及 FastMode 缓存模式。 |
|
||||
| `TestECDSA_Hybrid` | PASS | 验证 ECDH + HKDF + AESGCM 混合加解密链路。 |
|
||||
| `TestEd25519` | PASS | 纯签名/验签逻辑验证。 |
|
||||
| `TestX25519_Hybrid` | PASS | 验证 X25519 混合加解密链路。 |
|
||||
| `TestSecurityErase` | PASS | **核心安全测试**:验证 `AndEraseKey` 调用后原始密钥内存确实被物理擦除。 |
|
||||
| `TestSymmetricObfuscation` | PASS | 验证传入 64 字节混淆密钥时,自动截断适配 32 字节 AES 密钥的逻辑。 |
|
||||
| `TestAnsiX923Padding` | PASS | 验证 ANSI X9.23 填充算法的一致性。 |
|
||||
| `TestConcurrentSafe` | PASS | 验证 `Symmetric` 对象在高并发环境下的数据隔离与安全性。 |
|
||||
| `TestHashCompatibility` | PASS | 确保封装的 Hash API 与标准库 MD5/SHA256/HMAC 结果 1:1 对齐。 |
|
||||
|
||||
## 🛡️ 鲁棒性防御 (Robustness)
|
||||
- **密钥混淆**:支持超长密钥输入以混淆内存特征,内部自动适配 16/24/32 字节核心密钥。
|
||||
- **故障静默**:`DecryptBytesN` 在填充或密文损坏时静默返回原始数据,防止业务因加密错误崩溃。
|
||||
- **哈希安全**:RSA/ECDSA 签名强制默认 SHA256,防止因哈希未指定导致的空指针 Panic。
|
||||
|
||||
## ⚡ 性能基准 (Benchmarks)
|
||||
| 算法类型 | 耗时 (ns/op) | 性能倍率 (对比 RSA) | 结论 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Ed25519 签名** | **~27938** | **50.0x** | **性能冠军**,极力推荐。 |
|
||||
| **ECDSA 签名** | **~54753** | **25.5x** | 现代 Web 标准,性能卓越。 |
|
||||
| **X25519 混合加密** | **~216035** | **6.5x** | 适合非对称大数据量加密。 |
|
||||
| **RSA-2048 签名** | **~1397766**| **1.0x** | **性能瓶颈**,仅建议用于兼容。 |
|
||||
| **AES-GCM** | **~4562** | - | 优于 CBC,首选对称算法。 |
|
||||
|
||||
> **首席架构师建议**:
|
||||
> 1. 云端高并发:优先 Ed25519 签名 + AES-GCM 对称加密。
|
||||
> 2. 传统兼容:使用 RSA-2048 + FastMode。
|
||||
> 3. 混合加密:大数据量直接用 X25519/ECDSA 的 `Encrypt` 方法。
|
||||
77
aes.go
Normal file
77
aes.go
Normal file
@ -0,0 +1,77 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
)
|
||||
|
||||
type AESCipher struct{ useGCM bool }
|
||||
|
||||
var (
|
||||
AESCBC = &AESCipher{useGCM: false}
|
||||
AESGCM = &AESCipher{useGCM: true}
|
||||
)
|
||||
|
||||
func NewAESCBC(safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error) {
|
||||
return NewSymmetric(AESCBC, safeKeyBuf, safeIvBuf)
|
||||
}
|
||||
|
||||
func NewAESCBCAndEraseKey(key, iv []byte) (*Symmetric, error) {
|
||||
return NewSymmetricAndEraseKey(AESCBC, key, iv)
|
||||
}
|
||||
|
||||
func NewAESCBCWithOutEraseKey(key, iv []byte) (*Symmetric, error) {
|
||||
return NewSymmetricWithOutEraseKey(AESCBC, key, iv)
|
||||
}
|
||||
|
||||
func NewAESGCM(safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error) {
|
||||
return NewSymmetric(AESGCM, safeKeyBuf, safeIvBuf)
|
||||
}
|
||||
|
||||
func NewAESGCMAndEraseKey(key, iv []byte) (*Symmetric, error) {
|
||||
return NewSymmetricAndEraseKey(AESGCM, key, iv)
|
||||
}
|
||||
|
||||
func NewAESGCMWithOutEraseKey(key, iv []byte) (*Symmetric, error) {
|
||||
return NewSymmetricWithOutEraseKey(AESGCM, key, iv)
|
||||
}
|
||||
|
||||
func (c *AESCipher) Encrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.useGCM {
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aesgcm.Seal(nil, iv[:aesgcm.NonceSize()], data, nil), nil
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
paddingData := Pkcs5Padding(data, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv[:blockSize])
|
||||
crypted := make([]byte, len(paddingData))
|
||||
blockMode.CryptBlocks(crypted, paddingData)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func (c *AESCipher) Decrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.useGCM {
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aesgcm.Open(nil, iv[:aesgcm.NonceSize()], data, nil)
|
||||
}
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
|
||||
origData := make([]byte, len(data))
|
||||
blockMode.CryptBlocks(origData, data)
|
||||
return Pkcs5UnPadding(origData), nil
|
||||
}
|
||||
149
asymmetric.go
Normal file
149
asymmetric.go
Normal file
@ -0,0 +1,149 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"runtime"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
)
|
||||
|
||||
// Asymmetric 封装非对称加密的生命周期与安全存储
|
||||
type Asymmetric struct {
|
||||
algorithm AsymmetricAlgorithm
|
||||
privateKeyBuf *safe.SafeBuf
|
||||
publicKeyBuf *safe.SafeBuf
|
||||
privCache any // FastMode 缓存
|
||||
pubCache any // FastMode 缓存
|
||||
}
|
||||
|
||||
// NewAsymmetric 基于已有的 SafeBuf 创建 Asymmetric
|
||||
func NewAsymmetric(algorithm AsymmetricAlgorithm, safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
|
||||
a := &Asymmetric{algorithm: algorithm, privateKeyBuf: safePrivateKeyBuf, publicKeyBuf: safePublicKeyBuf}
|
||||
runtime.SetFinalizer(a, func(obj *Asymmetric) { obj.Close() })
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// NewAsymmetricAndEraseKey 创建并自动擦除传入的密钥
|
||||
func NewAsymmetricAndEraseKey(algorithm AsymmetricAlgorithm, privateKey, publicKey []byte) (*Asymmetric, error) {
|
||||
if privateKey != nil {
|
||||
defer safe.ZeroMemory(privateKey)
|
||||
}
|
||||
if publicKey != nil {
|
||||
defer safe.ZeroMemory(publicKey)
|
||||
}
|
||||
return NewAsymmetricWithoutEraseKey(algorithm, privateKey, publicKey, false)
|
||||
}
|
||||
|
||||
// NewAsymmetricWithoutEraseKey 提供可选的 fastModeButIsNotSecure 以提高高性能场景下的解析性能
|
||||
func NewAsymmetricWithoutEraseKey(algorithm AsymmetricAlgorithm, privateKey, publicKey []byte, fastModeButIsNotSecure bool) (*Asymmetric, error) {
|
||||
a := &Asymmetric{algorithm: algorithm}
|
||||
var err error
|
||||
if privateKey != nil {
|
||||
if fastModeButIsNotSecure {
|
||||
if a.privCache, err = algorithm.ParsePrivateKey(privateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a.privateKeyBuf = safe.NewSafeBuf(privateKey)
|
||||
}
|
||||
}
|
||||
if publicKey != nil {
|
||||
if fastModeButIsNotSecure {
|
||||
if a.pubCache, err = algorithm.ParsePublicKey(publicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a.publicKeyBuf = safe.NewSafeBuf(publicKey)
|
||||
}
|
||||
}
|
||||
runtime.SetFinalizer(a, func(obj *Asymmetric) { obj.Close() })
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Close 销毁实例并擦除敏感数据
|
||||
func (a *Asymmetric) Close() {
|
||||
if a.privateKeyBuf != nil {
|
||||
a.privateKeyBuf.Close()
|
||||
a.privateKeyBuf = nil
|
||||
}
|
||||
if a.publicKeyBuf != nil {
|
||||
a.publicKeyBuf.Close()
|
||||
a.publicKeyBuf = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Sign 进行签名逻辑
|
||||
func (a *Asymmetric) Sign(data []byte, hash ...crypto.Hash) ([]byte, error) {
|
||||
if a.privCache != nil {
|
||||
return a.algorithm.Sign(a.privCache, data, hash...)
|
||||
}
|
||||
if a.privateKeyBuf == nil {
|
||||
return nil, ErrPrivKeyMissing
|
||||
}
|
||||
sp := a.privateKeyBuf.Open()
|
||||
defer sp.Close()
|
||||
privKey, err := a.algorithm.ParsePrivateKey(sp.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.algorithm.Sign(privKey, data, hash...)
|
||||
}
|
||||
|
||||
// Verify 进行验签逻辑
|
||||
func (a *Asymmetric) Verify(data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||
if a.pubCache != nil {
|
||||
return a.algorithm.Verify(a.pubCache, data, signature, hash...)
|
||||
}
|
||||
if a.publicKeyBuf == nil {
|
||||
return false, ErrPubKeyMissing
|
||||
}
|
||||
sp := a.publicKeyBuf.Open()
|
||||
defer sp.Close()
|
||||
pubKey, err := a.algorithm.ParsePublicKey(sp.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return a.algorithm.Verify(pubKey, data, signature, hash...)
|
||||
}
|
||||
|
||||
// Encrypt 使用公钥进行非对称加密
|
||||
func (a *Asymmetric) Encrypt(data []byte) ([]byte, error) {
|
||||
cipherAlgo, ok := a.algorithm.(AsymmetricCipherAlgorithm)
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNoEncrypt
|
||||
}
|
||||
if a.pubCache != nil {
|
||||
return cipherAlgo.Encrypt(a.pubCache, data)
|
||||
}
|
||||
if a.publicKeyBuf == nil {
|
||||
return nil, ErrPubKeyMissing
|
||||
}
|
||||
sp := a.publicKeyBuf.Open()
|
||||
defer sp.Close()
|
||||
pubKey, err := a.algorithm.ParsePublicKey(sp.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipherAlgo.Encrypt(pubKey, data)
|
||||
}
|
||||
|
||||
// Decrypt 使用私钥进行非对称解密
|
||||
func (a *Asymmetric) Decrypt(data []byte) ([]byte, error) {
|
||||
cipherAlgo, ok := a.algorithm.(AsymmetricCipherAlgorithm)
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNoDecrypt
|
||||
}
|
||||
if a.privCache != nil {
|
||||
return cipherAlgo.Decrypt(a.privCache, data)
|
||||
}
|
||||
if a.privateKeyBuf == nil {
|
||||
return nil, ErrPrivKeyMissing
|
||||
}
|
||||
sp := a.privateKeyBuf.Open()
|
||||
defer sp.Close()
|
||||
privKey, err := a.algorithm.ParsePrivateKey(sp.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipherAlgo.Decrypt(privKey, data)
|
||||
}
|
||||
82
asymmetric_test.go
Normal file
82
asymmetric_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/go/crypto"
|
||||
)
|
||||
|
||||
func TestRSA_AllModes(t *testing.T) {
|
||||
priv, pub, _ := crypto.GenerateRSAKeyPair(2048)
|
||||
data := []byte("rsa multi-mode test")
|
||||
|
||||
// 1. PSS (Default)
|
||||
a, _ := crypto.NewRSAWithOutEraseKey(priv, pub)
|
||||
sig, _ := a.Sign(data)
|
||||
if ok, _ := a.Verify(data, sig); !ok { t.Error("RSA PSS Sign failed") }
|
||||
enc, _ := a.Encrypt(data)
|
||||
dec, _ := a.Decrypt(enc)
|
||||
if !bytes.Equal(data, dec) { t.Error("RSA OAEP Encrypt failed") }
|
||||
|
||||
// 2. FastMode
|
||||
fastA, _ := crypto.NewAsymmetricWithoutEraseKey(&crypto.RSAAlgorithm{IsPSS: true, IsOAEP: true}, priv, pub, true)
|
||||
sig2, _ := fastA.Sign(data)
|
||||
if ok, _ := fastA.Verify(data, sig2); !ok { t.Error("RSA FastMode failed") }
|
||||
}
|
||||
|
||||
func TestECDSA_Hybrid(t *testing.T) {
|
||||
priv, pub, _ := crypto.GenerateECDSAKeyPair(256)
|
||||
data := []byte("ecdsa hybrid test")
|
||||
|
||||
a, _ := crypto.NewECDSAndEraseKey(append([]byte(nil), priv...), append([]byte(nil), pub...))
|
||||
|
||||
// Test Hybrid Encrypt (ECDH + AESGCM)
|
||||
enc, err := a.Encrypt(data)
|
||||
if err != nil { t.Fatal(err) }
|
||||
|
||||
dec, err := a.Decrypt(enc)
|
||||
if err != nil { t.Fatal(err) }
|
||||
|
||||
if !bytes.Equal(data, dec) { t.Error("ECDSA Hybrid roundtrip failed") }
|
||||
}
|
||||
|
||||
func TestEd25519_Simple(t *testing.T) {
|
||||
priv, pub, _ := crypto.GenerateEd25519KeyPair()
|
||||
data := []byte("ed25519 sign test")
|
||||
|
||||
a, _ := crypto.NewED25519AndEraseKey(priv, pub)
|
||||
sig, _ := a.Sign(data)
|
||||
if ok, _ := a.Verify(data, sig); !ok { t.Error("Ed25519 failed") }
|
||||
|
||||
// Test Negative: Algorithm doesn't support encryption
|
||||
if _, err := a.Encrypt(data); err == nil {
|
||||
t.Error("Ed25519 should NOT support encryption")
|
||||
}
|
||||
}
|
||||
|
||||
func TestX25519_Hybrid(t *testing.T) {
|
||||
priv, pub, _ := crypto.GenerateX25519KeyPair()
|
||||
data := []byte("x25519 data")
|
||||
|
||||
a, _ := crypto.NewX25519WithOutEraseKey(priv, pub)
|
||||
enc, _ := a.Encrypt(data)
|
||||
dec, _ := a.Decrypt(enc)
|
||||
if !bytes.Equal(data, dec) { t.Error("X25519 roundtrip failed") }
|
||||
}
|
||||
|
||||
func TestAsymmetricErrors(t *testing.T) {
|
||||
_, pub, _ := crypto.GenerateRSAKeyPair(2048)
|
||||
|
||||
// Only public key
|
||||
a, _ := crypto.NewRSAWithOutEraseKey(nil, pub)
|
||||
if _, err := a.Sign([]byte("x")); err == nil {
|
||||
t.Error("Should fail to sign without private key")
|
||||
}
|
||||
|
||||
// Missing both
|
||||
aEmpty, _ := crypto.NewRSAWithOutEraseKey(nil, nil)
|
||||
if _, err := aEmpty.Encrypt([]byte("x")); err == nil {
|
||||
t.Error("Should fail to encrypt without public key")
|
||||
}
|
||||
}
|
||||
80
crypto.go
Normal file
80
crypto.go
Normal file
@ -0,0 +1,80 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// SymmetricCipher 对称加密算法引擎接口
|
||||
type SymmetricCipher interface {
|
||||
Encrypt(data []byte, key []byte, iv []byte) ([]byte, error)
|
||||
Decrypt(data []byte, key []byte, iv []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// AsymmetricAlgorithm 非对称算法基础接口 (签名/验签)
|
||||
type AsymmetricAlgorithm interface {
|
||||
ParsePrivateKey(der []byte) (any, error)
|
||||
ParsePublicKey(der []byte) (any, error)
|
||||
Sign(privateKey any, data []byte, hash ...crypto.Hash) ([]byte, error)
|
||||
Verify(publicKey any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error)
|
||||
}
|
||||
|
||||
// AsymmetricCipherAlgorithm 非对称加解密能力接口
|
||||
type AsymmetricCipherAlgorithm interface {
|
||||
Encrypt(publicKey any, data []byte) ([]byte, error)
|
||||
Decrypt(privateKey any, data []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// 通用错误
|
||||
var (
|
||||
ErrKeySize = errors.New("invalid key size")
|
||||
ErrNotImplemented = errors.New("algorithm not implemented")
|
||||
ErrAlgorithmNoEncrypt = errors.New("the current algorithm does not support encryption")
|
||||
ErrAlgorithmNoDecrypt = errors.New("the current algorithm does not support decryption")
|
||||
ErrPrivKeyMissing = errors.New("private key is not set")
|
||||
ErrPubKeyMissing = errors.New("public key is not set")
|
||||
)
|
||||
|
||||
// Pkcs5Padding 填充逻辑 (实际上是 PKCS#7,广泛兼容)
|
||||
func Pkcs5Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padtext := make([]byte, padding)
|
||||
for i := range padtext {
|
||||
padtext[i] = byte(padding)
|
||||
}
|
||||
return append(data, padtext...)
|
||||
}
|
||||
|
||||
// Pkcs5UnPadding 去除填充逻辑
|
||||
func Pkcs5UnPadding(data []byte) []byte {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
unpadding := int(data[length-1])
|
||||
if unpadding > length || unpadding == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[:length-unpadding]
|
||||
}
|
||||
|
||||
// AnsiX923Padding 填充逻辑 (中间补 0,末尾补长度)
|
||||
func AnsiX923Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padtext := make([]byte, padding)
|
||||
padtext[len(padtext)-1] = byte(padding) // 仅在末尾存长度
|
||||
return append(data, padtext...)
|
||||
}
|
||||
|
||||
// AnsiX923UnPadding 去除 ANSI X9.23 填充
|
||||
func AnsiX923UnPadding(data []byte) []byte {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
unpadding := int(data[length-1])
|
||||
if unpadding > length || unpadding == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[:length-unpadding]
|
||||
}
|
||||
167
crypto_test.go
Normal file
167
crypto_test.go
Normal file
@ -0,0 +1,167 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
lcrypto "apigo.cc/go/crypto"
|
||||
)
|
||||
|
||||
func TestSecurityErase(t *testing.T) {
|
||||
key := []byte("1234567890123456")
|
||||
iv := []byte("1234567890123456")
|
||||
|
||||
// 测试 AndEraseKey 确实擦除了原始内存
|
||||
lcrypto.NewAESCBCAndEraseKey(key, iv)
|
||||
|
||||
if bytes.Equal(key, []byte("1234567890123456")) {
|
||||
t.Fatal("Security Failure: Key was NOT erased")
|
||||
}
|
||||
if bytes.Equal(iv, []byte("1234567890123456")) {
|
||||
t.Fatal("Security Failure: IV was NOT erased")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESExhaustive(t *testing.T) {
|
||||
key := []byte("1234567890123456")
|
||||
iv := []byte("1234567890123456")
|
||||
data := []byte("hello aes exhaustive testing")
|
||||
|
||||
aes, _ := lcrypto.NewAESCBCWithOutEraseKey(key, iv)
|
||||
|
||||
// 1. 正常加解密
|
||||
enc, _ := aes.EncryptBytes(data)
|
||||
dec, _ := aes.DecryptBytes(enc)
|
||||
if !bytes.Equal(data, dec) { t.Fatal("CBC roundtrip failed") }
|
||||
|
||||
// 2. 损坏密文测试 (应能正常处理 Pkcs5UnPadding 失败)
|
||||
damaged := append([]byte(nil), enc...)
|
||||
damaged[len(damaged)-1] ^= 0xFF
|
||||
decN := aes.DecryptBytesN(damaged)
|
||||
if bytes.Equal(decN, data) {
|
||||
t.Fatal("Security Breach: Damaged ciphertext still returned correct plaintext")
|
||||
}
|
||||
|
||||
// 3. 非法 Key 长度测试
|
||||
_, err := lcrypto.NewAESCBCWithOutEraseKey([]byte("too short"), iv)
|
||||
if err == nil {
|
||||
t.Fatal("Edge failure: Accepted invalid key size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsymmetricExhaustive(t *testing.T) {
|
||||
// RSA OAEP
|
||||
priv, pub, _ := lcrypto.GenerateRSAKeyPair(2048)
|
||||
rsa, _ := lcrypto.NewRSAndEraseKey(priv, pub)
|
||||
data := []byte("rsa test data")
|
||||
|
||||
enc, _ := rsa.Encrypt(data)
|
||||
dec, _ := rsa.Decrypt(enc)
|
||||
if !bytes.Equal(data, dec) { t.Fatal("RSA encryption failed") }
|
||||
|
||||
// ECDSA Hybrid (ECDH + AESGCM)
|
||||
priv2, pub2, _ := lcrypto.GenerateECDSAKeyPair(256)
|
||||
ecdsa, _ := lcrypto.NewECDSAndEraseKey(priv2, pub2)
|
||||
enc2, _ := ecdsa.Encrypt(data)
|
||||
dec2, _ := ecdsa.Decrypt(enc2)
|
||||
if !bytes.Equal(data, dec2) { t.Fatal("ECDSA Hybrid encryption failed") }
|
||||
}
|
||||
|
||||
func TestAnsiX923Padding(t *testing.T) {
|
||||
data := []byte("ansi test")
|
||||
blockSize := 16
|
||||
padded := lcrypto.AnsiX923Padding(data, blockSize)
|
||||
if len(padded) != blockSize {
|
||||
t.Fatalf("Padding length mismatch: %d", len(padded))
|
||||
}
|
||||
if padded[len(padded)-1] != byte(blockSize-len(data)) {
|
||||
t.Fatal("Padding length marker incorrect")
|
||||
}
|
||||
unpadded := lcrypto.AnsiX923UnPadding(padded)
|
||||
if !bytes.Equal(data, unpadded) {
|
||||
t.Fatal("ANSI X9.23 roundtrip failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentSafe(t *testing.T) {
|
||||
key := []byte("1234567890123456")
|
||||
iv := []byte("1234567890123456")
|
||||
aes, _ := lcrypto.NewAESGCMWithOutEraseKey(key, iv)
|
||||
data := []byte("concurrent data")
|
||||
|
||||
done := make(chan bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
enc, _ := aes.EncryptBytes(data)
|
||||
dec, _ := aes.DecryptBytes(enc)
|
||||
if !bytes.Equal(data, dec) {
|
||||
t.Error("Concurrency breach detected")
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
for i := 0; i < 100; i++ { <-done }
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
func BenchmarkAES_GCM(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
iv := make([]byte, 12)
|
||||
data := make([]byte, 1024)
|
||||
aes, _ := lcrypto.NewAESGCMWithOutEraseKey(key, iv)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = aes.EncryptBytes(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAES_CBC(b *testing.B) {
|
||||
key := make([]byte, 32)
|
||||
iv := make([]byte, 16)
|
||||
data := make([]byte, 1024)
|
||||
aes, _ := lcrypto.NewAESCBCWithOutEraseKey(key, iv)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = aes.EncryptBytes(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRSA_Sign(b *testing.B) {
|
||||
priv, pub, _ := lcrypto.GenerateRSAKeyPair(2048)
|
||||
rsa, _ := lcrypto.NewRSAndEraseKey(priv, pub)
|
||||
data := []byte("performance test")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = rsa.Sign(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkECDSA_Sign(b *testing.B) {
|
||||
priv, pub, _ := lcrypto.GenerateECDSAKeyPair(256)
|
||||
ecdsa, _ := lcrypto.NewECDSAndEraseKey(priv, pub)
|
||||
data := []byte("performance test")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = ecdsa.Sign(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEd25519_Sign(b *testing.B) {
|
||||
priv, pub, _ := lcrypto.GenerateEd25519KeyPair()
|
||||
ed, _ := lcrypto.NewED25519AndEraseKey(priv, pub)
|
||||
data := []byte("performance test")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = ed.Sign(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkX25519_Encrypt(b *testing.B) {
|
||||
priv, pub, _ := lcrypto.GenerateX25519KeyPair()
|
||||
x, _ := lcrypto.NewX25519AndEraseKey(priv, pub)
|
||||
data := []byte("performance test data 1kb")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = x.Encrypt(data)
|
||||
}
|
||||
}
|
||||
198
ecdsa.go
Normal file
198
ecdsa.go
Normal file
@ -0,0 +1,198 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type ECDSAAlgorithm struct {
|
||||
UseGCM bool
|
||||
KdfInfo []byte
|
||||
KdfSalt []byte
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
ECDSAGCM = &ECDSAAlgorithm{UseGCM: true, Hash: crypto.SHA256}
|
||||
ECDSACBC = &ECDSAAlgorithm{UseGCM: false, Hash: crypto.SHA256}
|
||||
)
|
||||
|
||||
func NewECDSA(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
|
||||
return NewAsymmetric(ECDSAGCM, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewECDSAndEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricAndEraseKey(ECDSAGCM, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewECDSAWithOutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricWithoutEraseKey(ECDSAGCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||
}
|
||||
|
||||
func NewECDSAAlgorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *ECDSAAlgorithm {
|
||||
return &ECDSAAlgorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
||||
}
|
||||
|
||||
func GenerateECDSAKeyPair(bitSize int) ([]byte, []byte, error) {
|
||||
var curve elliptic.Curve
|
||||
switch bitSize {
|
||||
case 256:
|
||||
curve = elliptic.P256()
|
||||
case 384:
|
||||
curve = elliptic.P384()
|
||||
default:
|
||||
curve = elliptic.P521()
|
||||
}
|
||||
|
||||
priKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
privateKey, err := x509.MarshalECPrivateKey(priKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
publicKey, err := x509.MarshalPKIXPublicKey(&priKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return privateKey, publicKey, nil
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) ParsePrivateKey(der []byte) (any, error) {
|
||||
return x509.ParseECPrivateKey(der)
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) ParsePublicKey(der []byte) (any, error) {
|
||||
pubKeyAny, err := x509.ParsePKIXPublicKey(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, ok := pubKeyAny.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not an ECDSA public key")
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) Sign(privateKeyObj any, data []byte, hash ...crypto.Hash) ([]byte, error) {
|
||||
privKey, ok := privateKeyObj.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key")
|
||||
}
|
||||
hFunc := e.Hash
|
||||
if len(hash) > 0 {
|
||||
hFunc = hash[0]
|
||||
}
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hasher := hFunc.New()
|
||||
hasher.Write(data)
|
||||
return ecdsa.SignASN1(rand.Reader, privKey, hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) Verify(publicKeyObj any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||
pubKey, ok := publicKeyObj.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return false, errors.New("invalid public key")
|
||||
}
|
||||
hFunc := e.Hash
|
||||
if len(hash) > 0 {
|
||||
hFunc = hash[0]
|
||||
}
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hasher := hFunc.New()
|
||||
hasher.Write(data)
|
||||
return ecdsa.VerifyASN1(pubKey, hasher.Sum(nil), signature), nil
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) Encrypt(publicKeyObj any, data []byte) ([]byte, error) {
|
||||
ecdsaPub, ok := publicKeyObj.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid public key type for ECDSA")
|
||||
}
|
||||
ecdhPub, err := ecdsaPub.ECDH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ephemeralPriv, err := ecdhPub.Curve().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecret, err := ephemeralPriv.ECDH(ecdhPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(sharedSecret)
|
||||
hkdfReader := hkdf.New(e.Hash.New, sharedSecret, e.KdfSalt, e.KdfInfo)
|
||||
aesKey := make([]byte, 32)
|
||||
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(aesKey)
|
||||
cipherAlgo := &AESCipher{useGCM: e.UseGCM}
|
||||
ivLen := 16
|
||||
if e.UseGCM {
|
||||
ivLen = 12
|
||||
}
|
||||
iv := safe.MakeSafeToken(ivLen)
|
||||
cipherText, err := cipherAlgo.Encrypt(data, aesKey, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubBytes := ephemeralPriv.PublicKey().Bytes()
|
||||
out := make([]byte, 0, len(pubBytes)+len(iv)+len(cipherText))
|
||||
out = append(out, pubBytes...)
|
||||
out = append(out, iv...)
|
||||
out = append(out, cipherText...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (e *ECDSAAlgorithm) Decrypt(privateKeyObj any, data []byte) ([]byte, error) {
|
||||
ecdsaPriv, ok := privateKeyObj.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type for ECDSA")
|
||||
}
|
||||
ecdhPriv, err := ecdsaPriv.ECDH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeyLen := len(ecdhPriv.PublicKey().Bytes())
|
||||
ivLen := 16
|
||||
if e.UseGCM {
|
||||
ivLen = 12
|
||||
}
|
||||
if len(data) < pubKeyLen+ivLen {
|
||||
return nil, errors.New("invalid ciphertext package size")
|
||||
}
|
||||
ephemeralPubBytes := data[:pubKeyLen]
|
||||
iv := data[pubKeyLen : pubKeyLen+ivLen]
|
||||
cipherText := data[pubKeyLen+ivLen:]
|
||||
ephemeralPub, err := ecdhPriv.Curve().NewPublicKey(ephemeralPubBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecret, err := ecdhPriv.ECDH(ephemeralPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(sharedSecret)
|
||||
hkdfReader := hkdf.New(e.Hash.New, sharedSecret, e.KdfSalt, e.KdfInfo)
|
||||
aesKey := make([]byte, 32)
|
||||
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(aesKey)
|
||||
cipherAlgo := &AESCipher{useGCM: e.UseGCM}
|
||||
return cipherAlgo.Decrypt(cipherText, aesKey, iv)
|
||||
}
|
||||
62
ed25519.go
Normal file
62
ed25519.go
Normal file
@ -0,0 +1,62 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
)
|
||||
|
||||
type Ed25519Algorithm struct{}
|
||||
|
||||
var ED25519 = &Ed25519Algorithm{}
|
||||
|
||||
func NewED25519(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
|
||||
return NewAsymmetric(ED25519, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewED25519AndEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricAndEraseKey(ED25519, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewED25519WithOutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricWithoutEraseKey(ED25519, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||
}
|
||||
|
||||
func GenerateEd25519KeyPair() ([]byte, []byte, error) {
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
func (e *Ed25519Algorithm) ParsePrivateKey(der []byte) (any, error) {
|
||||
if len(der) != ed25519.PrivateKeySize {
|
||||
return nil, errors.New("invalid Ed25519 private key size")
|
||||
}
|
||||
return ed25519.PrivateKey(der), nil
|
||||
}
|
||||
|
||||
func (e *Ed25519Algorithm) ParsePublicKey(der []byte) (any, error) {
|
||||
if len(der) != ed25519.PublicKeySize {
|
||||
return nil, errors.New("invalid Ed25519 public key size")
|
||||
}
|
||||
return ed25519.PublicKey(der), nil
|
||||
}
|
||||
|
||||
func (e *Ed25519Algorithm) Sign(privateKeyObj any, data []byte, hash ...crypto.Hash) ([]byte, error) {
|
||||
privKey, ok := privateKeyObj.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type for Ed25519")
|
||||
}
|
||||
return ed25519.Sign(privKey, data), nil
|
||||
}
|
||||
|
||||
func (e *Ed25519Algorithm) Verify(publicKeyObj any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||
pubKey, ok := publicKeyObj.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return false, errors.New("invalid public key type for Ed25519")
|
||||
}
|
||||
return ed25519.Verify(pubKey, data, signature), nil
|
||||
}
|
||||
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module apigo.cc/go/crypto
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/encoding v1.0.0
|
||||
apigo.cc/go/safe v1.0.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
)
|
||||
|
||||
require (
|
||||
apigo.cc/go/rand v1.0.2 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
)
|
||||
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
||||
apigo.cc/go/encoding v1.0.0 h1:NFb658uGqyh8hKKK9EYqQ6ybmcIOslV57Tdqvd0+z6Y=
|
||||
apigo.cc/go/encoding v1.0.0/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI=
|
||||
apigo.cc/go/rand v1.0.2 h1:dJsm607EynJOAoukTvarrUyvLtBF7pi27A99vw2+i78=
|
||||
apigo.cc/go/rand v1.0.2/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||
apigo.cc/go/safe v1.0.0 h1:zgZ83EFwJM5tpMbOxnZG9NpWmtYAZROgbDW80k+vt2U=
|
||||
apigo.cc/go/safe v1.0.0/go.mod h1:7hXqV2irGeggfnZWO5E1+WvFeCLznJbDQMGjEjUpJAA=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
115
hash.go
Normal file
115
hash.go
Normal file
@ -0,0 +1,115 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
|
||||
"apigo.cc/go/encoding"
|
||||
)
|
||||
|
||||
// MD5 返回数据的原始 MD5 字节
|
||||
func MD5(data ...[]byte) []byte {
|
||||
h := md5.New()
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// MD5ToHex 返回 MD5 的 Hex 字符串
|
||||
func MD5ToHex(data []byte) string {
|
||||
return encoding.HexToString(MD5(data))
|
||||
}
|
||||
|
||||
// MD5ToBase64 返回 MD5 的 Base64 字符串
|
||||
func MD5ToBase64(data []byte) string {
|
||||
return encoding.Base64ToString(MD5(data))
|
||||
}
|
||||
|
||||
// MD5ToUrlBase64 返回 MD5 的 URL 安全 Base64 字符串
|
||||
func MD5ToUrlBase64(data []byte) string {
|
||||
return encoding.UrlBase64ToString(MD5(data))
|
||||
}
|
||||
|
||||
// Sha1 系列
|
||||
func Sha1(data ...[]byte) []byte {
|
||||
h := sha1.New()
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func Sha1ToHex(data []byte) string { return encoding.HexToString(Sha1(data)) }
|
||||
func Sha1ToBase64(data []byte) string { return encoding.Base64ToString(Sha1(data)) }
|
||||
func Sha1ToUrlBase64(data []byte) string { return encoding.UrlBase64ToString(Sha1(data)) }
|
||||
|
||||
// Sha256 系列
|
||||
func Sha256(data ...[]byte) []byte {
|
||||
h := sha256.New()
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func Sha256ToHex(data []byte) string { return encoding.HexToString(Sha256(data)) }
|
||||
func Sha256ToBase64(data []byte) string { return encoding.Base64ToString(Sha256(data)) }
|
||||
func Sha256ToUrlBase64(data []byte) string { return encoding.UrlBase64ToString(Sha256(data)) }
|
||||
|
||||
// Sha512 系列
|
||||
func Sha512(data ...[]byte) []byte {
|
||||
h := sha512.New()
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func Sha512ToHex(data []byte) string { return encoding.HexToString(Sha512(data)) }
|
||||
func Sha512ToBase64(data []byte) string { return encoding.Base64ToString(Sha512(data)) }
|
||||
func Sha512ToUrlBase64(data []byte) string { return encoding.UrlBase64ToString(Sha512(data)) }
|
||||
|
||||
// HMAC 系列
|
||||
func HmacMD5(key []byte, data ...[]byte) []byte {
|
||||
h := hmac.New(md5.New, key)
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func HmacSha1(key []byte, data ...[]byte) []byte {
|
||||
h := hmac.New(sha1.New, key)
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func HmacSha256(key []byte, data ...[]byte) []byte {
|
||||
h := hmac.New(sha256.New, key)
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func HmacSha512(key []byte, data ...[]byte) []byte {
|
||||
h := hmac.New(sha512.New, key)
|
||||
for _, v := range data {
|
||||
h.Write(v)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// MakeToken 生成随机令牌
|
||||
func MakeToken(size int) []byte {
|
||||
token := make([]byte, size)
|
||||
rand.Read(token)
|
||||
return token
|
||||
}
|
||||
35
hash_test.go
Normal file
35
hash_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/go/crypto"
|
||||
)
|
||||
|
||||
func TestHashCompatibility(t *testing.T) {
|
||||
data := []byte("hello world")
|
||||
|
||||
// MD5
|
||||
h1 := md5.Sum(data)
|
||||
if !bytes.Equal(crypto.MD5(data), h1[:]) { t.Error("MD5 mismatch") }
|
||||
|
||||
// SHA256
|
||||
h2 := sha256.Sum256(data)
|
||||
if !bytes.Equal(crypto.Sha256(data), h2[:]) { t.Error("Sha256 mismatch") }
|
||||
|
||||
// HMAC
|
||||
key := []byte("key")
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write(data)
|
||||
if !bytes.Equal(crypto.HmacSha256(key, data), mac.Sum(nil)) { t.Error("HmacSha256 mismatch") }
|
||||
}
|
||||
|
||||
func TestHashString(t *testing.T) {
|
||||
s := []byte("hello")
|
||||
if crypto.MD5ToHex(s) == "" { t.Error("MD5ToHex empty") }
|
||||
if crypto.Sha256ToBase64(s) == "" { t.Error("Sha256ToBase64 empty") }
|
||||
}
|
||||
147
rsa.go
Normal file
147
rsa.go
Normal file
@ -0,0 +1,147 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
)
|
||||
|
||||
type RSAAlgorithm struct {
|
||||
IsPSS bool
|
||||
IsOAEP bool
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
RSA = &RSAAlgorithm{IsPSS: true, IsOAEP: true, Hash: crypto.SHA256}
|
||||
// Deprecated: RSAPKCS1v15 is not recommended.
|
||||
RSAPKCS1v15 = &RSAAlgorithm{IsPSS: false, IsOAEP: false, Hash: crypto.SHA256}
|
||||
)
|
||||
|
||||
func NewRSA(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
|
||||
return NewAsymmetric(RSA, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewRSAndEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricAndEraseKey(RSA, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewRSAWithOutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricWithoutEraseKey(RSA, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||
}
|
||||
|
||||
func GenerateRSAKeyPair(bitSize int) ([]byte, []byte, error) {
|
||||
if bitSize < 2048 {
|
||||
bitSize = 2048
|
||||
}
|
||||
priKey, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
privateKey, err := x509.MarshalPKCS8PrivateKey(priKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
publicKey, err := x509.MarshalPKIXPublicKey(&priKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return privateKey, publicKey, nil
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) ParsePrivateKey(der []byte) (any, error) {
|
||||
keyAny, err := x509.ParsePKCS8PrivateKey(der)
|
||||
if err != nil {
|
||||
keyAny, err = x509.ParsePKCS1PrivateKey(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
privKey, ok := keyAny.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not an RSA private key")
|
||||
}
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) ParsePublicKey(der []byte) (any, error) {
|
||||
pubKeyAny, err := x509.ParsePKIXPublicKey(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, ok := pubKeyAny.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not an RSA public key")
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) Sign(privateKeyObj any, data []byte, hash ...crypto.Hash) ([]byte, error) {
|
||||
privKey, ok := privateKeyObj.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type for RSA")
|
||||
}
|
||||
hFunc := r.Hash
|
||||
if len(hash) > 0 {
|
||||
hFunc = hash[0]
|
||||
}
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hasher := hFunc.New()
|
||||
hasher.Write(data)
|
||||
hashed := hasher.Sum(nil)
|
||||
if r.IsPSS {
|
||||
return rsa.SignPSS(rand.Reader, privKey, hFunc, hashed, nil)
|
||||
}
|
||||
return rsa.SignPKCS1v15(rand.Reader, privKey, hFunc, hashed)
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) Verify(publicKeyObj any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||
pubKey, ok := publicKeyObj.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return false, errors.New("invalid public key type for RSA")
|
||||
}
|
||||
hFunc := r.Hash
|
||||
if len(hash) > 0 {
|
||||
hFunc = hash[0]
|
||||
}
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hasher := hFunc.New()
|
||||
hasher.Write(data)
|
||||
hashed := hasher.Sum(nil)
|
||||
var err error
|
||||
if r.IsPSS {
|
||||
err = rsa.VerifyPSS(pubKey, hFunc, hashed, signature, nil)
|
||||
} else {
|
||||
err = rsa.VerifyPKCS1v15(pubKey, hFunc, hashed, signature)
|
||||
}
|
||||
return err == nil, nil
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) Encrypt(publicKeyObj any, data []byte) ([]byte, error) {
|
||||
pubKey, ok := publicKeyObj.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid public key type for RSA")
|
||||
}
|
||||
if r.IsOAEP {
|
||||
return rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, data, nil)
|
||||
}
|
||||
return rsa.EncryptPKCS1v15(rand.Reader, pubKey, data)
|
||||
}
|
||||
|
||||
func (r *RSAAlgorithm) Decrypt(privateKeyObj any, data []byte) ([]byte, error) {
|
||||
privKey, ok := privateKeyObj.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type for RSA")
|
||||
}
|
||||
if r.IsOAEP {
|
||||
return rsa.DecryptOAEP(sha256.New(), rand.Reader, privKey, data, nil)
|
||||
}
|
||||
return rsa.DecryptPKCS1v15(rand.Reader, privKey, data)
|
||||
}
|
||||
104
symmetric.go
Normal file
104
symmetric.go
Normal file
@ -0,0 +1,104 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
)
|
||||
|
||||
// Symmetric 封装对称加密的生命周期与安全存储
|
||||
type Symmetric struct {
|
||||
cipher SymmetricCipher
|
||||
key *safe.SafeBuf
|
||||
iv *safe.SafeBuf
|
||||
}
|
||||
|
||||
// NewSymmetric 基于已有的 SafeBuf 创建 Symmetric
|
||||
func NewSymmetric(cipher SymmetricCipher, safeKeyBuf, safeIvBuf *safe.SafeBuf) (*Symmetric, error) {
|
||||
s := &Symmetric{cipher: cipher, key: safeKeyBuf, iv: safeIvBuf}
|
||||
runtime.SetFinalizer(s, func(obj *Symmetric) { obj.Close() })
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewSymmetricAndEraseKey 创建并自动擦除传入的密钥与 IV
|
||||
func NewSymmetricAndEraseKey(cipher SymmetricCipher, key, iv []byte) (*Symmetric, error) {
|
||||
defer safe.ZeroMemory(key)
|
||||
defer safe.ZeroMemory(iv)
|
||||
return NewSymmetricWithOutEraseKey(cipher, key, iv)
|
||||
}
|
||||
|
||||
// NewSymmetricWithOutEraseKey 创建但不擦除传入的密钥与 IV,支持密钥长度自动适配(混淆防御)
|
||||
func NewSymmetricWithOutEraseKey(cipher SymmetricCipher, key, iv []byte) (*Symmetric, error) {
|
||||
keySize := 16
|
||||
if len(key) >= 32 {
|
||||
keySize = 32
|
||||
} else if len(key) >= 24 {
|
||||
keySize = 24
|
||||
} else if len(key) < 16 {
|
||||
return nil, errors.New("key size is too short, at least 16 bytes required")
|
||||
}
|
||||
|
||||
// 自动适配长度,允许传入超长 buffer 以混淆特征
|
||||
s := &Symmetric{
|
||||
cipher: cipher,
|
||||
key: safe.NewSafeBuf(key[:keySize]),
|
||||
iv: safe.NewSafeBuf(iv),
|
||||
}
|
||||
runtime.SetFinalizer(s, func(obj *Symmetric) { obj.Close() })
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Close 销毁加密实例并擦除密钥
|
||||
func (s *Symmetric) Close() {
|
||||
if s.key != nil {
|
||||
s.key.Close()
|
||||
}
|
||||
if s.iv != nil {
|
||||
s.iv.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt 使用 SafeBuf 传入明文进行加密
|
||||
func (s *Symmetric) Encrypt(safeBuf *safe.SafeBuf) ([]byte, error) {
|
||||
buf := safeBuf.Open()
|
||||
defer buf.Close()
|
||||
return s.EncryptBytes(buf.Data)
|
||||
}
|
||||
|
||||
// EncryptBytes 使用字节切片传入明文进行加密
|
||||
func (s *Symmetric) EncryptBytes(data []byte) ([]byte, error) {
|
||||
key := s.key.Open()
|
||||
defer key.Close()
|
||||
iv := s.iv.Open()
|
||||
defer iv.Close()
|
||||
return s.cipher.Encrypt(data, key.Data, iv.Data)
|
||||
}
|
||||
|
||||
// Decrypt 进行解密并返回一个受保护的 SafeBuf
|
||||
func (s *Symmetric) Decrypt(data []byte) (*safe.SafeBuf, error) {
|
||||
buf, err := s.DecryptBytes(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(buf)
|
||||
return safe.NewSafeBuf(buf), nil
|
||||
}
|
||||
|
||||
// DecryptBytes 进行解密并返回原始明文字节
|
||||
func (s *Symmetric) DecryptBytes(data []byte) ([]byte, error) {
|
||||
key := s.key.Open()
|
||||
defer key.Close()
|
||||
iv := s.iv.Open()
|
||||
defer iv.Close()
|
||||
return s.cipher.Decrypt(data, key.Data, iv.Data)
|
||||
}
|
||||
|
||||
// DecryptBytesN 解密失败时返回原始数据 (静默解密)
|
||||
func (s *Symmetric) DecryptBytesN(data []byte) []byte {
|
||||
r, err := s.DecryptBytes(data)
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
return r
|
||||
}
|
||||
61
symmetric_test.go
Normal file
61
symmetric_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/go/crypto"
|
||||
)
|
||||
|
||||
func TestSymmetricObfuscation(t *testing.T) {
|
||||
// 传入 64 字节密钥,应自动截断为 32 字节使用
|
||||
longKey := bytes.Repeat([]byte{0x01}, 64)
|
||||
iv := bytes.Repeat([]byte{0x02}, 16)
|
||||
data := []byte("secret data")
|
||||
|
||||
aes, err := crypto.NewAESGCMWithOutEraseKey(longKey, iv)
|
||||
if err != nil { t.Fatal(err) }
|
||||
|
||||
enc, err := aes.EncryptBytes(data)
|
||||
if err != nil { t.Fatal(err) }
|
||||
|
||||
dec, err := aes.DecryptBytes(enc)
|
||||
if err != nil { t.Fatal(err) }
|
||||
|
||||
if !bytes.Equal(data, dec) { t.Error("Decryption failed with long key") }
|
||||
}
|
||||
|
||||
func TestSymmetricPadding(t *testing.T) {
|
||||
key := []byte("1234567890123456")
|
||||
iv := []byte("1234567890123456")
|
||||
data := []byte("test padding data")
|
||||
|
||||
// PKCS5 (Default)
|
||||
aes, _ := crypto.NewAESCBCWithOutEraseKey(key, iv)
|
||||
enc, _ := aes.EncryptBytes(data)
|
||||
dec, _ := aes.DecryptBytes(enc)
|
||||
if !bytes.Equal(data, dec) { t.Error("PKCS5 roundtrip failed") }
|
||||
|
||||
// 模拟损坏密文导致填充错误
|
||||
damaged := append([]byte(nil), enc...)
|
||||
damaged[len(damaged)-1] ^= 0xFF
|
||||
if d := aes.DecryptBytesN(damaged); bytes.Equal(d, data) {
|
||||
t.Error("Should detect padding error in damaged ciphertext")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentSymmetric(t *testing.T) {
|
||||
key := []byte("1234567890123456")
|
||||
iv := []byte("1234567890123456")
|
||||
aes, _ := crypto.NewAESGCMWithOutEraseKey(key, iv)
|
||||
data := []byte("concurrent")
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
t.Run("Concurrent", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
enc, _ := aes.EncryptBytes(data)
|
||||
dec, _ := aes.DecryptBytes(enc)
|
||||
if !bytes.Equal(data, dec) { t.Error("Data race detected") }
|
||||
})
|
||||
}
|
||||
}
|
||||
153
x25519.go
Normal file
153
x25519.go
Normal file
@ -0,0 +1,153 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"apigo.cc/go/safe"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
type X25519Algorithm struct {
|
||||
UseGCM bool
|
||||
KdfInfo []byte
|
||||
KdfSalt []byte
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
X25519GCM = &X25519Algorithm{UseGCM: true, Hash: crypto.SHA256}
|
||||
X25519CBC = &X25519Algorithm{UseGCM: false, Hash: crypto.SHA256}
|
||||
)
|
||||
|
||||
func NewX25519(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
|
||||
return NewAsymmetric(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewX25519AndEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricAndEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf)
|
||||
}
|
||||
func NewX25519WithOutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
|
||||
return NewAsymmetricWithoutEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf, false)
|
||||
}
|
||||
|
||||
func NewX25519Algorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *X25519Algorithm {
|
||||
return &X25519Algorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
|
||||
}
|
||||
|
||||
func GenerateX25519KeyPair() ([]byte, []byte, error) {
|
||||
privKey := make([]byte, curve25519.ScalarSize)
|
||||
if _, err := rand.Read(privKey); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pubKey, err := curve25519.X25519(privKey, curve25519.Basepoint)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) ParsePrivateKey(der []byte) (any, error) {
|
||||
if len(der) != curve25519.ScalarSize {
|
||||
return nil, errors.New("invalid X25519 private key size")
|
||||
}
|
||||
key := make([]byte, curve25519.ScalarSize)
|
||||
copy(key, der)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) ParsePublicKey(der []byte) (any, error) {
|
||||
if len(der) != curve25519.PointSize {
|
||||
return nil, errors.New("invalid X25519 public key size")
|
||||
}
|
||||
key := make([]byte, curve25519.PointSize)
|
||||
copy(key, der)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) Sign(privateKeyObj any, data []byte, hash ...crypto.Hash) ([]byte, error) {
|
||||
return nil, errors.New("X25519 does not support signing, use Ed25519 instead")
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) Verify(publicKeyObj any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
|
||||
return false, errors.New("X25519 does not support verification, use Ed25519 instead")
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) Encrypt(publicKeyObj any, data []byte) ([]byte, error) {
|
||||
targetPub, ok := publicKeyObj.([]byte)
|
||||
if !ok || len(targetPub) != curve25519.PointSize {
|
||||
return nil, errors.New("invalid public key type/size for X25519")
|
||||
}
|
||||
ephemeralPriv, ephemeralPub, err := GenerateX25519KeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(ephemeralPriv)
|
||||
sharedSecret, err := curve25519.X25519(ephemeralPriv, targetPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(sharedSecret)
|
||||
hFunc := x.Hash
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hkdfReader := hkdf.New(hFunc.New, sharedSecret, x.KdfSalt, x.KdfInfo)
|
||||
aesKey := make([]byte, 32)
|
||||
defer safe.ZeroMemory(aesKey)
|
||||
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipherAlgo := &AESCipher{useGCM: x.UseGCM}
|
||||
ivLen := 16
|
||||
if x.UseGCM {
|
||||
ivLen = 12
|
||||
}
|
||||
iv := safe.MakeSafeToken(ivLen)
|
||||
cipherText, err := cipherAlgo.Encrypt(data, aesKey, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]byte, 0, len(ephemeralPub)+len(iv)+len(cipherText))
|
||||
out = append(out, ephemeralPub...)
|
||||
out = append(out, iv...)
|
||||
out = append(out, cipherText...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (x *X25519Algorithm) Decrypt(privateKeyObj any, data []byte) ([]byte, error) {
|
||||
myPriv, ok := privateKeyObj.([]byte)
|
||||
if !ok || len(myPriv) != curve25519.ScalarSize {
|
||||
return nil, errors.New("invalid private key type/size for X25519")
|
||||
}
|
||||
pubKeyLen := curve25519.PointSize
|
||||
ivLen := 16
|
||||
if x.UseGCM {
|
||||
ivLen = 12
|
||||
}
|
||||
if len(data) < pubKeyLen+ivLen {
|
||||
return nil, errors.New("invalid ciphertext package size")
|
||||
}
|
||||
ephemeralPub := data[:pubKeyLen]
|
||||
iv := data[pubKeyLen : pubKeyLen+ivLen]
|
||||
cipherText := data[pubKeyLen+ivLen:]
|
||||
sharedSecret, err := curve25519.X25519(myPriv, ephemeralPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer safe.ZeroMemory(sharedSecret)
|
||||
hFunc := x.Hash
|
||||
if hFunc == 0 {
|
||||
hFunc = crypto.SHA256
|
||||
}
|
||||
hkdfReader := hkdf.New(hFunc.New, sharedSecret, x.KdfSalt, x.KdfInfo)
|
||||
aesKey := make([]byte, 32)
|
||||
defer safe.ZeroMemory(aesKey)
|
||||
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cipherAlgo := &AESCipher{useGCM: x.UseGCM}
|
||||
return cipherAlgo.Decrypt(cipherText, aesKey, iv)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user