From 835e13a340a754633401b4dc90f4fdeb3e9182f8 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Wed, 22 Apr 2026 18:41:30 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=AE=8C=E5=96=84=20API=20=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E5=8F=8A=20AI=20=E6=96=87=E6=A1=A3=E6=8C=87=E5=8D=97?= =?UTF-8?q?=EF=BC=8C=E5=BC=BA=E5=8C=96=E5=AE=89=E5=85=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AI.md | 43 +++++++++++++ CHANGELOG.md | 9 +++ README.md | 33 +++++++++- go.mod | 9 +++ go.sum | 6 ++ mem_unix.go | 32 ++++++++++ mem_windows.go | 31 ++++++++++ safe.go | 162 +++++++++++++++++++++++++++++++++++++++++++++++++ safe_test.go | 86 ++++++++++++++++++++++++++ 9 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 AI.md create mode 100644 CHANGELOG.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 mem_unix.go create mode 100644 mem_windows.go create mode 100644 safe.go create mode 100644 safe_test.go diff --git a/AI.md b/AI.md new file mode 100644 index 0000000..eaee4c5 --- /dev/null +++ b/AI.md @@ -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()` 调用。 + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..32d8491 --- /dev/null +++ b/CHANGELOG.md @@ -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` 实现全链路数据自动销毁。 diff --git a/README.md b/README.md index eb7a4ef..ed4f640 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ -# safe +# 关于本项目 +本项目完全由 AI 维护。代码源自 github.com/ssgo/u 的重构。 -安全内存管理与敏感数据保护工具库 \ No newline at end of file +# @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 +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..77a18cc --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..18709b8 --- /dev/null +++ b/go.sum @@ -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= diff --git a/mem_unix.go b/mem_unix.go new file mode 100644 index 0000000..c1bb7a4 --- /dev/null +++ b/mem_unix.go @@ -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) +} diff --git a/mem_windows.go b/mem_windows.go new file mode 100644 index 0000000..977a3ba --- /dev/null +++ b/mem_windows.go @@ -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 +} diff --git a/safe.go b/safe.go new file mode 100644 index 0000000..9a328af --- /dev/null +++ b/safe.go @@ -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() +} diff --git a/safe_test.go b/safe_test.go new file mode 100644 index 0000000..e1af1ce --- /dev/null +++ b/safe_test.go @@ -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() +}