ShardingSphere 实现数据加密(脱敏)第二篇

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 通过 Apache ShardingSphere,针对新增的数据,会把明文写到 pwd 列,并同时把明文进行加密存储到 pwd_cipher 列。此时,由于 queryWithCipherColumn 设置为 false,对业务应用来说,依旧使用 pwd 这一明文列进行查询存储,却在底层数据库表 pwd_cipher 上额外存储了新增数据的密文数据

上一篇文章中说道数据加密分两种场景 ShardingSphere 实现数据加密(脱敏)第一篇

分别是:


  • 新上线业务
  • 已上线业务


这篇我们对已上线业务进行模拟实验。


已上线业务改造


系统迁移前


建表语句和配置文件


CREATE TABLE `t_cipher_old` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `pwd` varchar(100) DEFAULT NULL,
  `mobile` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


为了模拟已经上线的业务,我们为表中造一些测试数据,并编写业务接口实现 CURD


27.jpg


然而需要在数据库表 t_user 里新增一个字段叫做 pwd_cipher,即 cipherColumn,用于存放密文数据,同时我们把 plainColumn 设置为 pwd,用于存放明文数据,而把 logicColumn 也设置为 pwd。


ALTER TABLE test.t_cipher_old ADD pwd_cipher varchar(100) NULL;


由于之前的代码 SQL 就是使用 pwd 进行编写,即面向逻辑列进行 SQL 编写,所以业务代码无需改动。通过 Apache ShardingSphere,针对新增的数据,会把明文写到 pwd 列,并同时把明文进行加密存储到 pwd_cipher 列。此时,由于 queryWithCipherColumn 设置为 false,对业务应用来说,依旧使用 pwd 这一明文列进行查询存储,却在底层数据库表 pwd_cipher 上额外存储了新增数据的密文数据

配置文件如下(本文只需要关注 encrypt 节点部分):


spring:
  profiles:
    include: common-local
  shardingsphere:
    datasource:
      names: write-ds,read-ds-0
      write-ds:
        jdbcUrl: jdbc:mysql://mysql.local.test.myapp.com:23306/test?allowPublicKeyRetrieval=true&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: Qq2e66hxnNd9MdNc
        connectionTimeoutMilliseconds: 3000
        idleTimeoutMilliseconds: 60000
        maxLifetimeMilliseconds: 1800000
        maxPoolSize: 50
        minPoolSize: 1
        maintenanceIntervalMilliseconds: 30000
      read-ds-0:
        jdbcUrl: jdbc:mysql://mysql.local.test.read1.glzhapp.com:23306/test?allowPublicKeyRetrieval=true&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: Qq2e66hxnNd9MdNc
        connectionTimeoutMilliseconds: 3000
        idleTimeoutMilliseconds: 60000
        maxLifetimeMilliseconds: 1800000
        maxPoolSize: 50
        minPoolSize: 1
        maintenanceIntervalMilliseconds: 30000
    rules:
      readwrite-splitting:
        data-sources:
          glapp:
            write-data-source-name: write-ds
            read-data-source-names:
              - read-ds-0
            load-balancer-name: roundRobin # 负载均衡算法名称
        load-balancers:
          roundRobin:
            type: ROUND_ROBIN # 一共两种一种是 RANDOM(随机),一种是 ROUND_ROBIN(轮询)
      encrypt:
        encryptors:
          pwd-encryptor:
            props:
              aes-key-value: 123456abc
            type: AES
        tables:
          t_cipher_old:
            columns:
              pwd: # pwd 与 pwd_cipher 的转换映射
                plain-column: pwd # 原文列名称
                cipher-column: pwd_cipher # 加密列名称
                encryptor-name: pwd-encryptor # 加密算法名称(名称不能有下划线)
        queryWithCipherColumn: false # 是否使用加密列进行查询。在有原文列的情况下,可以使用原文列进行查询


此时调用业务接口,新插入的数据就会在明文列 pwd 和加密列 pwd_cipher 同时存储数据。


28.jpg


上面整个的处理流程如下图所示:


29.jpg


至此,改造以后时间点进入的数据都是加密的了。


系统迁移中


将旧的数据自行加密处理


具体到我们这个例子来讲,需要手动将 pwd 字段未加密的值全部手动加密后将密文存储到 pwd_cipher.形象地说,就是将空的位置手动补齐。


30.jpg


首先我们参考 ShardingSphere 的 AES 加解密码算法改造了一个工具类:


/**
 * AES 加解密
 *
 * @author xiaohezi
 * @since 2021-09-23 15:49
 */
public class AesUtils {
    private static byte[] createSecretKey(String aesKey) {
        return Arrays.copyOf(DigestUtils.sha1(aesKey), 16);
    }
    /**
     * AES 加密方法
     *
     * @param plaintext 加密文本
     * @param aesKey    加密 key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws NoSuchPaddingException
     * @throws IllegalBlockSizeException
     */
    public static Object encrypt(String plaintext, String aesKey) throws NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException {
        try {
            if (null == plaintext) {
                return null;
            } else {
                byte[] result = getCipher(1, aesKey).doFinal(StringUtils.getBytesUtf8(plaintext));
                return Base64.encodeBase64String(result);
            }
        } catch (GeneralSecurityException var3) {
            throw var3;
        }
    }
    /**
     * AES 解密方法
     *
     * @param ciphertext 密码
     * @param aesKey     加密 Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws NoSuchPaddingException
     * @throws IllegalBlockSizeException
     */
    public static Object decrypt(String ciphertext, String aesKey) throws NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException {
        try {
            if (null == ciphertext) {
                return null;
            } else {
                byte[] result = getCipher(2, aesKey).doFinal(Base64.decodeBase64(ciphertext));
                return new String(result, StandardCharsets.UTF_8);
            }
        } catch (GeneralSecurityException var3) {
            throw var3;
        }
    }
    private static Cipher getCipher(int decryptMode, String aesKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
        Cipher result = Cipher.getInstance(getType());
        result.init(decryptMode, new SecretKeySpec(createSecretKey(aesKey), getType()));
        return result;
    }
    public static String getType() {
        return "AES";
    }
}


然后为了简单演示,我的思路是用 java 程序将数据查出来以后直接更新,查询简单,更新的话用 mybatisplus 的 mapper 简单写了个自定义 sql 的方法


/**
 * 根据 id 将密码的密文更新
 *
 * @param id
 * @param pwdCipher
 */
@Update("update t_cipher_old set pwd_cipher =#{pwdCipher}  where id = #{id}")
void updateCipher(@Param("id") Long id, @Param("pwdCipher") String pwdCipher);

下面是更新方法,注意我这里的 aesKey 和上面的配置文件是保持一致的。

@Override
public void updateOldPwd() {
    QueryWrapper<CipherOldDO> wrapper = new QueryWrapper<>();
    wrapper.isNull("pwd_cipher");
    List<CipherOldDO> list = list(wrapper);
    String aesKey = "123456abc";
    try {
        for (CipherOldDO cipherOldDO : list) {
            Object encrypt = AesUtils.encrypt(cipherOldDO.getPwd(), aesKey);
            //更新密码的密文
            getBaseMapper().updateCipher(cipherOldDO.getId(), encrypt.toString());
        }
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
}


程序执行完,加密列 pwd_cipher 就有数据了。


31.jpg


由于配置项中的 queryWithCipherColumn = false,所以密文一直没有被使用过。如果我们为了让系统能切到密文数据进行查询,需要将加密配置中的 queryWithCipherColumn 设置为 true。


虽然现在业务系统通过将密文列的数据取出,解密后返回;但是,在存储的时候仍旧会存一份原文数据到明文列,这是为什么呢?答案是:为了能够进行系统回滚。因为只要密文和明文永远同时存在,我们就可以通过开关项配置自由将业务查询切换到 cipherColumn 或 plainColumn。也就是说,如果将系统切到密文列进行查询时,发现系统报错,需要回滚。那么只需将 queryWithCipherColumn = false,Apache ShardingSphere 将会还原,即又重新开始使用 plainColumn 进行查询。处理流程如下图所示:


32.jpg


系统迁移后


业务系统一般不可能让数据库的明文列和密文列永久同步保留,我们需要在系统稳定后将明文列数据删除。


但是删除列对于业务代码来说是不需要发动的,因为有 logicColumn 存在,用户的编写 SQL 都面向这个虚拟列,Apache ShardingSphere 就可以把这个逻辑列和底层数据表中的密文列进行映射转换。于是迁移后的加密配置即为:


encrypt:
    encryptors:
        pwd-encryptor:
        props:
            aes-key-value: 123456abc
        type: AES
    tables:
        t_cipher_old:
        columns:
            pwd: # pwd 与 pwd_cipher 的转换映射
            cipher-column: pwd_cipher # 加密列名称
            encryptor-name: pwd-encryptor # 加密算法名称(名称不能有下划线)
    queryWithCipherColumn: true # 是否使用加密列进行查询。在有原文列的情况下,可以使用原文列进行查询


在数据库中直接将 pwd 列删除


33.jpg


可以看到已经没有 pwd 列的,只剩下加过密的 pwd_cipher , 从数据库这里我们已经看不出密码是什么了。然后我们调用查询接口,看到数据:


{
    "code": 100000,
    "msg": "",
    "data": [
        {
            "id": 1,
            "name": "Tara",
            "pwd": "dogT",
            "mobile": "+425(864)267-129",
            "createTime": "1994-12-02 18:39:01",
            "updateTime": "2021-09-23 16:45:08"
        },
        {
            "id": 2,
            "name": "Earl",
            "pwd": "ju",
            "mobile": "+17(252)465-481",
            "createTime": "2016-10-05 15:15:43",
            "updateTime": "2021-09-23 16:45:08"
        },
        {
            "id": 3,
            "name": "Roberta",
            "pwd": "fo",
            "mobile": "+44(296)354-787",
            "createTime": "2008-10-09 17:21:36",
            "updateTime": "2021-09-23 16:45:08"
        },
        {
            "id": 4,
            "name": "Travis",
            "pwd": "brow",
            "mobile": "+77(975)452-214",
            "createTime": "2005-02-17 07:14:24",
            "updateTime": "2021-09-23 16:45:08"
        }
    ]
}


可以看到,数据是解密以后的样子。


其处理流程如下:


34.jpg



相关文章
|
5月前
|
存储 大数据 API
大数据隐私保护策略:加密、脱敏与访问控制实践
【4月更文挑战第9天】本文探讨了大数据隐私保护的三大策略:数据加密、数据脱敏和访问控制。数据加密通过加密技术保护静态和传输中的数据,密钥管理确保密钥安全;数据脱敏通过替换、遮蔽和泛化方法降低敏感信息的敏感度;访问控制则通过用户身份验证和权限设置限制数据访问。示例代码展示了数据库、文件系统和API访问控制的实施方式,强调了在实际应用中需结合业务场景和平台特性定制部署。
1293 0
|
数据安全/隐私保护
|
SQL 存储 关系型数据库
ShardingSphere 实现数据加密(脱敏)第一篇
Apache ShardingSphere 通过对用户输入的 SQL 进行解析,并依据用户提供的加密规则对 SQL 进行改写,从而实现对原文数据进行加密,并将原文数据(可选)及密文数据同时存储到底层数据库。在用户查询数据时,它仅从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用户。Apache ShardingSphere 自动化 & 透明化了数据加密过程,让用户无需关注数据加密的实现细节,像使用普通数据那样使用加密数据。此外,无论是已在线业务进行加密改造,还是新上线业务使用加密功能,Apache ShardingSphere 都可以提供一套相对完善的解决方案。
ShardingSphere 实现数据加密(脱敏)第一篇
|
4天前
|
安全 网络协议 网络安全
网络安全与信息安全:漏洞、加密与意识的三重奏
【9月更文挑战第32天】在数字世界的交响乐中,网络安全是那不可或缺的乐章。本文将带您深入探索网络安全的三大主题:网络漏洞的识别与防范、加密技术的奥秘以及安全意识的重要性。通过深入浅出的方式,我们将一起揭开这些概念的神秘面纱,并学习如何在实际生活中应用它们来保护自己的数字足迹。让我们开始这场既刺激又富有教育意义的旅程,提升个人和组织的网络安全防御能力。
|
5天前
|
存储 安全 网络安全
揭秘网络安全的盾牌与剑:漏洞防御与加密技术
【9月更文挑战第31天】在数字时代的浪潮中,网络安全和信息安全成为了保护个人隐私和企业资产的重要屏障。本文将通过浅显易懂的语言和生动的比喻,带你深入了解网络安全漏洞、加密技术的奥秘,以及如何培养安全意识。我们将一起探索网络安全的“盾牌”和“剑”,了解它们如何守护我们的数字世界。
110 61
|
2天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【9月更文挑战第34天】在数字化时代,网络安全与信息安全的重要性日益凸显。本文将探讨网络安全漏洞、加密技术以及安全意识等关键方面,旨在提升读者对网络安全防护的认识和理解。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调培养良好的安全意识的必要性,本文旨在为读者提供实用的知识和建议,以应对日益复杂的网络威胁。
|
2天前
|
安全 算法 网络安全
网络安全的盾牌:从漏洞到加密,构筑信息安全长城
【9月更文挑战第34天】在数字时代的浪潮中,网络安全成为保护个人和组织数据不受侵犯的关键。本文将深入探讨网络安全中的漏洞发现、利用与防范,介绍加密技术的原理与应用,并强调培养安全意识的重要性。我们将通过实际代码示例,揭示网络攻防的复杂性,并提供实用的防护策略,旨在提升读者对网络安全的认识和应对能力。
29 10
|
4天前
|
SQL 安全 算法
网络安全的盾牌:揭秘加密技术与安全意识的重要性
【9月更文挑战第32天】在数字时代的浪潮中,网络安全成为维护个人隐私和企业资产的关键防线。本文将深入探讨网络安全漏洞的成因与危害,解析加密技术的核心原理及其在防御策略中的应用,同时强调培养安全意识的必要性。通过深入浅出的方式,带领读者理解网络安全的复杂性,以及如何通过技术和意识的双重保障来构建更为坚固的网络防护墙。
|
3天前
|
存储 安全 算法
网络安全与信息安全:漏洞、加密与意识的三维防线
【9月更文挑战第33天】在数字化浪潮中,网络安全与信息安全成为守护数据宝藏的坚固盾牌。本文将深入探讨网络防御的三大支柱:安全漏洞的识别与防范,加密技术的应用和原理,以及提升个人和组织的安全意识。通过这些知识的分享,我们旨在为读者提供一套全面的网络安全策略,确保数字资产的安全无虞。