Compare commits
No commits in common. "main" and "v1.0.4" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1 @@
|
||||
.ai/
|
||||
.geminiignore
|
||||
.gemini
|
||||
/CODE-FULL.md
|
||||
go.sum
|
||||
|
||||
54
AI.md
Normal file
54
AI.md
Normal file
@ -0,0 +1,54 @@
|
||||
# AI Coding Context: @go/safe (v1.0.4)
|
||||
|
||||
本索引供 AI 模型理解 `@go/safe` 的设计规范,以生成符合本项目“安全闭环、防御优先”哲学的代码。
|
||||
|
||||
## 🤖 AI 行为准则 (API 优先级)
|
||||
|
||||
1. **优先使用 (Tier 1)**:处理敏感数据,首选 `NewSafeBufAndErase` 或 `NewSafeBuf`,配合 `Open()/Close()` 闭环操作。
|
||||
2. **安全约束**:
|
||||
- `SafeBuf` 内部已自动处理 `LockMemory` 和 `ZeroMemory`,**严禁**对已托管的 `SafeBuf` 或其内部数据再次手动调用 `LockMemory`。
|
||||
- 任何 `Open()` 获得的 `SecretPlaintext`,**必须**在同一作用域内 `defer secret.Close()`。
|
||||
3. **系统与高级工具 (Tier 2)**:系统级防御(如 `DisableCoreDump`)应在 `init()` 调用。`ZeroMemory` 等底层 API 仅限高性能 buffer 复用场景。
|
||||
|
||||
## 🛠 API Reference
|
||||
|
||||
### 安全存储 (SafeBuf)
|
||||
- `func NewSafeBuf(raw []byte) *SafeBuf`:创建保护实例,需手动执行 `ZeroMemory` 擦除原始数据。
|
||||
- `func NewSafeBufAndErase(raw []byte) *SafeBuf`:创建实例并自动擦除原始明文,**首选 API**。
|
||||
- `func NewSafeBufFromEncrypted(cipher, salt []byte) *SafeBuf`:从加密源重建保护实例。
|
||||
- `func NewSafeString(raw []byte) (*SecretPlaintext, string)`:创建临时的敏感字符串。
|
||||
- `func (safeBuf *SafeBuf) Open() *SecretPlaintext`:解密 SafeBuf。**调用后必须 `defer secret.Close()`**。
|
||||
- `func (safeBuf *SafeBuf) Close()`:销毁加密实例并擦除加密内容。
|
||||
|
||||
### 内存管理与防御
|
||||
- `func LockMemory(buf []byte) error`:锁定内存页,防止 Swap。
|
||||
- `func UnlockMemory(buf []byte) error`:解锁内存页。
|
||||
- `func DisableCoreDump() error`:禁止进程核心转储。
|
||||
- `func ZeroMemory(buf []byte)`:强力覆盖内存。用于 `NewSafeBuf` 后清理原数据或 buffer 复用。
|
||||
|
||||
### 辅助与底层工具
|
||||
- `func MakeSafeToken(size int) []byte`:生成高熵随机令牌。
|
||||
- `func SetSafeBufObfuscator(encrypt func([]byte) ([]byte, []byte), decrypt func([]byte, []byte) []byte)`:设置自定义混淆器。
|
||||
- `func EncryptChaCha20(raw []byte, key []byte, salt []byte) []byte`:ChaCha20 加密。
|
||||
- `func DecryptChaCha20(cipher []byte, key []byte, salt []byte) []byte`:ChaCha20 解密。
|
||||
|
||||
## 🧩 典型模式 (Best Practices)
|
||||
|
||||
* **✅ 场景:标准敏感数据处理**:
|
||||
```go
|
||||
// 数据使用后立即销毁,无需二次操作
|
||||
safeBuf := safe.NewSafeBufAndErase(secretData)
|
||||
defer safeBuf.Close()
|
||||
|
||||
secret := safeBuf.Open()
|
||||
defer secret.Close() // 闭环清理明文副本
|
||||
process(secret.String())
|
||||
```
|
||||
|
||||
* **✅ 系统级防御**:
|
||||
```go
|
||||
func init() {
|
||||
_ = safe.DisableCoreDump()
|
||||
}
|
||||
```
|
||||
|
||||
@ -1,14 +1,5 @@
|
||||
# Changelog: @go/safe
|
||||
|
||||
## [v1.0.6] - 2026-05-09
|
||||
|
||||
### Added
|
||||
- **安全拼接 (Concat)**:新增 `Concat` 工具,支持对多个敏感部分(SafeBuf, SecretPlaintext, string, []byte)进行零残留安全拼接,返回持久加密的 `SafeBuf`。
|
||||
- **安全编码 (Base64/Hex/UrlEncode)**:新增多种编码工具,支持接受 `...any` 变长参数。内置隐式安全拼接能力(无需额外调用 `Concat`),直接返回持久加密的 `SafeBuf` 对象,脱离对 GC Finalizer 的强依赖,安全且可重复使用。
|
||||
|
||||
### Improved
|
||||
- **生命周期增强**:优化了 `SecretPlaintext` 的安全性,增强了 `String()` 方法的鲁棒性。
|
||||
|
||||
## [v1.0.4] - 2026-05-01
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -30,13 +30,6 @@
|
||||
- `func NewSafeString(data []byte) (*SecretPlaintext, string)`: 创建临时的安全字符串。
|
||||
- `func MakeSafeToken(size int) []byte`: 生成带随机偏移的安全令牌。
|
||||
|
||||
### 无感安全辅助工具 (Frictionless Security Helpers)
|
||||
提供基于可变参数 `...any` 的隐式拼接与编码能力。所有操作均在底层私有缓冲区完成,并在返回持久加密的 `*SafeBuf` 后立刻物理擦除中间明文,彻底杜绝字符串拼接造成的内存泄露。
|
||||
- `func Concat(parts ...any) *SafeBuf`: 隐式安全拼接。
|
||||
- `func Base64(parts ...any) *SafeBuf`: 隐式安全拼接并 Base64 编码。
|
||||
- `func Hex(parts ...any) *SafeBuf`: 隐式安全拼接并 Hex 编码。
|
||||
- `func UrlEncode(parts ...any) *SafeBuf`: 隐式安全拼接并 URL 编码。
|
||||
|
||||
### 混淆器与算法
|
||||
- `func SetSafeBufObfuscator(enc, dec)`: 自定义 SafeBuf 的底层加密逻辑。
|
||||
- `func EncryptChaCha20(raw, key, salt []byte) []byte`
|
||||
|
||||
8
go.mod
8
go.mod
@ -3,9 +3,7 @@ module apigo.cc/go/safe
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/rand v1.3.1
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/sys v0.44.0
|
||||
apigo.cc/go/rand v1.0.4
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/sys v0.43.0
|
||||
)
|
||||
|
||||
require apigo.cc/go/cast v1.3.3
|
||||
|
||||
8
go.sum
8
go.sum
@ -1,8 +0,0 @@
|
||||
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
|
||||
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
|
||||
apigo.cc/go/rand v1.3.1 h1:7FvsI6PtQ5XrWER0dTiLVo0p7GIxRidT/TBKhVy93j8=
|
||||
apigo.cc/go/rand v1.3.1/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||
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=
|
||||
135
safe.go
135
safe.go
@ -2,16 +2,12 @@ package safe
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"apigo.cc/go/cast"
|
||||
"apigo.cc/go/rand"
|
||||
"golang.org/x/crypto/chacha20"
|
||||
)
|
||||
@ -120,7 +116,7 @@ type SecretPlaintext struct {
|
||||
|
||||
// String 返回明文的字符串表示
|
||||
func (secret *SecretPlaintext) String() string {
|
||||
if secret == nil || len(secret.Data) == 0 {
|
||||
if len(secret.Data) == 0 {
|
||||
return ""
|
||||
}
|
||||
return unsafe.String(&secret.Data[0], len(secret.Data))
|
||||
@ -128,22 +124,12 @@ func (secret *SecretPlaintext) String() string {
|
||||
|
||||
// Close 清除明文数据
|
||||
func (secret *SecretPlaintext) Close() {
|
||||
if secret != nil && secret.Data != nil {
|
||||
if secret.Data != nil {
|
||||
ZeroMemory(secret.Data)
|
||||
secret.Data = nil
|
||||
}
|
||||
}
|
||||
|
||||
// newSecretPlaintext 内部辅助函数,统一绑定 Finalizer 作为安全兜底
|
||||
func newSecretPlaintext(data []byte) *SecretPlaintext {
|
||||
secret := &SecretPlaintext{Data: data}
|
||||
// GC Finalizer 仅作为兜底策略,主生命周期应由调用者显式管理
|
||||
runtime.SetFinalizer(secret, func(obj *SecretPlaintext) {
|
||||
obj.Close()
|
||||
})
|
||||
return secret
|
||||
}
|
||||
|
||||
// NewSafeBuf 创建一个新的 SafeBuf
|
||||
func NewSafeBuf(raw []byte) *SafeBuf {
|
||||
cipher, salt := safeBufEncrypt(raw)
|
||||
@ -167,22 +153,20 @@ func NewSafeBufFromEncrypted(cipher, salt []byte) *SafeBuf {
|
||||
|
||||
// Open 解密 SafeBuf 并返回明文副本
|
||||
func (safeBuf *SafeBuf) Open() *SecretPlaintext {
|
||||
if safeBuf == nil {
|
||||
return &SecretPlaintext{}
|
||||
}
|
||||
data := safeBufDecrypt(safeBuf.buf, safeBuf.salt)
|
||||
if data == nil {
|
||||
return &SecretPlaintext{}
|
||||
}
|
||||
_ = LockMemory(data)
|
||||
return newSecretPlaintext(data)
|
||||
secret := &SecretPlaintext{Data: data}
|
||||
runtime.SetFinalizer(secret, func(obj *SecretPlaintext) {
|
||||
obj.Close()
|
||||
})
|
||||
return secret
|
||||
}
|
||||
|
||||
// Close 擦除 SafeBuf 中的加密数据
|
||||
func (safeBuf *SafeBuf) Close() {
|
||||
if safeBuf == nil {
|
||||
return
|
||||
}
|
||||
_ = UnlockMemory(safeBuf.buf)
|
||||
ZeroMemory(safeBuf.buf)
|
||||
_ = UnlockMemory(safeBuf.salt)
|
||||
@ -191,106 +175,9 @@ func (safeBuf *SafeBuf) Close() {
|
||||
|
||||
// NewSafeString 创建一个临时的敏感字符串
|
||||
func NewSafeString(raw []byte) (*SecretPlaintext, string) {
|
||||
secret := newSecretPlaintext(raw)
|
||||
secret := &SecretPlaintext{Data: raw}
|
||||
runtime.SetFinalizer(secret, func(obj *SecretPlaintext) {
|
||||
obj.Close()
|
||||
})
|
||||
return secret, secret.String()
|
||||
}
|
||||
|
||||
// Concat 安全地拼接多个部分并返回一个持久加密的 SafeBuf 对象
|
||||
func Concat(parts ...any) *SafeBuf {
|
||||
return NewSafeBufAndErase(buildBytes(parts...))
|
||||
}
|
||||
|
||||
// Base64 对输入进行拼接与编码,并返回一个持久加密的 SafeBuf 对象
|
||||
func Base64(parts ...any) *SafeBuf {
|
||||
src := buildBytes(parts...)
|
||||
defer ZeroMemory(src)
|
||||
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(src)))
|
||||
base64.StdEncoding.Encode(buf, src)
|
||||
|
||||
return NewSafeBufAndErase(buf)
|
||||
}
|
||||
|
||||
// Hex 将数据拼接并转换为 Hex 编码,返回一个持久加密的 SafeBuf 对象
|
||||
func Hex(parts ...any) *SafeBuf {
|
||||
src := buildBytes(parts...)
|
||||
defer ZeroMemory(src)
|
||||
|
||||
buf := make([]byte, hex.EncodedLen(len(src)))
|
||||
hex.Encode(buf, src)
|
||||
|
||||
return NewSafeBufAndErase(buf)
|
||||
}
|
||||
|
||||
// UrlEncode 对数据拼接并进行 URL 编码,返回一个持久加密的 SafeBuf 对象
|
||||
func UrlEncode(parts ...any) *SafeBuf {
|
||||
src := buildBytes(parts...)
|
||||
defer ZeroMemory(src)
|
||||
|
||||
// 使用 unsafe.String 避免对 src 产生额外的 string 分配
|
||||
srcStr := unsafe.String(&src[0], len(src))
|
||||
encoded := url.QueryEscape(srcStr)
|
||||
buf := []byte(encoded)
|
||||
|
||||
return NewSafeBufAndErase(buf)
|
||||
}
|
||||
|
||||
func buildBytes(parts ...any) []byte {
|
||||
totalLen := 0
|
||||
for _, p := range parts {
|
||||
totalLen += partLen(p)
|
||||
}
|
||||
|
||||
buf := make([]byte, totalLen)
|
||||
pos := 0
|
||||
for _, p := range parts {
|
||||
pos += copyPart(buf[pos:], p)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func partLen(v any) int {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return len(t)
|
||||
case []byte:
|
||||
return len(t)
|
||||
case *SecretPlaintext:
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return len(t.Data)
|
||||
case *SafeBuf:
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
p := t.Open()
|
||||
defer p.Close()
|
||||
return len(p.Data)
|
||||
default:
|
||||
return len(cast.String(v))
|
||||
}
|
||||
}
|
||||
|
||||
func copyPart(dst []byte, v any) int {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return copy(dst, t)
|
||||
case []byte:
|
||||
return copy(dst, t)
|
||||
case *SecretPlaintext:
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return copy(dst, t.Data)
|
||||
case *SafeBuf:
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
p := t.Open()
|
||||
defer p.Close()
|
||||
return copy(dst, p.Data)
|
||||
default:
|
||||
return copy(dst, cast.String(v))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user