feat: 实现隐式安全拼接与编码工具 (Frictionless Security)(by AI)
This commit is contained in:
parent
0d26198bb2
commit
a4b80570c4
@ -1,5 +1,14 @@
|
|||||||
# Changelog: @go/safe
|
# 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
|
## [v1.0.4] - 2026-05-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -30,6 +30,13 @@
|
|||||||
- `func NewSafeString(data []byte) (*SecretPlaintext, string)`: 创建临时的安全字符串。
|
- `func NewSafeString(data []byte) (*SecretPlaintext, string)`: 创建临时的安全字符串。
|
||||||
- `func MakeSafeToken(size int) []byte`: 生成带随机偏移的安全令牌。
|
- `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 SetSafeBufObfuscator(enc, dec)`: 自定义 SafeBuf 的底层加密逻辑。
|
||||||
- `func EncryptChaCha20(raw, key, salt []byte) []byte`
|
- `func EncryptChaCha20(raw, key, salt []byte) []byte`
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -7,5 +7,3 @@ require (
|
|||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/sys v0.43.0
|
golang.org/x/sys v0.43.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace apigo.cc/go/rand => ../rand
|
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
apigo.cc/go/rand v1.0.5 h1:AkUoWr0SELgeDmRjLEDjOIp29nXdzqQQvmGRIHpTN7U=
|
||||||
|
apigo.cc/go/rand v1.0.5/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
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/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 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
|
|||||||
135
safe.go
135
safe.go
@ -2,12 +2,16 @@ package safe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"apigo.cc/go/cast"
|
||||||
"apigo.cc/go/rand"
|
"apigo.cc/go/rand"
|
||||||
"golang.org/x/crypto/chacha20"
|
"golang.org/x/crypto/chacha20"
|
||||||
)
|
)
|
||||||
@ -116,7 +120,7 @@ type SecretPlaintext struct {
|
|||||||
|
|
||||||
// String 返回明文的字符串表示
|
// String 返回明文的字符串表示
|
||||||
func (secret *SecretPlaintext) String() string {
|
func (secret *SecretPlaintext) String() string {
|
||||||
if len(secret.Data) == 0 {
|
if secret == nil || len(secret.Data) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return unsafe.String(&secret.Data[0], len(secret.Data))
|
return unsafe.String(&secret.Data[0], len(secret.Data))
|
||||||
@ -124,12 +128,22 @@ func (secret *SecretPlaintext) String() string {
|
|||||||
|
|
||||||
// Close 清除明文数据
|
// Close 清除明文数据
|
||||||
func (secret *SecretPlaintext) Close() {
|
func (secret *SecretPlaintext) Close() {
|
||||||
if secret.Data != nil {
|
if secret != nil && secret.Data != nil {
|
||||||
ZeroMemory(secret.Data)
|
ZeroMemory(secret.Data)
|
||||||
secret.Data = nil
|
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
|
// NewSafeBuf 创建一个新的 SafeBuf
|
||||||
func NewSafeBuf(raw []byte) *SafeBuf {
|
func NewSafeBuf(raw []byte) *SafeBuf {
|
||||||
cipher, salt := safeBufEncrypt(raw)
|
cipher, salt := safeBufEncrypt(raw)
|
||||||
@ -153,20 +167,22 @@ func NewSafeBufFromEncrypted(cipher, salt []byte) *SafeBuf {
|
|||||||
|
|
||||||
// Open 解密 SafeBuf 并返回明文副本
|
// Open 解密 SafeBuf 并返回明文副本
|
||||||
func (safeBuf *SafeBuf) Open() *SecretPlaintext {
|
func (safeBuf *SafeBuf) Open() *SecretPlaintext {
|
||||||
|
if safeBuf == nil {
|
||||||
|
return &SecretPlaintext{}
|
||||||
|
}
|
||||||
data := safeBufDecrypt(safeBuf.buf, safeBuf.salt)
|
data := safeBufDecrypt(safeBuf.buf, safeBuf.salt)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return &SecretPlaintext{}
|
return &SecretPlaintext{}
|
||||||
}
|
}
|
||||||
_ = LockMemory(data)
|
_ = LockMemory(data)
|
||||||
secret := &SecretPlaintext{Data: data}
|
return newSecretPlaintext(data)
|
||||||
runtime.SetFinalizer(secret, func(obj *SecretPlaintext) {
|
|
||||||
obj.Close()
|
|
||||||
})
|
|
||||||
return secret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close 擦除 SafeBuf 中的加密数据
|
// Close 擦除 SafeBuf 中的加密数据
|
||||||
func (safeBuf *SafeBuf) Close() {
|
func (safeBuf *SafeBuf) Close() {
|
||||||
|
if safeBuf == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
_ = UnlockMemory(safeBuf.buf)
|
_ = UnlockMemory(safeBuf.buf)
|
||||||
ZeroMemory(safeBuf.buf)
|
ZeroMemory(safeBuf.buf)
|
||||||
_ = UnlockMemory(safeBuf.salt)
|
_ = UnlockMemory(safeBuf.salt)
|
||||||
@ -175,9 +191,106 @@ func (safeBuf *SafeBuf) Close() {
|
|||||||
|
|
||||||
// NewSafeString 创建一个临时的敏感字符串
|
// NewSafeString 创建一个临时的敏感字符串
|
||||||
func NewSafeString(raw []byte) (*SecretPlaintext, string) {
|
func NewSafeString(raw []byte) (*SecretPlaintext, string) {
|
||||||
secret := &SecretPlaintext{Data: raw}
|
secret := newSecretPlaintext(raw)
|
||||||
runtime.SetFinalizer(secret, func(obj *SecretPlaintext) {
|
|
||||||
obj.Close()
|
|
||||||
})
|
|
||||||
return secret, secret.String()
|
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