Fork me on GitHub

顶级企业如何用数据脱敏保护用户隐私!

0 前言

ShardingSphere提供数据访问安全性:通过数据脱敏,完成对敏感数据的安全访问。本文介绍ShardingSphere数据脱敏功能。

数据脱敏,指对敏感信息通过脱敏规则进行数据转换,实现敏感隐私数据的可靠保护。相较传统私有化部署方案,互联网应用对数据安全要求更高,涉及范围更广。根据行业和业务场景属性,不同系统敏感信息不同,但诸如身份证号、手机号、卡号、用户姓名、账号密码等个人信息一般都需脱敏。

1 咋抽象数据脱敏?

先梳理实现数据脱敏的抽象过程。从这三维抽象数据脱敏:

  • 敏感数据存储方式
  • 敏感数据的加解密过程
  • 在业务代码中嵌入加解密的过程

针对每维,将基于 ShardingSphere 给出这框架的具体抽象过程,以便理解使用它的方法和技巧。

1.1 咋存储?

讨论点在于,是否需将敏感数据以明文存储在数据库。答案并非绝对。

① 直接密文

先考虑第一种情况。对一些敏感数据,我们显然应直接密文形式将加密后的数据存储,防止任何途径能从数据库获取明文。这类敏感数据,最典型的就是用户密码,通常采用 MD5 等不可逆加密算法,而使用这些数据的方法也只是依赖其密文形式,不涉及明文直接处理。

② 一列明文,一列密文

但对用户姓名、手机号等信息,由于统计分析等需要,显然不能直接采用不可逆加密算法,还需处理明文信息。常见地将一个字段用两列保存:

  • 一列明文
  • 一列密文

可将第一种情况看作第二种情况的特例。即第一种情况无明文列,仅密文列。ShardingSphere基于俩情况进行抽象:

  • 明文列命为 plainColumn,选填
  • 密文列命为 cipherColumn,必填

ShardingSphere 还提出一个逻辑列 logicColumn,代表一种虚拟列,只面向开发人员编程使用。

1.2 咋加解密?

数据脱敏本质上就是一种加解密技术应用场景,自然少不了对各种加解密算法和技术的封装。传统的加解密方式有两种,一种是对称加密,常见的包括 DEA 和 AES;另一种是非对称加密,常见的包括 RSA。

ShardingSphere抽象一个 ShardingEncryptor 组件封装各种加解密操作:

public interface ShardingEncryptor extends TypeBasedSPI {

    void init();

    String encrypt(Object plaintext);

    Object decrypt(String ciphertext);
}

ShardingSphere 内置 AESShardingEncryptor、MD5ShardingEncryptor具体实现。由于扩展了TypeBasedSPI接口,所以可基于微内核架构和 JDK SPI 来实现和动态加载自定义的 ShardingEncryptor。

1.3 业务代码咋嵌入数据脱敏?

显然这过程应尽量:

  • 自动化
  • 低侵入性
  • 对开发透明

我们可以通过一个具体的示例来描述数据脱敏的执行流程。假设系统中存在一张 user 表,其中包含一个 user_name 列。我们认为这个 user_name 列属于敏感数据,需要对其进行数据脱敏。那么按照前面讨论的数据存储方案,可以在 user 表中设置两个字段,一个代表明文的 user_name_plain,一个代表密文的 user_name_cipher。然后应用程序通过 user_name 这个逻辑列与数据库表进行交互:三种数据列交互方式示意图

针对这个交互过程,我们希望存在一种机制,能够自动将 user_name 逻辑列映射到 user_name_plain 和 user_name_cipher 列。同时,我们希望提供一种配置机制,能够让开发人员根据需要灵活指定脱敏过程中所采用的各种加解密算法。

ShardingSphere就提供这样的机制:

  • ShardingSphere解析应用程序传入的SQL,并依据开发提供的脱敏配置去改写SQL,实现对明文数据自动加密
  • 将加密后的密文数据存储到数据库
  • 当我们查询数据,它又从数据库取出密文,并自动解密,最终将解密后明文返给用户

ShardingSphere 提供自动化+透明化的数据脱敏过程,业务开发可像用普通数据使用脱敏数据,无需关注数据脱敏实现细节。

2 系统改造:咋实现数据脱敏?

2.1 准备数据脱敏

为演示数据脱敏,定义一个 EncryptUser 实体类,定义与数据脱敏相关的常见用户名、密码等字段,与数据库encrypt_user表列对应:

public class EncryptUser {
    //用户Id
    private Long userId;
    //用户名(密文)
    private String userName;
    //用户名(明文)
    private String userNamePlain;
    //密码(密文)
    private String pwd;
	…
}

EncryptUserMapper关于 resultMap 和 insert 语句的定义:

<mapper namespace="com.demo.repository.EncryptUserRepository">
    <resultMap id="encryptUserMap" type="com.demo.entity.EncryptUser">
        <result column="user_id" property="userId" jdbcType="INTEGER"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="pwd" property="pwd" jdbcType="VARCHAR"/>
    </resultMap> 
    <insert id="addEntity">
        INSERT INTO encrypt_user (user_id, user_name, pwd) VALUES (#{userId,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR})
    </insert>
       … 
</mapper>
@Service
public class EncryptUserServiceImpl implements EncryptUserService { 
    @Autowired
    private EncryptUserRepository encryptUserRepository;

  	// 插入用户
    @Override
    public void processEncryptUsers() throws SQLException {
       insertEncryptUsers();
    }

    private List<Long> insertEncryptUsers() throws SQLException {
       List<Long> result = new ArrayList<>(10);
        for (Long i = 1L; i <= 10; i++) {
         EncryptUser encryptUser = new EncryptUser();
         encryptUser.setUserId(i);
         encryptUser.setUserName("username_" + i);
         encryptUser.setPwd("pwd" + i);
            encryptUserRepository.addEntity(encryptUser);
            result.add(encryptUser.getUserId());
        }

        return result;
    }
  
  	// 获取用户列表
    @Override
    public List<EncryptUser> getEncryptUsers() throws SQLException {
       return encryptUserRepository.findEntities();
    }
}

数据脱敏功能内嵌在sharding-jdbc-spring-boot-starter:

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.0.0</version>
</dependency>

2.2 配置数据脱敏

整体架构和分库分表及读写分离一样,数据脱敏对外暴露的入口也是符合JDBC规范的EncryptDataSource。

ShardingSphere 提供 EncryptDataSourceFactory 完成 EncryptDataSource 对象构建:

public final class EncryptDataSourceFactory {

    DataSource createDataSource(DataSource dataSource, EncryptRuleConfiguration encryptRuleConfiguration, Properties props) {
        return new EncryptDataSource(dataSource, new EncryptRule(encryptRuleConfiguration), props);
    }
}

EncryptRuleConfiguration类包含两个 Map:

// 加解密器配置列表
private final Map<String, EncryptorRuleConfiguration> encryptors;
// 加密表配置列表
private final Map<String, EncryptTableRuleConfiguration> tables;

EncryptorRuleConfiguration集成了ShardingSphere的通用抽象类TypeBasedSPIConfiguration,包含type、properties字段:

// 类型(如MD5/AES加密器)
private final String type;
// 属性(如AES加密器用到的Key值)
private final Properties properties;

EncryptTableRuleConfiguration持有一个包含多个 EncryptColumnRuleConfiguration 的 Map,EncryptColumnRuleConfiguration 就是 ShardingSphere 对加密列的配置,包含plainColumn、cipherColumn定义:

public final class EncryptColumnRuleConfiguration {
    // 存储明文的字段
    private final String plainColumn;
    // 存储密文的字段
    private final String cipherColumn;
    // 辅助查询字段
    private final String assistedQueryColumn;
    // 加密器名字
    private final String encryptor;
}

各配置类关系,数据脱敏所需配置项:

定义数据源dsencrypt

spring.shardingsphere.datasource.names=dsencrypt 
spring.shardingsphere.datasource.dsencrypt.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsencrypt.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsencrypt.jdbc-url=jdbc:mysql://localhost:3306/dsencrypt
spring.shardingsphere.datasource.dsencrypt.username=root
spring.shardingsphere.datasource.dsencrypt.password=root

配置加密器

定义name_encryptor、pwd_encryptor加密器分别对user_name、pwd列加解密:

# 对name_encryptor使用对称加密算法AES
spring.shardingsphere.encrypt.encryptors.name_encryptor.type=aes
spring.shardingsphere.encrypt.encryptors.name_encryptor.props.aes.key.value=123456
# 对pwd_encryptor,我们则直接使用不可逆的 MD5 散列算法:
spring.shardingsphere.encrypt.encryptors.pwd_encryptor.type=md5

脱敏表配置

针对案例场景,可选择:

  • user_name列设置plainColumn、cipherColumn及encryptor属性
  • pwd列,由于不希望在数据库存储明文,所以配置cipherColumn、encryptor
spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.plainColumn=user_name_plain
spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.cipherColumn=user_name
spring.shardingsphere.encrypt.tables.encrypt_user.columns.user_name.encryptor=name_encryptor
spring.shardingsphere.encrypt.tables.encrypt_user.columns.pwd.cipherColumn=pwd
spring.shardingsphere.encrypt.tables.encrypt_user.columns.pwd.encryptor=pwd_encryptor

ShardingSphere提供属性开关,当底层数据库表里同时存储了明文和密文数据后,该属性开关可以决定是直接查询数据库表里的明文数据进行返回,还是查询密文数据并进行解密之后再返回:

spring.shardingsphere.props.query.with.cipher.comlum=true

2.3 执行数据脱敏

执行测试用例。先执行数据插入,下图数据表中对应字段存储加密后的密文:

这过程,ShardingSphere会把原SQL语句转换为用于数据脱敏的目标语句:

再执行查询语句并获取控制台日志:

2024-11-16 21:20:49.782  INFO 68311 --- [           main] ShardingSphere-SQL                       : Rule Type: encrypt
2024-11-16 21:20:49.782  INFO 68311 --- [           main] ShardingSphere-SQL                       : SQL: SELECT * FROM test_db.encrypt_user;
user_id: 1, user_name: test_1, pwd: 99024280cab824efca53a5d1341b9210
user_id: 2, user_name: test_2, pwd: 36ddda5af915d91549d3ab5bff1bafec
…

路由类型“encrypt”,获取的 user_name 是解密后的明文,而非数据库存储的密文,即spring.shardingsphere.props.query.with.cipher.comlum=true配置项作用。若配置项置false,就返回密文。

3 总结

数据脱敏是数据库管理和数据访问控制的一个重要话题,今天我们讲解了 ShardingSphere 在数据脱敏方面提供的技术方案,但实际上,数据脱敏的实现思路有很多,ShardingSphere 采用了一种自动化、透明化的方案完成敏感数据存储、加解密以及和应用程序之间的无缝整合。同时,今天的课程也围绕系统案例对其进行了数据库脱敏改造,我们给出了具体的配置项和执行过程。

4 实现方式集

在使用 ShardingSphere 的数据脱敏模块(Data Masking)进行数据加密时,可以通过以下几种方式设置需要加密的数据项:


1. 基于 SQL 配置规则

通过在 SQL 语句中动态添加脱敏规则配置项。例如:

ALTER TABLE user ADD COLUMN encrypted_column VARBINARY(255);

在运行时通过 SQL 的形式配置指定列的脱敏或加密规则。此方式适合临时性或动态规则配置。


2. 配置文件中静态配置

通过 YAML 或 Java 配置静态指定加密数据项。以下是 YAML 配置示例:

YAML 配置示例

encryptRule:
  tables:
    user:
      columns:
        password:
          plainColumn: plain_password
          cipherColumn: cipher_password
          encryptorName: aes_encryptor
  encryptors:
    aes_encryptor:
      type: AES
      props:
        aes-key-value: 123456abc

Java 代码配置示例

如果你使用 Java 编程动态配置规则,可以通过如下代码设置:

EncryptRuleConfiguration encryptRuleConfig = new EncryptRuleConfiguration();
EncryptColumnRuleConfiguration columnConfig = new EncryptColumnRuleConfiguration("plain_password", "cipher_password", "aes_encryptor");
encryptRuleConfig.getTables().put("user", new EncryptTableRuleConfiguration(Map.of("password", columnConfig)));

此方式适合静态规则配置,适用场景较广。


3. 在前端代码中配置脱敏规则

通过在调用 ShardingSphere Proxy 时,在前端客户端(如 JDBC 应用程序)配置脱敏规则并执行数据查询或插入操作。例如:

// 在 JDBC 中绑定加密规则
String query = "SELECT AES_ENCRYPT('plain_text', 'key') AS cipher_column;";
ResultSet rs = statement.executeQuery(query);

4. 通过 ShardingSphere 控制台配置(推荐)

在使用 ShardingSphere-UIShardingSphere-Proxy 时,直接通过其管理控制台添加或修改脱敏规则。可以实现对某些列动态指定加密规则,例如:

  • 登录控制台
  • 选择目标数据源
  • 在加密规则模块中为指定列配置 PlainColumnCipherColumn,并选择加密器(如 AES、MD5 等)

5. 动态规则加载

通过 SPI(Service Provider Interface)机制自定义脱敏规则和算法。例如,如果现有规则无法满足需求,可以实现一个自定义加密算法:

自定义加密器示例

public final class CustomEncryptAlgorithm implements EncryptAlgorithm {

    @Override
    public String encrypt(Object plaintext, Properties props) {
        // 自定义加密逻辑
        return Base64.getEncoder().encodeToString(plaintext.toString().getBytes());
    }

    @Override
    public String decrypt(String ciphertext, Properties props) {
        // 自定义解密逻辑
        return new String(Base64.getDecoder().decode(ciphertext));
    }
}

在配置中引入该算法即可。


总结

主要有以下几种方式可以设置需要加密的数据项:

  1. SQL 动态配置
  2. YAML/Java 静态配置
  3. 前端代码中直接配置
  4. ShardingSphere 控制台(UI/Proxy)配置
  5. 自定义加密规则,通过 SPI 动态加载

根据具体场景和需求选择。

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化
  • 活动&券等营销中台建设
  • 交易平台及数据中台等架构和开发设计
  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化
  • LLM Agent应用开发
  • 区块链应用开发
  • 大数据开发挖掘经验
  • 推荐系统项目

目前主攻市级软件项目设计、构建服务全社会的应用系统。

参考:

本文由博客一文多发平台 OpenWrite 发布!

posted @ 2024-11-17 19:13  公众号-JavaEdge  阅读(12)  评论(0编辑  收藏  举报