掘金 后端 ( ) • 2024-05-18 16:36

highlight: magula theme: cyanosis

前言

在一些常见的业务场景中可能涉及到用户的手机号,银行卡号等敏感数据,对于这部分的数据经常需要进行数据脱敏处理,就是将此部分数据隐私化,防止数据泄露。但是在生产环境中,数据一般都是实时的,且在进行脱敏操作时不能影响正常的业务使用。

脱敏方案

脱敏方案一般会采用以下方式:

  • 数据部分脱敏:只对部分内容脱敏,保留一些关键部分,比如人名保留姓氏,其他使用星号或者其他占位符代替。
  • 数据完全脱敏:对整个数据进行脱敏,完全隐藏所有数据,比如整个数据都是用占位符代替或者是用其他随机生成的数据替换。
  • 数据加密:直接对数据进行加密,其实就是完全脱敏。加密方式更多用在数据传输或存储的过程中。比如接口传输时加密某字段数据防止请求被拦截时数据泄露。
  • 数据删除,对于一些没有必要且不再使用的数据应当直接删除。

详细设计

基本脱敏方法

就目前我所使用的场景,遇到比较多的是将在展示时对数据进行部分脱敏后再展示,使用占位符替换。

// 部分数据脱敏
func PlaceholderHandle(data string) string {
    if len(data) <= 4 {
    		return strings.Repeat("*", len(data))
    	}
    	return data[:3] + strings.Repeat("*", len(data)-7) + data[len(data)-4:]
}

// 完全数据脱敏
func PlaceFullHandle(){
  return strings.Repeat("*", len(data))  
}

// 使用MD5对数据进行加密
func GetMD5Hash(data string) string {
	hash := md5.Sum([]byte(text))
	return hex.EncodeToString(hash[:])
}

// 使用AES算法对数据进行加密
func Encrypt(data []byte, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	ciphertext := make([]byte, aes.BlockSize+len(data))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], data)

	return ciphertext, nil
}

// 使用AES算法对数据进行解密
func Decrypt(data []byte, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	if len(data) < aes.BlockSize {
		return nil, fmt.Errorf("ciphertext too short")
	}

	iv := data[:aes.BlockSize]
	data = data[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(data, data)

	return data, nil
}

md5 是不可逆的,所以如果使用MD5 加密的话就数据就无法解密,如果使用MD5加密的话更适合的场景就是用于数据加密后没有其他用处。所以如果希望加密后再解密可以使用AES 对称加密算法进行加解密,其还支持使用Key 进行加密。

脱敏模块

场景

  • 项目中很多个模块都需要进行数据脱敏
  • 数据脱敏的规则复杂,不同数据类型,不同场景,不同角色有不同的脱敏规则
  • 脱敏规则多变时需要有个统一管理中心,可以增加其灵活性,特别遇到动态更新的需求时可以增加可配置功能

功能模块划分:

当遇到以上点可以考虑设计一个专门进行数据脱敏的模块,以便统一管理和调度数据脱敏逻辑从而保证数据脱敏的一致性和规范性。一个简单的数据脱敏模块大致划分三个模块:

  • 数据源:获取数据源,来源支持文件,数据库或其他数据源
  • 解析器:解析数据源并对提取需要脱敏的字段
  • 脱敏策略引擎:根据不同的规则对数据进行脱敏处理

数据源可以使用 工厂函数,根据数据源类型创建对应数据源实例从而调用方法 GetData 获取相应的数据源,后续可以增加其他数据源时只需要再工厂函数增加类型和对应的实例创建。

type DataSource interface {
	GetData() []byte
}

type UserDbSource struct {
}

func (p *UserDbSource) GetData() []byte {
	return nil
}

type OrderDbSource struct {
	dbName string
}

func (p *OrderDbSource) GetData() []byte {
	return nil
}

type ThirdpartySource struct {
	url string
}

func (p *ThirdpartySource) GetData() []byte {
	return nil
}

type DataSourceFactory struct {
}

func (p *DataSourceFactory) CreateDataSource(sourceType string, sourceConfig string) DataSource {
	switch sourceType {
	case "file":
		return &FileSource{filePath: sourceConfig}
	case "thirdparty":
		return &ThirdpartySource{url: sourceConfig}
	case "orderDb":
		return &OrderDbSource{dbName: sourceConfig}
	case "userDb":
		return &UserDbSource{}
	}
	return nil
}

数据脱敏引擎使用策略模式,先定义不同的脱敏规则,并通过DataMaskCore接口统一了脱敏方法。在初始化DataMask结构体可以根据传入不同脱敏策略,然后在进行脱敏处理时根据不同的脱敏规则进行处理。这样可以实现根据不同规则进行不同的脱敏处理,使代码更加灵活和可扩展。

type DataMaskCore interface {
	DoMask(data string)
}

// 脱敏规则1
type MaskingRule1 struct{}

func (mr1 *MaskingRule1) DoMask(data string) string {
	// 实现脱敏规则1的逻辑
	return "Masked Data 1"
}

// 脱敏规则2
type MaskingRule2 struct{}

func (mr2 MaskingRule2) DoMask(data string) string {
	// 实现脱敏规则2的逻辑
	return "Masked Data 2"
}

type DataMask struct {
	Strategy DataMaskCore
}

func NewDataMask(strategy DataMaskCore) *DataMask {
	return &DataMask{
		Strategy: strategy,
	}
}

// 根据不同规则进行不同的脱敏处理
func (p *DataMask) MaskData(data string) {
	p.Strategy.DoMask(data)
}

简单概述,数据脱敏其实本质就是对数据不可直接展示。根据自身需求对数据敏感度,可使用简单处理方式就是对数据进行占位符代替,或者直接加密处理。如果是没有用处的数据直接暴力解决—删除。