chore: 完善 API 语义及 AI 文档指南,强化安全性
This commit is contained in:
parent
ce56a760b7
commit
835e13a340
43
AI.md
Normal file
43
AI.md
Normal file
@ -0,0 +1,43 @@
|
||||
# AI Coding Context: @go/safe
|
||||
|
||||
本索引供 AI 模型理解 `@go/safe` 的设计规范,以生成符合本项目“安全、闭环、防御优先”哲学的代码。
|
||||
|
||||
## 🤖 AI 行为准则
|
||||
|
||||
1. **敏感数据全托管**:所有涉及密钥、密码、私人数据的处理,必须经由 `SafeBuf` 或其派生结构处理。
|
||||
2. **强制清理机制**:`SafeBuf.Open()` 返回的 `SecretPlaintext` 必须在业务使用完毕后显式调用 `Close()`。虽然存在 Finalizer 兜底,但不得依赖它作为主要的清理手段。
|
||||
3. **系统级安全防御**:对于运行高敏感任务的进程,在 `init()` 阶段应优先调用 `DisableCoreDump()` 和 `LockMemory()`。
|
||||
|
||||
## 🛠 关键 API 设计约定
|
||||
|
||||
| 类型 | 功能 | 语义约定 |
|
||||
| :--- | :--- | :--- |
|
||||
| **内存保护** | 锁定/解锁页面 | `LockMemory`/`UnlockMemory` |
|
||||
| **转储防御** | 系统级防御 | `DisableCoreDump` |
|
||||
| **数据安全** | 敏感存储 | `NewSafeBuf`/`NewSafeBufAndErase` -> `Open()` -> `Close()` |
|
||||
|
||||
## 🧩 典型模式 (Best Practices)
|
||||
|
||||
* **✅ 强制推荐 (内存立即擦除)**:
|
||||
```go
|
||||
// 创建即擦除原始明文,杜绝残留
|
||||
sb := safe.NewSafeBufAndErase(secretData)
|
||||
defer sb.Close()
|
||||
|
||||
sp := sb.Open()
|
||||
defer sp.Close() // 显式释放明文副本
|
||||
```
|
||||
|
||||
* **⚠️ 谨慎使用**:
|
||||
```go
|
||||
// 若原始数据后续还需要使用,使用 NewSafeBuf,但必须手动擦除原始数据
|
||||
sb := safe.NewSafeBuf(secretData)
|
||||
safe.ZeroMemory(secretData) // 必须手动执行
|
||||
defer sb.Close()
|
||||
```
|
||||
|
||||
* **🤖 AI 强制指令**:
|
||||
1. **禁止残留**:绝不允许明文数据在内存中无保护停留。
|
||||
2. **API 优先级**:在代码生成时,凡涉及敏感数据传入,默认首选 `NewSafeBufAndErase`。
|
||||
3. **闭环调用**:任何 `Open()` 后的 `SecretPlaintext` 对象,生成的代码必须同时生成对应的 `defer sp.Close()` 调用。
|
||||
|
||||
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Changelog: @go/safe
|
||||
|
||||
## [v1.0.0] - 2026-04-22
|
||||
|
||||
### Added
|
||||
- **内存锁定机制**:实现 `LockMemory`/`UnlockMemory` (支持 Unix/Windows 条件编译),防止敏感数据被交换至磁盘。
|
||||
- **Core Dump 防御**:新增 `DisableCoreDump`,切断异常进程的核心转储文件暴露路径。
|
||||
- **安全缓存 (SafeBuf)**:实现动态加密内存缓冲,提供基于 `ChaCha20` 的存储保护,并支持自定义混淆器注入。
|
||||
- **内存物理擦除**:提供 `ZeroMemory` 强力覆盖机制,配合 `runtime.Finalizer` 实现全链路数据自动销毁。
|
||||
33
README.md
33
README.md
@ -1,3 +1,32 @@
|
||||
# safe
|
||||
# 关于本项目
|
||||
本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。
|
||||
|
||||
安全内存管理与敏感数据保护工具库
|
||||
# @go/safe
|
||||
|
||||
`@go/safe` 是一个为高敏感业务场景设计的内存安全管理库。它通过内存锁定、物理地址擦除及防 Core Dump 技术,确保敏感数据(如密钥、密码)在内存处理周期内的全链路防御。
|
||||
|
||||
## 🎯 设计哲学
|
||||
|
||||
* **物理内存防御**:集成系统级 `mlock`,杜绝敏感数据通过交换分区泄露。
|
||||
* **内存转储抗性**:提供 `DisableCoreDump` 接口,从 OS 层级切断异常快照的敏感数据暴露。
|
||||
* **全生命周期擦除**:通过 `ZeroMemory` 与 `runtime.Finalizer` 双重机制,确保敏感数据在 `Close()` 时及对象销毁时物理覆盖。
|
||||
|
||||
## 🛠 API Reference
|
||||
|
||||
### 内存保护
|
||||
- `func LockMemory(buf []byte) error`
|
||||
- `func UnlockMemory(buf []byte) error`
|
||||
- `func DisableCoreDump() error`
|
||||
|
||||
### 安全存储
|
||||
- `func NewSafeBuf(data []byte) *SafeBuf`
|
||||
- `func NewSafeBufAndErase(data []byte) *SafeBuf`
|
||||
- `func NewSafeBufFromEncrypted(cipher, salt []byte) *SafeBuf`
|
||||
- `func MakeSafeToken(size int) []byte`
|
||||
- `func SetSafeBufObfuscator(enc, dec)`
|
||||
|
||||
## 📦 安装
|
||||
|
||||
```bash
|
||||
go get apigo.cc/go/safe
|
||||
```
|
||||
|
||||
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
||||
module apigo.cc/go/safe
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/rand v1.0.2 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
)
|
||||
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
||||
apigo.cc/go/rand v1.0.2 h1:dJsm607EynJOAoukTvarrUyvLtBF7pi27A99vw2+i78=
|
||||
apigo.cc/go/rand v1.0.2/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||
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=
|
||||
32
mem_unix.go
Normal file
32
mem_unix.go
Normal file
@ -0,0 +1,32 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package safe
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LockMemory 锁定内存页,防止其被置换到硬盘 (Swap)
|
||||
func LockMemory(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unix.Mlock(buf)
|
||||
}
|
||||
|
||||
// UnlockMemory 解锁内存页
|
||||
func UnlockMemory(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unix.Munlock(buf)
|
||||
}
|
||||
|
||||
// DisableCoreDump 禁止进程核心转储
|
||||
func DisableCoreDump() error {
|
||||
var rlimit unix.Rlimit
|
||||
rlimit.Cur = 0
|
||||
rlimit.Max = 0
|
||||
return unix.Setrlimit(unix.RLIMIT_CORE, &rlimit)
|
||||
}
|
||||
31
mem_windows.go
Normal file
31
mem_windows.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package safe
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// LockMemory 锁定内存页 (Windows 需使用 VirtualLock)
|
||||
func LockMemory(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Windows 内存锁定通常需要处理内存页对齐
|
||||
return windows.VirtualLock(uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
||||
}
|
||||
|
||||
// UnlockMemory 解锁内存页
|
||||
func UnlockMemory(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
return windows.VirtualUnlock(uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
||||
}
|
||||
|
||||
// DisableCoreDump 禁止进程核心转储
|
||||
func DisableCoreDump() error {
|
||||
windows.SetErrorMode(windows.SEM_NOGPFAULTERRORBOX)
|
||||
return nil
|
||||
}
|
||||
162
safe.go
Normal file
162
safe.go
Normal file
@ -0,0 +1,162 @@
|
||||
package safe
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"apigo.cc/go/rand"
|
||||
"golang.org/x/crypto/chacha20"
|
||||
)
|
||||
|
||||
// MakeSafeToken 生成指定大小的随机字节令牌
|
||||
func MakeSafeToken(size int) []byte {
|
||||
// 使用 rand.Int (apigo.cc/go/rand) 生成偏移量
|
||||
fixsize := rand.Int(1, size/10+2)
|
||||
key := make([]byte, size+fixsize*2)
|
||||
crand.Read(key)
|
||||
return key[fixsize : fixsize+size]
|
||||
}
|
||||
|
||||
// EncryptChaCha20 使用 ChaCha20 进行加密
|
||||
func EncryptChaCha20(raw []byte, key []byte, salt []byte) []byte {
|
||||
cipherObj, _ := chacha20.NewUnauthenticatedCipher(key[:chacha20.KeySize], salt[:chacha20.NonceSizeX])
|
||||
cipher := make([]byte, len(raw))
|
||||
cipherObj.XORKeyStream(cipher, raw)
|
||||
return cipher
|
||||
}
|
||||
|
||||
// DecryptChaCha20 使用 ChaCha20 进行解密
|
||||
func DecryptChaCha20(cipher []byte, key []byte, salt []byte) []byte {
|
||||
cipherObj, _ := chacha20.NewUnauthenticatedCipher(key[:chacha20.KeySize], salt[:chacha20.NonceSizeX])
|
||||
plaintext := make([]byte, len(cipher))
|
||||
cipherObj.XORKeyStream(plaintext, cipher)
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// ZeroMemory 使用随机种子覆盖内存,确保敏感数据擦除
|
||||
func ZeroMemory(buf []byte) {
|
||||
seed := uint64(time.Now().UnixNano())
|
||||
i := 0
|
||||
n := len(buf)
|
||||
for i <= n-8 {
|
||||
seed ^= seed << 13
|
||||
seed ^= seed >> 7
|
||||
seed ^= seed << 17
|
||||
binary.LittleEndian.PutUint64(buf[i:], seed)
|
||||
i += 8
|
||||
}
|
||||
if i < n {
|
||||
seed ^= seed << 13
|
||||
seed ^= seed >> 7
|
||||
seed ^= seed << 17
|
||||
var tmp [8]byte
|
||||
binary.LittleEndian.PutUint64(tmp[:], seed)
|
||||
copy(buf[i:], tmp[:n-i])
|
||||
}
|
||||
runtime.KeepAlive(buf)
|
||||
}
|
||||
|
||||
var chachaGlobalKey = make([]byte, chacha20.KeySize)
|
||||
|
||||
func init() {
|
||||
crand.Read(chachaGlobalKey)
|
||||
}
|
||||
|
||||
var safeBufEncrypt = func(raw []byte) ([]byte, []byte) {
|
||||
salt := MakeSafeToken(chacha20.NonceSizeX)
|
||||
return EncryptChaCha20(raw, chachaGlobalKey, salt), salt
|
||||
}
|
||||
|
||||
var safeBufDecrypt = func(cipher []byte, salt []byte) []byte {
|
||||
return DecryptChaCha20(cipher, chachaGlobalKey, salt)
|
||||
}
|
||||
|
||||
var setObfOnce sync.Once
|
||||
|
||||
// SetSafeBufObfuscator 设置自定义的 SafeBuf 加解密混淆器
|
||||
func SetSafeBufObfuscator(encrypt func([]byte) ([]byte, []byte), decrypt func([]byte, []byte) []byte) {
|
||||
setObfOnce.Do(func() {
|
||||
safeBufEncrypt = encrypt
|
||||
safeBufDecrypt = decrypt
|
||||
})
|
||||
}
|
||||
|
||||
// SafeBuf 用于存储受保护的敏感数据
|
||||
type SafeBuf struct {
|
||||
buf []byte
|
||||
salt []byte
|
||||
}
|
||||
|
||||
// SecretPlaintext 表示敏感数据的明文副本
|
||||
type SecretPlaintext struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// String 返回明文的字符串表示
|
||||
func (sp *SecretPlaintext) String() string {
|
||||
if len(sp.Data) == 0 {
|
||||
return ""
|
||||
}
|
||||
return unsafe.String(&sp.Data[0], len(sp.Data))
|
||||
}
|
||||
|
||||
// Close 清除明文数据
|
||||
func (sp *SecretPlaintext) Close() {
|
||||
if sp.Data != nil {
|
||||
ZeroMemory(sp.Data)
|
||||
sp.Data = nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewSafeBuf 创建一个新的 SafeBuf
|
||||
func NewSafeBuf(raw []byte) *SafeBuf {
|
||||
cipher, salt := safeBufEncrypt(raw)
|
||||
LockMemory(cipher)
|
||||
LockMemory(salt)
|
||||
return &SafeBuf{buf: cipher, salt: salt}
|
||||
}
|
||||
|
||||
// NewSafeBufAndErase 创建一个新的 SafeBuf 并自动擦除原始数据
|
||||
func NewSafeBufAndErase(raw []byte) *SafeBuf {
|
||||
defer ZeroMemory(raw)
|
||||
return NewSafeBuf(raw)
|
||||
}
|
||||
|
||||
// NewSafeBufFromEncrypted 从已加密数据创建 SafeBuf
|
||||
func NewSafeBufFromEncrypted(cipher, salt []byte) *SafeBuf {
|
||||
LockMemory(cipher)
|
||||
LockMemory(salt)
|
||||
return &SafeBuf{buf: cipher, salt: salt}
|
||||
}
|
||||
|
||||
// Open 解密 SafeBuf 并返回明文副本
|
||||
func (sb *SafeBuf) Open() *SecretPlaintext {
|
||||
data := safeBufDecrypt(sb.buf, sb.salt)
|
||||
LockMemory(data)
|
||||
sp := &SecretPlaintext{Data: data}
|
||||
runtime.SetFinalizer(sp, func(obj *SecretPlaintext) {
|
||||
obj.Close()
|
||||
})
|
||||
return sp
|
||||
}
|
||||
|
||||
// Close 擦除 SafeBuf 中的加密数据
|
||||
func (sb *SafeBuf) Close() {
|
||||
UnlockMemory(sb.buf)
|
||||
ZeroMemory(sb.buf)
|
||||
UnlockMemory(sb.salt)
|
||||
ZeroMemory(sb.salt)
|
||||
}
|
||||
|
||||
// NewSafeString 创建一个临时的敏感字符串
|
||||
func NewSafeString(raw []byte) (*SecretPlaintext, string) {
|
||||
sp := &SecretPlaintext{Data: raw}
|
||||
runtime.SetFinalizer(sp, func(obj *SecretPlaintext) {
|
||||
obj.Close()
|
||||
})
|
||||
return sp, sp.String()
|
||||
}
|
||||
86
safe_test.go
Normal file
86
safe_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package safe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestSafeBuf(t *testing.T) {
|
||||
data := []byte("secret_password_123")
|
||||
sb := NewSafeBuf(data)
|
||||
defer sb.Close()
|
||||
|
||||
sp := sb.Open()
|
||||
if !bytes.Equal(sp.Data, data) {
|
||||
t.Fatal("SafeBuf decryption failed")
|
||||
}
|
||||
|
||||
if sp.String() != string(data) {
|
||||
t.Fatal("String representation mismatch")
|
||||
}
|
||||
|
||||
sp.Close()
|
||||
if sp.Data != nil {
|
||||
t.Error("Data not cleared after Close")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryErasure(t *testing.T) {
|
||||
data := []byte("secret_sensitive_content")
|
||||
sb := NewSafeBuf(data)
|
||||
sp := sb.Open()
|
||||
|
||||
// 捕获内存指针
|
||||
ptr := unsafe.Pointer(&sp.Data[0])
|
||||
dataLen := len(sp.Data)
|
||||
|
||||
sp.Close()
|
||||
|
||||
// 检查内存是否被覆盖
|
||||
// 注意:在释放内存后直接检查虽然是未定义行为,但在这里是为了验证安全基石
|
||||
raw := unsafe.Slice((*byte)(ptr), dataLen)
|
||||
if bytes.Equal(raw, data) {
|
||||
t.Fatal("Security Breach: Memory not erased after Close()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeBufCustomObfuscator(t *testing.T) {
|
||||
encrypt := func(raw []byte) ([]byte, []byte) {
|
||||
return append([]byte(nil), raw...), []byte("salt")
|
||||
}
|
||||
decrypt := func(cipher []byte, salt []byte) []byte {
|
||||
return cipher
|
||||
}
|
||||
|
||||
SetSafeBufObfuscator(encrypt, decrypt)
|
||||
|
||||
data := []byte("custom_obfuscator_test")
|
||||
sb := NewSafeBuf(data)
|
||||
defer sb.Close()
|
||||
|
||||
sp := sb.Open()
|
||||
if !bytes.Equal(sp.Data, data) {
|
||||
t.Error("Custom obfuscator failed to roundtrip")
|
||||
}
|
||||
sp.Close()
|
||||
}
|
||||
|
||||
func TestDisableCoreDump(t *testing.T) {
|
||||
if err := DisableCoreDump(); err != nil {
|
||||
t.Errorf("DisableCoreDump failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSafeBufOpenClose(b *testing.B) {
|
||||
data := []byte("benchmark_data")
|
||||
sb := NewSafeBuf(data)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp := sb.Open()
|
||||
sp.Close()
|
||||
runtime.KeepAlive(sp)
|
||||
}
|
||||
sb.Close()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user