desensitize

package
v0.19.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

  • @Author: kamalyes 501893067@qq.com
  • @Date: 2023-07-28 00:50:58
  • @LastEditors: kamalyes 501893067@qq.com
  • @LastEditTime: 2025-12-23 11:52:30
  • @FilePath: \go-toolbox\pkg\desensitize\constants.go
  • @Description: *
  • Copyright (c) 2024 by kamalyes, All Rights Reserved.

Package desensitize 提供数据脱敏:零第三方依赖,按结构体 tag、字段名或整段报文三种方式工作

三条入口,判定依据不同

这三条路解决的不是同一个问题,取舍方向也不同,选错会既误擦又漏擦:

入口                        判定依据              适用场景        取舍
Desensitization(obj)       结构体 tag 逐字段声明    自己的 Model    最准,不猜
ActionForKey(key)          字段名严格匹配          日志 KV         保守,宁可漏擦
ActionForKeyLoose(key)     字段名 + 内部词根        报文 / 响应体    激进,宁可误擦
DataMasker.Mask(body)      同上,整段重写          请求 / 响应体    激进

为什么 Model 不走字段名启发式

字段名规则表(keyrules.go)是按**日志**调出来的,它的取舍对 Model 是错的:

  • name 不入表:通用日志里 service_name、table_name、file_name 远多于人名
  • status_code、cache_key、trace_id 进豁免表:它们是排障主线索,擦掉日志就失去价值

而在一个 Model 里,Name 字段通常就是人名,Address 通常就是收货地址 所以 Model 这条路**完全不碰规则表**,只认你写的 tag:

type User struct {
    Name  string `desensitize:"name"`
    Phone string `desensitize:"mobilePhone"`
    Email string `desensitize:"email"`
    Memo  string // 没有 tag 就不动
}
err := desensitize.Desensitization(&user)

你掌握 schema 时显式声明永远比猜更准,这也是 tag 存在的理由

什么时候才该用字段名启发式

只有在**拿不到 schema** 时才猜:日志的 KV 参数来自各处调用方, HTTP 响应体可能是上游返回的任意 JSON,这些场合没有 tag 可依

两种强度对应两种代价:

  • 日志用 ActionForKey:误擦会让排障线索静默消失,所以只认精确与后缀匹配, 并单列运维豁免表兜底
  • 报文用 ActionForKeyLoose:漏擦等于把凭据写进了日志或响应, 所以额外扫字段名内部的敏感词根,my_token_field 这类也会命中

零依赖

主模块不引入任何第三方库,使用者编译本库时第三方包数为 0 testify 仅测试使用,不会进入使用者的构建图

迁移出处

本库由两处代码合并而成,都在 2025 年底提取:

  • 按 tag 与按类型的脱敏(adapter.go、desensitize.go、masker.go、model.go、 constants.go)取自 github.com/HitGameAI/apex-toolbox v0.17.0 的 pkg/desensitize, 该包自 v0.17.0 起未再变动
  • 按字段名的规则表与判定(keyrules.go)取自 github.com/HitGameAI/apex-toolbox/pkg/logger 的 sanitize.go,其中不依赖 Logger 的纯逻辑部分

提取后 go-toolbox 的 go-toolbox/pkg/argus 依赖与 go-toolbox/pkg/logger 的规则副本都不再需要, 200 余条安全规则从此只有一处来源

  • @Author: kamalyes 501893067@qq.com
  • @Date: 2023-07-28 00:50:58
  • @LastEditors: kamalyes 501893067@qq.com
  • @LastEditTime: 2025-12-23 11:52:30
  • @FilePath: \go-toolbox\pkg\desensitize\model.go
  • @Description: *
  • Copyright (c) 2024 by kamalyes, All Rights Reserved.

Index

Constants

View Source
const CipherTagName = "cipher"

CipherTagName desensitize tag 里引用可逆脱敏的名字

View Source
const RedactedText = "[REDACTED]"

RedactedText 敏感值的替换文本

View Source
const TagName = "desensitize"

TagName 结构体字段上声明脱敏规则所用的 tag 名

对外导出是为了让下游不必硬编码这个字面量:go-toolbox/pkg/pbmo 这类库要在自己的字段 计划里读同一个 tag,若各自写死字符串,这里改名下游不会报错、只会静默不再 命中——脱敏静默失效正是最危险的失败方式 本包内部读 tag 也走这个常量,保证只有一处来源

[EN] Name of the struct tag that declares a masking rule. It is exported so downstream libraries reading the same tag in their own field plans need not hardcode the literal: if each side spelled it out and this name changed, nothing would fail to compile and masking would simply stop matching in silence, which is the most dangerous way for it to break. This package reads the tag through the same constant, so there is a single source of truth

Variables

View Source
var (
	ErrCipherKeySize   = errors.New("desensitize: 密钥长度必须为 16、24 或 32 字节")
	ErrCipherNotSealed = errors.New("desensitize: 值不带加密标识")
	ErrCipherVersion   = errors.New("desensitize: 不支持的加密标识版本")
	ErrCipherCorrupt   = errors.New("desensitize: 密文损坏或密钥不匹配")

	// ErrCipherNotConfigured 未调用 SetDefaultCipher 就使用了全局可逆脱敏
	ErrCipherNotConfigured = errors.New("desensitize: 未装配全局密钥,请在启动阶段调用 SetDefaultCipher")
)

加解密相关错误

Functions

func AppendFingerprint

func AppendFingerprint(buf []byte, s string) []byte

AppendFingerprint 追加指纹掩码:"***" + 原值 SHA-256 的前若干位十六进制 同一输入恒产出同一串,因此可跨行关联;拿到指纹的人无法还原也无法重放

func AppendMaskedEmail

func AppendMaskedEmail(buf []byte, s string) []byte

AppendMaskedEmail 追加邮箱掩码:保留首字符与完整域名 域名是排障所需且不含个人信息,故完整保留

func AppendMaskedPhone

func AppendMaskedPhone(buf []byte, s string) []byte

AppendMaskedPhone 追加号码掩码:"***" + 末 4 位数字;数字不足 5 位时整体擦除 只取数字位,故 +86 138-0013-8000 与 13800138000 掩码结果一致

func ClearDefaultCipher

func ClearDefaultCipher()

ClearDefaultCipher 卸载全局可逆脱敏器,之后 cipher tag 回到失败关闭 主要供测试复原全局状态

func DecryptStructDefault

func DecryptStructDefault(obj interface{}) error

DecryptStructDefault 用全局可逆脱敏器还原结构体字段

func Desensitization

func Desensitization(obj interface{}) error

Desensitization 按结构体 tag 对对象原地脱敏 obj 须为指向结构体的非 nil 指针

func Desensitize

func Desensitize(str string, DesensitizeType DesensitizeType, options ...DesensitizeOptions) string

Desensitize 数据脱敏

func EncryptStructDefault

func EncryptStructDefault(obj interface{}) error

EncryptStructDefault 用全局可逆脱敏器加密结构体字段 未装配时报错,而不是静默留下明文

func FingerprintString

func FingerprintString(s string) string

FingerprintString 指纹掩码的字符串形态

func HasNormalizedSuffix

func HasNormalizedSuffix(key, suffix string) bool

HasNormalizedSuffix 判断字段名归一化后是否以 suffix 结尾(suffix 须已是归一化形态) 从尾部边跳分隔符边转小写地比较,无需先构造归一化串,故零分配

func IsCredentialKey

func IsCredentialKey(key string) bool

IsCredentialKey 报告字段名是否为需整体擦除的凭据(供 URL 查询参数判定复用)

func IsEncrypted

func IsEncrypted(value string) bool

IsEncrypted 报告值是否带本库的加密标识 只看标识,不验证密文完整性,故不需要密钥

func MaskEmailString

func MaskEmailString(s string) string

MaskEmailString 邮箱掩码的字符串形态,供不走缓冲的调用方使用

func MaskPhoneString

func MaskPhoneString(s string) string

MaskPhoneString 号码掩码的字符串形态,供不走缓冲的调用方使用

func OperateByRule

func OperateByRule(desensitizerType string, in interface{}) (interface{}, error)

OperateByRule 根据规则对单个值脱敏 desensitizerType 支持普通名称(如 email)与带参数形式(如 jump(3,-4,1)) in 应为字符串

func OverrideKeyAction

func OverrideKeyAction(key string, action KeyAction)

OverrideKeyAction 覆盖某个字段名的判定结果,全进程生效

应在启动阶段调用完毕,规则表在判定路径上只读 字段名按归一化形态存入,因此 session_id、sessionId、SESSION-ID 一并生效

典型用法是把会话类字段从「整体擦除」调成可关联的形态:

// 只要能把一个会话的请求串起来
desensitize.OverrideKeyAction("session_id", desensitize.MaskFingerprint)
// 还要能在事故复盘时还原出是哪个会话
desensitize.SetDefaultCipher(key)
desensitize.OverrideKeyAction("session_id", desensitize.MaskEncrypt)

也可以调成 KeepAsIs 让字段明文,但那意味着把可重放的凭据写进日志, 请确认日志的可见范围与留存期之后再这么做

相比专门给 session 开一个开关,通用覆盖更可取: 同一个机制能应付日后任何字段的取舍变化,不必为每类字段各加一个配置项

[EN] Overrides one key's decision process-wide; do it at startup. Keys are stored normalised, so session_id, sessionId and SESSION-ID are all covered. KeepAsIs is available but means writing a replayable credential into the log, so weigh the log's audience and retention first. A general override beats a bespoke session switch: one mechanism covers any future field

func RegisterDesensitizer

func RegisterDesensitizer(desensitizerType string, desensitizer Desensitizer)

RegisterDesensitizer 注册脱敏器(支持覆盖内置项) 注册会令已缓存的类型计划作废并在下次使用时重建

func RegisterDesensitizerFactory

func RegisterDesensitizerFactory(name string, factory DesensitizerFactory)

RegisterDesensitizerFactory 注册参数化脱敏器工厂 name 为标签中括号前的名称,如 jump

func RegisterSensitiveKeys

func RegisterSensitiveKeys(keys ...string)

RegisterSensitiveKeys 追加自定义敏感字段名(归一化后参与精确匹配,整体擦除) 全进程生效:敏感字段清单属于安全基线,不应因调用方不同而不同 应在启动阶段调用完毕,规则表在判定路径上只读

func SensitiveData

func SensitiveData(str string, start, end int) string

通用脱敏函数

func SensitizeBankCard

func SensitizeBankCard(str string, cardLength int) string

银行卡号脱敏

func SensitizeIpv4

func SensitizeIpv4(str string) string

ipv4 脱敏

func SensitizeIpv6

func SensitizeIpv6(str string) string

ipv6 脱敏

func SensitizeJump

func SensitizeJump(str string, start, end, step int) string

SensitizeJump 跳步脱敏:掩码 [start, end) 区间内从 start 开始、以 step 为步长跳步选取的字符 start:开始索引(含),支持负数(-1 表示最后一个字符,按实际长度动态换算) end:结束索引(不含),支持负数动态换算(-N 表示保留末尾 N 个字符,0 表示到末尾) step:跳步数(<=0 时按 1 处理,1 为连续掩码,2 为隔位掩码)

func SensitizePEMKey

func SensitizePEMKey(str string, prefixVisibleLen, suffixVisibleLen int) string

PEM 密钥脱敏

func SensitizePhoneNumber

func SensitizePhoneNumber(str string, start, end int) string

手机号脱敏

func SetDefaultCipher

func SetDefaultCipher(key []byte) error

SetDefaultCipher 以密钥装配全局可逆脱敏器,应在启动阶段调用一次

装配后即可在 tag 里直接用,不必把 *Cipher 传遍每个调用点:

func main() {
    key, _ := base64.StdEncoding.DecodeString(os.Getenv("MASK_KEY"))
    if err := desensitize.SetDefaultCipher(key); err != nil { log.Fatal(err) }
}

type Order struct {
    Receiver string `desensitize:"cipher"`      // 加密,可还原
    Phone    string `desensitize:"mobilePhone"` // 掩码,不可还原
}

为什么没有默认密钥

两种「默认」都比没有更危险,所以本库一个都不提供:

  • 写死一个默认密钥等于没加密:拿到本库源码的人都能解开所有数据, 这是看起来像保护的安全剧场,比明文更坏——因为它会让人放松警惕
  • 启动时随机生成会永久丢数据:进程重启就换一把钥匙, 上次加密写进数据库的值再也解不开,而且故障要到下次读取才暴露

密钥的保管强度就是数据的保护强度,这件事只能由调用方决定

未装配时的行为

未装配而使用 cipher tag,字段会被**整体擦除**而不是留下明文 失败关闭是这里唯一安全的选择:忘记装配的后果应当是「看不到数据」, 而不是「以为加密了其实是明文」后者不报错、不可见,等到泄漏才发现

[EN] Why there is no default key: a hard-coded one is security theatre that anyone holding this source can undo, and a per-process random one silently destroys data at the next restart. Key custody is the whole of the protection, so only the caller can decide it. Using the cipher tag without a configured key wipes the field rather than leaving plaintext — forgetting to configure must mean "no data visible", never "thought it was encrypted"

func UnregisterSensitiveKeys

func UnregisterSensitiveKeys(keys ...string)

UnregisterSensitiveKeys 移除此前通过 RegisterSensitiveKeys 追加的字段名 内置规则不受影响,传入未注册过的名字是无操作

形状位图上对应的位**不**清除:位图只是精确表查询前的保守预筛, 多个字段名可能共用同一形状,清位会让它们永远查不到表 残留的位最多让一次本不必要的查表发生,不会给出错误答案

Types

type Cipher

type Cipher struct {
	// contains filtered or unexported fields
}

Cipher 可逆脱敏器,基于 AES-GCM

AES-GCM 是带认证的加密:密文被改动一个字节,Decrypt 就会报错而不是返回错数据 这对脱敏场景很关键——无声地还原出错误原文比直接失败危险得多

每次加密取一个随机 nonce,因此同一明文每次加密结果都不同 这是有意的:确定性输出会泄漏「两个字段的值相等」,而相等关系本身常常就是信息 代价是密文不能用于比较与去重,需要这类能力时应当另存一份哈希

[EN] AES-GCM is authenticated, so a single altered byte fails the Decrypt rather than yielding wrong plaintext — silently recovering the wrong value would be worse than failing. A fresh random nonce per encryption means the same plaintext encrypts differently every time, deliberately: deterministic output would leak equality between fields, which is itself information. The cost is that ciphertexts cannot be compared or deduplicated

func DefaultCipher

func DefaultCipher() *Cipher

DefaultCipher 返回全局可逆脱敏器,未装配时返回 nil

func NewCipher

func NewCipher(key []byte) (*Cipher, error)

NewCipher 以密钥创建可逆脱敏器 key 长度须为 16、24 或 32 字节,分别对应 AES-128、AES-192、AES-256

密钥由调用方保管:本库不生成也不持久化密钥 丢失密钥等于数据永久不可还原,这是加密的固有性质而非缺陷

func (*Cipher) Decrypt

func (c *Cipher) Decrypt(value string) (string, error)

Decrypt 还原带标识的值 不带标识的值原样返回并报 ErrCipherNotSealed,便于调用方区分「本来就是明文」

func (*Cipher) DecryptStruct

func (c *Cipher) DecryptStruct(obj interface{}) error

DecryptStruct 还原结构体中所有带加密标识的字符串字段

只认标识,不需要「哪些字段加密过」的清单——这正是加密值带标识的用处 混在同一结构体里的明文字段不会被动,解不开的值也原样保留, 这样一次失败不会把整个对象变成不可读

func (*Cipher) Desensitize

func (c *Cipher) Desensitize(value string) string

Desensitize 实现 Desensitizer 接口,使 Cipher 可注册为脱敏器

注册后即可在 tag 里使用:

c, _ := desensitize.NewCipher(key)
desensitize.RegisterDesensitizer("cipher", c)

type Order struct {
    Receiver string `desensitize:"cipher"` // 加密,可还原
    Phone    string `desensitize:"mobilePhone"` // 掩码,不可还原
}

func (*Cipher) Encrypt

func (c *Cipher) Encrypt(plain string) (string, error)

Encrypt 加密明文,返回带标识的值,形如 enc:v1:<base64url> 空串原样返回:对空值加密只会产生一串看不懂的东西,没有意义

func (*Cipher) EncryptDeterministic

func (c *Cipher) EncryptDeterministic(plain string) string

EncryptDeterministic 确定性加密:同一明文恒产出同一密文

解决「既要跨行关联、又要出事能定位身份」这对需求: 随机 nonce 的 Encrypt 让同一 session_id 每次密文都不同,身份能还原但关联断了; 这里把 nonce 由明文确定性派生(SIV 构造),于是同一会话在所有日志行里 都是同一串密文,既能串起来,持密钥的人又能解出到底是哪个会话

nonce = HMAC-SHA256(独立派生的子密钥, 明文) 截断到 GCM 的 nonce 长度 确定性意味着**必然**泄漏「两个字段的值相等」,这正是可关联的前提; 若连相等关系都不能暴露,请用 Encrypt 或整体擦除

密文格式与 Encrypt 完全一致,因此 Decrypt 不需要区分两者

[EN] Resolves the tension between correlating across log lines and being able to identify the session during an incident: the random-nonce Encrypt yields a different ciphertext every time, so identity survives but correlation does not. Here the nonce is derived from the plaintext (an SIV construction), so one session is one ciphertext everywhere while a key holder can still resolve it. Determinism necessarily reveals that two values are equal — the very property that makes it correlatable; use Encrypt or wiping when even that must not leak. The output format matches Encrypt, so Decrypt needs no distinction 不返回 error:nonce 由明文经 HMAC 派生,不消耗随机数, 因此与 Encrypt 不同,这条路没有任何可失败的环节 给它配一个永远为 nil 的 error 只会让调用方写出永不执行的分支 [EN] No error return: the nonce is HMAC-derived from the plaintext and consumes no randomness, so unlike Encrypt nothing here can fail. An error that is always nil would only make callers write a branch that never runs

func (*Cipher) EncryptStruct

func (c *Cipher) EncryptStruct(obj interface{}) error

EncryptStruct 按 tag 加密结构体字段 等价于注册 cipher 脱敏器后调用 Desensitization,这里只是省掉注册步骤

obj 须为指向结构体的非 nil 指针

func (*Cipher) MustEncrypt

func (c *Cipher) MustEncrypt(plain string) string

MustEncrypt 加密明文,失败时回落为整体擦除

供不便处理错误的调用点使用(如实现 Desensitizer 接口) 失败时返回 RedactedText 而非原文:加密不可用时宁可什么都看不到, 也不能把明文漏出去——这是必须失败关闭的地方

type DataMasker

type DataMasker struct {
	// contains filtered or unexported fields
}

DataMasker 数据脱敏器

无锁且可安全并发使用:原实现带一个正则缓存与读写锁, 现在按字段名单趟扫描,不再编译任何正则

func NewMasker

func NewMasker(configs ...*MaskerConfig) *DataMasker

NewMasker 创建数据脱敏器(支持可选配置)

func (*DataMasker) AddSensitiveKeys

func (dm *DataMasker) AddSensitiveKeys(keys ...string) *DataMasker

AddSensitiveKeys 追加额外的敏感字段名

func (*DataMasker) Mask

func (dm *DataMasker) Mask(data []byte) string

Mask 脱敏数据(主入口)

func (*DataMasker) MaskBytes

func (dm *DataMasker) MaskBytes(data []byte) string

MaskBytes 脱敏字节数组

func (*DataMasker) MaskString

func (dm *DataMasker) MaskString(data string) string

MaskString 脱敏字符串

func (*DataMasker) WithMask

func (dm *DataMasker) WithMask(mask string) *DataMasker

WithMask 设置掩码文本

func (*DataMasker) WithMaxBodySize

func (dm *DataMasker) WithMaxBodySize(size int) *DataMasker

WithMaxBodySize 设置最大处理数据大小

func (*DataMasker) WithSensitiveKeys

func (dm *DataMasker) WithSensitiveKeys(keys ...string) *DataMasker

WithSensitiveKeys 设置额外的敏感字段名(整体替换) 内置规则表不受影响,始终生效

type DefaultDesensitizer

type DefaultDesensitizer struct {
	// contains filtered or unexported fields
}

DefaultDesensitizer 默认脱敏适配器,按脱敏类型分派到 Desensitize

func (*DefaultDesensitizer) Desensitize

func (e *DefaultDesensitizer) Desensitize(value string) string

Desensitize 按指定的脱敏类型处理输入值

type DesensitizeOptions

type DesensitizeOptions struct {
	CustomExtensionStartIndex int
	CustomExtensionEndIndex   int
	ChineseNameStartIndex     int
	IdCardStartIndex          int
	IdCardLength              int
	PhoneNumberStartIndex     int
	PhoneNumberEndIndex       int
	MobilePhoneStartIndex     int
	EmailStartIndex           int
	PEMBodyPrefixVisibleLen   int
	PEMBodySuffixVisibleLen   int
	APIKeyPrefixVisibleLen    int
	APIKeySuffixVisibleLen    int
	SecretPrefixVisibleLen    int
	SecretSuffixVisibleLen    int
	IDPrefixVisibleLen        int // ID 类(用户ID/玩家ID/订单号/UUID/OpenID/账号)前缀可见长度
	IDSuffixVisibleLen        int // ID 类后缀可见长度
}

DesensitizeOptions 脱敏选项

func NewDesensitizeOptions

func NewDesensitizeOptions() DesensitizeOptions

NewDesensitizeOptions 创建带有默认值的 DesensitizeOptions

type DesensitizeType

type DesensitizeType int
const (
	CustomExtension DesensitizeType = iota + 1 // 自定义扩展
	ChineseName                                // 中文名称
	IDCard                                     // 身份证号
	PhoneNumber                                // 手机号码
	MobilePhone                                // 移动电话号码
	Address                                    // 地址
	Email                                      // 邮箱
	Password                                   // 密码
	CarLicense                                 // 车牌号:油车、电车
	BankCard                                   // 银行卡号
	IPV4                                       // ipv4
	IPV6                                       // ipv6
	PEMKey                                     // PEM 密钥内容(私钥/证书等)
	APIKey                                     // API Key(保留首尾各若干位)
	Secret                                     // Secret Key(保留首尾各若干位)
	UserID                                     // 用户ID(保留首尾各若干位)
	PlayerID                                   // 玩家ID(保留首尾各若干位)
	OrderNo                                    // 订单号(保留首尾各若干位)
	UUID                                       // 通用唯一标识符/设备ID(保留首尾各若干位)
	OpenID                                     // 第三方开放平台ID(保留首尾各若干位)
	Account                                    // 账号(保留首尾各若干位)
)

type Desensitizer

type Desensitizer interface {
	Desensitize(value string) string
}

Desensitizer 脱敏器接口,输入一个值返回脱敏后的值

func NewDefaultDesensitizer

func NewDefaultDesensitizer(t DesensitizeType) Desensitizer

NewDefaultDesensitizer 以内置脱敏类型构造一个脱敏器

用于把内置类型注册到自定义的 tag 名下:

desensitize.RegisterDesensitizer("myType",
    desensitize.NewDefaultDesensitizer(desensitize.CustomExtension))

没有这个构造函数时包外无路可走:DefaultDesensitizer 的字段不导出, 复合字面量 &DefaultDesensitizer{CustomExtension} 在包外编译不过 上游文档里一直写着那种用法,但它从来没能编译 —— 这里把这条路补通

[EN] Wraps a built-in masking type so it can be registered under a custom tag name. Without this there is no way to do it from outside the package: the field is unexported, so the composite literal shown in upstream's docs never actually compiled

func NewJumpDesensitizer

func NewJumpDesensitizer(params ...int) Desensitizer

NewJumpDesensitizer 创建跳步脱敏器 参数依次为开始、结束、跳步,均可省略,省略时默认从头到尾连续掩码

type DesensitizerFactory

type DesensitizerFactory func(params ...int) Desensitizer

DesensitizerFactory 参数化脱敏器工厂 根据标签中的整数参数创建脱敏器,如 jump(3,-4,1) 中的 3、-4、1

type JumpDesensitizer

type JumpDesensitizer struct {
	// contains filtered or unexported fields
}

JumpDesensitizer 跳步脱敏器 按开始、结束、跳步参数对字符进行掩码,参数含义见 SensitizeJump

func (*JumpDesensitizer) Desensitize

func (j *JumpDesensitizer) Desensitize(value string) string

Desensitize 按跳步规则对输入值脱敏

type KeyAction

type KeyAction uint8

KeyAction 字段名命中规则后施加的动作

const (
	KeepAsIs        KeyAction = iota // 不处理
	WipeValue                        // 整体替换为 RedactedText
	MaskPhone                        // 号码掩码(保留末 4 位)
	MaskEmail                        // 邮箱掩码(保留首字符与域名)
	MaskFingerprint                  // 指纹掩码:稳定、不可逆、可跨行关联
	MaskEncrypt                      // 确定性加密:稳定、可关联,持密钥可还原身份
)

func ActionForKey

func ActionForKey(key string) KeyAction

ActionForKey 判定字段名对应的脱敏动作(零分配)

Go 对 m[string(byteSlice)] 有专门优化,归一化重查时的 string 转换不产生堆分配

func ActionForKeyLoose

func ActionForKeyLoose(key string) KeyAction

ActionForKeyLoose 宽松判定:在 ActionForKey 落空后,继续查字段名内部是否嵌有敏感词根

两档强度对应两类调用方,取舍方向相反:

  • ActionForKey(严格)给日志字段名用误擦会让 service_name、status_code 这类排障主线索消失,日志从此查不出问题,漏擦则由调用方补 RegisterSensitiveKeys
  • ActionForKeyLoose(宽松)给报文脱敏用:面对整段请求或响应体, my_token_field 这种中间位置的敏感词一旦漏擦就是真泄漏,误擦只是少看一个值

实现复用后缀字典树:树按反向字符构建,因此从每个结束位置各回溯一次, 等价于对那 26 条敏感词根做子串匹配,无需另立一张表 运维豁免同样生效,cache_key 不会因为内部含 key 被擦

[EN] Loose variant for body masking. The two entry points trade off in opposite directions: the strict one serves log field names, where a false positive erases the operator's diagnostic trail, while the loose one serves whole bodies, where a missed embedded root is a real leak. Implemented by replaying the reverse suffix trie from every end position, which is substring matching over the 26 sensitive roots without a second table

type MaskFunc

type MaskFunc func(value string) string

MaskFunc 已绑定某个标签的脱敏函数,可长期持有并并发调用

func ResolveRule

func ResolveRule(tag string) (MaskFunc, error)

type MaskerConfig

type MaskerConfig struct {
	// SensitiveKeys 额外的敏感字段名,按不区分大小写的子串匹配
	//
	// 内置规则表(见 keyrules.go)始终生效,本列表只做追加
	// 换句话说无法通过它收窄安全基线:脱敏库允许「漏擦」被意外配置出来是不可接受的
	// [EN] Extra key names, matched case-insensitively as substrings. The
	// built-in tables always apply; this list only adds to them, so the
	// security baseline cannot be narrowed by configuration
	SensitiveKeys []string

	SensitiveMask string // 掩码文本(默认 "***")
	MaxBodySize   int    // 最大处理数据大小(字节)
}

MaskerConfig 脱敏器配置

func DefaultMaskerConfig

func DefaultMaskerConfig() *MaskerConfig

DefaultMaskerConfig 返回默认配置 SensitiveKeys 为空:内置规则表已覆盖,无需再列一遍

type TagMisuseError

type TagMisuseError struct {
	Struct string // 结构体类型名
	Field  string // 字段名
	Tag    string // 该字段的 desensitize tag
	Type   string // 字段的实际类型
}

TagMisuseError 字段的 desensitize tag 与字段类型不匹配

脱敏器只接受字符串,因此带 tag 的字段(或切片元素、映射值)必须是字符串形态 命名字符串类型(type Name string)同样可用,判定按 reflect.Kind 而非具体类型

func (*TagMisuseError) Error

func (e *TagMisuseError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL