在真实业务场景中,为了符合企业数据安全方面的要求,相关业务开发团队则往往需要针对公司安全需求,自行实行并维护一套加解密系统。但是当加密场景发生改变时,一方面,研发团队自行维护的加密系统往往会面临着重构或修改风险;另外一方面,对于已经上线的业务,在不修改业务逻辑和 SQL 的情况下,透明化、安全低风险地实现无缝进行加密改造,也相对复杂。
因此,在这种情况下,需要一种更加灵活和易于维护的加密方案,以便在加密场景发生改变时能够轻松地进行修改和适应。对于一些安全级别更高的非幂等加密算法,提供不可逆的幂等列用于查询,即查询辅助列。
同时为了提升功能的易用性,SphereEx-DBPlusEngine 数据加密插件提供了云端密钥管理、洗数以及反洗数能力。笔者将从数据加密的基本概念出发,结合 SphereEx-DBPlusEngine 数据加密的原理和架构,着重讲述在不同业务阶段 SphereEx-DBPlusEngine 数据加密的部署方式。
数据加密的基本概念
· 逻辑列
用于计算加解密列的逻辑名称,是 SQL 中列的逻辑标识。 逻辑列包含密文列(必须)、查询辅助列(可选)和明文列(可选)。
· 逻辑列类型(dataType)
用于定义逻辑列的类型,例如 INT NOT NULL,VARCHAR(200) DEFAULT NULL 等,具体可以参见官方文档中各种方言字段的定义。
· 密文列(cipherColumn)
加密后的数据列。
· 密文列类型(cipherDataType)
用于定义密文列的类型,同逻辑列类型。
· 查询辅助列(assistedQueryColumn)
用于查询的辅助列。对于一些安全级别更高的非幂等加密算法,提供不可逆的幂等列用于查询。
· 查询辅助列类型(assistedQueryDataType)
用于定义查询辅助列类型,可结合实际需要进行人工定义。
· 模糊查询列
对于需要模糊查询的场景,该字段用于实现模糊查询。
· 模糊查询列类型
用于定义模糊查询列类型,可结合实际需要进行人工定义。
· 明文列(plainColumn)
存储明文的列,用于在加密数据迁移过程中仍旧提供服务。 在清洗数据结束后可以删除。
· 明文列类型(plainDataType)
用于定义明文列类型,同逻辑列类型。
· 加密算法
指对数据加密过程中,所使用的具体加密方式,如 AES 及 MD5 等。
· 加密洗数(encrypting)
对数据库中未加密数据进行批量加密。
· 解密洗数(decrypting)
对数据库中已加密数据进行批量解密。
· 密钥管理
密钥的存放及管理方式。
详解 SphereEx-DBPlusEngine 数据加密架构和原理
SphereEx-DBPlusEngine 数据加密整体架构
下面进入干货时间,开始介绍数据加密的整体架构和实现原理:DBPlusEngine 通过对用户输入的 SQL 进行解析,并依据用户提供的加密规则对 SQL 进行改写,从而实现对原文数据进行加密,并将原文数据(可选)及密文数据同时存储到底层数据库。在用户查询数据时,它仅从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用户。DBPlusEngine 自动化 & 透明化了数据加密过程,让用户无需关注数据加密的实现细节,像使用普通数据那样使用加密数据。此外,无论是已在线业务进行加密改造,还是新上线业务使用加密功能,DBPlusEngine 都可以提供一套相对完善的解决方案。
加密模块将用户发起的 SQL 进行拦截,并通过 SQL 语法解析器进行解析、理解 SQL 行为,再依据用户传入的加密规则,找出需要加密的字段和所使用的加解密算法对目标字段进行加解密处理后,再与底层数据库进行交互。DBPlusEngine 会将用户请求的明文进行加密后存储到底层数据库;并在用户查询时,将密文从数据库中取出进行解密后返回给终端用户。通过屏蔽对数据的加密处理,使用户无需感知解析 SQL、数据加密、数据解密的处理过程,就像在使用普通数据一样使用加密数据。
加密策略解析
DBPlusEngine 提供了两种策略用于数据加密,这两种策略分别对应 DBPlusEngine 的两种加解密的接口:
EncryptAlgorithm
和 QueryAssistedEncryptAlgorithm
一方面,DBPlusEngine 为用户提供了内置的加解密实现类,用户只需进行配置即可使用;另一方面,为了满足用户不同场景的需求,我们还开放了相关加解密接口,用户可依据这两种类型的接口提供具体实现类。再进行简单配置,即可让 DBPlusEngine 调用用户自定义的加解密方案进行数据加密。
EncryptAlgorithm
该解决方案通过提供 encrypt()
, decrypt()
两种方法对需要加密的数据进行加解密。在用户进行INSERT
, DELETE
, UPDATE
时,DBPlusEngine 会按照用户配置,对SQL进行解析、改写、路由,并调用encrypt()
将数据加密后存储到数据库, 而在SELECT
时,则调用decrypt()
方法将从数据库中取出的加密数据进行逆向解密,最终将原始数据返回给用户。
当前,DBPlusEngine 针对这种类型的加密解决方案提供了三种具体实现类,分别是 MD5(不可逆),AES(可逆),RC4(可逆),用户只需配置即可使用这三种内置的方案。
QueryAssistedEncryptAlgorithm
相比较于第一种加密方案,该方案更为安全和复杂。它的理念是:即使是相同的数据,如两个用户的密码相同,它们在数据库里存储的加密数据也应当是不一样的。这种理念更有利于保护用户信息,防止撞库成功。
它提供三种函数进行实现,分别是encrypt()
, decrypt()
, queryAssistedEncrypt()
。在encrypt()
阶段,用户通过设置某个变动种子,例如时间戳。针对原始数据+变动种子组合的内容进行加密,就能保证即使原始数据相同,也因为有变动种子的存在,致使加密后的加密数据是不一样的。在decrypt()
可依据之前规定的加密算法,利用种子数据进行解密。
虽然这种方式确实可以增加数据的保密性,但是另一个问题却随之出现:相同的数据在数据库里存储的内容是不一样的,那么当用户按照这个加密列进行等值查询(SELECT FROM table WHERE encryptedColumnn = ?
)时会发现无法将所有相同的原始数据查询出来。为此,我们提出了辅助查询列的概念。该辅助查询列通过queryAssistedEncrypt()
生成,与decrypt()
不同的是,该方法通过对原始数据进行另一种方式的加密,但是针对原始数据相同的数据,这种加密方式产生的加密数据是一致的。将queryAssistedEncrypt()
后的数据存储到数据中用于辅助查询真实数据。因此,数据库表中多出这一个辅助查询列。
由于queryAssistedEncrypt()
和encrypt()
产生不同加密数据进行存储,而decrypt()
可逆,queryAssistedEncrypt()
不可逆。在查询原始数据的时候,我们会自动对SQL进行解析、改写、路由,利用辅助查询列进行WHERE
条件的查询,却利用 decrypt()对encrypt()
加密后的数据进行解密,并将原始数据返回给用户。这一切都是对用户透明化的。
当前,DBPlusEngine 针对这种类型的加密解决方案并没有提供具体实现类,却将该理念抽象成接口,提供给用户自行实现。DBPlusEngine 将调用用户提供的该方案的具体实现类进行数据加密。
SphereEx-DBPlusEngine 数据加密特色能力
原理介绍完了,下面重点讲一下除了数据加密的功能以外,还提供更加贴近业务的四个特色的能力:云端密钥管理、加密洗数、反洗数、重洗数,这四个特色能力,可以在保证数据安全的前提下,提高数据处理的效率和可靠性。
由于加密 key 以明文形式配置在 props 文件中存在安全隐患,SphereEx-DBPlusEngine 可通过增加 AWS 云端密钥管理功能来管理加密的 key。例如利用 AWS 的 secretKey 功能保存密钥,从而提升整个加密的安全性和便捷性。程序在初始化加密算法时,会同 AWS 建立连接,从而获取存储在 AWS 中的相关密钥,然后将密钥存储在算法中。在整个数据加密的过程中不涉及与云端的网络交互。
目前 SphereEx-DBPlusEngine 已经提供了加密的解决方案,对于新表新业务而言,直接使用加密规则配置即可,但是对于已有数据表,就需要将这些表中的明文字段进行洗数,转化为加密内容,SphereEx-DBPlusEngine 提供了完整的洗数方案。加密洗数通过 DistSQL 来触发洗数任务,程序收到洗数任务的请求之后,会根据当前的洗数规则以及加密规则创建洗数任务。洗数任务主要由两部分构成,一部分是查询任务,一部分是更新任务,查询任务负责查询用户的表数据并获取需要加密的明文字段,然后推送到通道中;更新任务则从通道中获取数据,并加密更新。整个任务的创建以及执行过程都会与治理中心进行交互,因此用户可以通过相关 DistSQL 来查询任务进度以及清理任务。
对于已经是密文存储的系统,此时如果需要还原到明文存储的状态,可通过 SphereEx-DBPlusEngine 反向洗数的能力,构建出对应的明文列。在操作上与洗数类似,反洗数前提是需要提前创建好对应的明文列。
对于更换加密算法或更换密钥等情况,可通过 SphereEx-DBPlusEngine 来重新洗数。重洗数方案会调用反洗数和洗数流程,需要用户手动操作,并且过程中会有明文数据存储。
在以上洗数过程中,SphereEx-DBPlusEngine 支持断点续传的能力,为用户提供更好地洗数体验。
加密配置
加密配置元素分为四部分:数据源配置,加密算法配置,加密表配置以及查询属性配置,其详情如下图所示:
加密算法配置:指使用什么加密算法进行加解密。目前 SphereEx-DBPlusEngine 内置了五种加解密算法:AES,MD5,RC4,SM3 和 SM4。
加密表配置:用于告诉 SphereEx-DBPlusEngine 数据表里哪个列用于存储密文数据(cipherColumn)、使用什么算法加解密(encryptorName)、哪个列用于存储辅助查询数据(assistedQueryColumn)、使用什么算法加解密(assistedQueryEncryptorName)、哪个列用于存储明文数据(plainColumn)以及用户想使用哪个列进行 SQL 编写(logicColumn)。
查询属性的配置:当底层数据库表里同时存储了明文数据、密文数据后,该属性开关用于决定是直接查询数据库表里的明文数据进行返回,还是查询密文数据通过 SphereEx-DBPlusEngine 解密后返回。该属性开关支持字段级、表级别和全局规则级别配置,由 QUERY_WITH_CIPHER_COLUMN
进行配置,字段级别优先级最高。
加密模块存在的意义:加密模块最终目的是希望屏蔽底层对数据的加密处理,也就是说我们不希望用户知道数据是如何被加解密的、如何将明文数据存储到 plainColumn,将密文数据存储到 cipherColumn。换句话说,我们不希望用户知道 plainColumn 和 cipherColumn 的存在和使用。所以,我们需要给用户提供一个概念意义上的列,这个列可以脱离底层数据库的真实列,它可以是数据库表里的一个真实列,也可以不是,从而使得用户可以随意改变底层数据库的 plainColumn 和 cipherColumn 的列名。或者删除 plainColumn,选择永远不再存储明文,只存储密文。只要用户的 SQL 面向这个逻辑列进行编写,并在加密规则里给出 logicColumn 和 plainColumn、cipherColumn 之间正确的映射关系即可。
加密处理过程
接下来通过具体的示例,假如数据库里有一张表叫做 t_user
,这张表里实际有两个字段 pwd_plain
,用于存放明文数据、pwd_cipher
,用于存放密文数据、pwd_assisted_query
,用于存放辅助查询数据,同时定义 logicColumn 为 pwd
。那么,用户在编写 SQL 时应该面向 logicColumn 进行编写,即 INSERT INTO t_user SET pwd = '123'
。SphereEx-DBPlusEngine 接收到该 SQL,通过用户提供的加密配置,发现 pwd
是 logicColumn,于是便对逻辑列及其对应的明文数据进行加密处理。SphereEx-DBPlusEngine 将面向用户的逻辑列与面向底层数据库的明文列和密文列进行了列名以及数据的加密映射转换。如下图所示:
SphereEx-DBPlusEngine 数据加密的应用场景
在了解了 DBPlusEngine 加密处理流程后,即可将加密配置、加密处理流程与实际场景进行结合。所有的设计开发都是为了解决业务场景遇到的痛点。那么面对之前提到的业务场景需求,又应该如何使用 DBPlusEngine 这把利器来满足业务需求呢?下面从三个具体的业务场景来介绍一下。
新上线业务场景
业务场景分析:新上线业务由于一切从零开始,不存在历史数据清洗问题,所以相对简单。解决方案说明:选择合适的加密算法,如 AES 后,只需配置逻辑列(面向用户编写 SQL )和密文列(数据表存密文数据)即可,逻辑列和密文列可以相同也可以不同。建议配置如下(YAML 格式展示):
-!ENCRYPT
encryptors:
aes_encryptor:
type: AES
props:
aes-key-value: 123456abc
tables:
t_user:
columns:
pwd:
cipherColumn: pwd
encryptorName: aes_encryptor
使用这套配置, DBPlusEngine 只需将 logicColumn 和 cipherColumn 进行转换,底层数据表不存储明文,只存储了密文,这也是安全审计部分的要求所在。如果用户希望将明文、密文一同存储到数据库,只需添加 plainColumn 配置即可。整体处理流程如下:
1、设计加密表,确定加密字段,可结合表结构、数据特点及应用情况综合考虑;
2、在 SphereEx-DBPlusEngine 创建逻辑库,注册存储节点(空);
3、创建加密规则,可使用 SphereEx-Console 或 DistSQL 完成;
4、创建加密表,完成加密表构建;
5、根据业务实际情况,再选择保留或删除明文字段。
已上线业务改造场景
业务场景分析:由于业务已经在线上运行,数据库里必然存有大量明文历史数据。现在的问题是如何让历史数据得以加密清洗、如何让增量数据得以加密处理、如何让业务在新旧两套数据系统之间进行无缝、透明化迁移。
解决方案说明:在提供解决方案之前,我们先来头脑风暴一下:首先,既然是旧业务需要进行加密改造,那一定存储了非常重要且敏感的信息。这些信息含金量高且业务相对基础重要。不应该采用停止业务禁止新数据写入,再找个加密算法把历史数据全部加密清洗,再把之前重构的代码部署上线,使其能把存量和增量数据进行在线加密解密。
那么另一种相对安全的做法是:重新搭建一套和生产环境一模一样的预发环境,然后通过相关迁移洗数工具把生产环境的存量原文数据加密后存储到预发环境, 而新增数据则通过例如 MySQL 主从复制及业务方自行开发的工具加密后存储到预发环境的数据库里,再把重构后可以进行加解密的代码部署到预发环境。这样生产环境是一套以明文为核心的查询修改的环境;预发环境是一套以密文为核心加解密查询修改的环境。在对比一段时间无误后,可以夜间操作将生产流量切到预发环境中。此方案相对安全可靠,只是时间、人力、资金、成本较高,主要包括:预发环境搭建、生产代码整改、相关辅助工具开发等。
业务开发人员最希望的做法是:减少资金费用的承担、最好不要修改业务代码、能够安全平滑迁移系统。于是,ShardingSphere 的加密功能模块便应运而生。可分为 3 步进行:
假设系统需要对 t_user
的 pwd
字段进行加密处理,业务方使用 DBPlusEngine 来代替标准化的 JDBC 接口,此举基本不需要额外改造(我们还提供了 Spring Boot Starter,Spring 命名空间,YAML 等接入方式,满足不同业务方需求)。另外,提供一套加密配置规则,如下所示:
-!ENCRYPT
encryptors:
aes_encryptor:
type: AES
props:
aes-key-value: 123456abc
tables:
t_user:
columns:
pwd:
plainColumn: pwd
cipherColumn: pwd_cipher
encryptorName: aes_encryptor
queryWithCipherColumn: false
依据上述加密规则可知,首先需要在数据库表 t_user
里新增一个字段叫做 pwd_cipher
,即 cipherColumn,用于存放密文数据,同时我们把 plainColumn 设置为 pwd
,用于存放明文数据,而把 logicColumn 也设置为 pwd
。由于之前的代码 SQL 就是使用 pwd
进行编写,即面向逻辑列进行 SQL 编写,所以业务代码无需改动。通过 DBPlusEngine,针对新增的数据,会把明文写到pwd列,并同时把明文进行加密存储到 pwd_cipher
列。此时,由于 queryWithCipherColumn
设置为 false,对业务应用来说,依旧使用 pwd
这一明文列进行查询存储,却在底层数据库表 pwd_cipher
上额外存储了新增数据的密文数据,其处理流程如下图所示:
1、确认加密范围 根据合规需求,明确具体的加密数据范围。
2、确认加密算法 选择并确认适合的加密算法。
3、梳理业务 SQL,确认支持情况 确认当前业务中是否存在非等值的密态计算情况,如 like、>、< 等,若存在,则需考虑业务改造。
4、洗数 完成以上内容后,对于已上线的项目,需要对存量数据进行洗数。该过程无需引用外部组件,SphereEx-DBPlusEngine 可完成洗数过程,该过程可在线进行。
5、启用密文列 启用密文列之后,应用的请求将通过 SphereEx-DBPlusEngine 与密文字段产生交互,此时明文字段依然在写入数据。
6、下线明文列 当系统经过足够长时间的使用和观察,可将明文字段进行下线。敏感字段只以密文形式存在系统中。
新增数据在插入时,就通过 DBPlusEngine 加密为密文数据,并被存储到了 cipherColumn。而现在就需要处理历史明文存量数据。由于 DBPlusEngine 目前并未提供相关迁移洗数工具,此时需要业务方自行将 pwd
中的明文数据进行加密处理存储到 pwd_cipher
。
新增的数据已被 DBPlusEngine 将密文存储到密文列,明文存储到明文列;历史数据被业务方自行加密清洗后,将密文也存储到密文列。也就是说现在的数据库里即存放着明文也存放着密文,只是由于配置项中的 queryWithCipherColumn = false
,所以密文一直没有被使用过。现在我们为了让系统能切到密文数据进行查询,需要将加密配置中的 queryWithCipherColumn 设置为 true。在重启系统后,我们发现系统业务一切正常,但是 DBPlusEngine 已经开始从数据库里取出密文列的数据,解密后返回给用户;而对于用户的增删改需求,则依旧会把原文数据存储到明文列,加密后密文数据存储到密文列。
虽然现在业务系统通过将密文列的数据取出,解密后返回;但是,在存储的时候仍旧会存一份原文数据到明文列,这是为什么呢?答案是:为了能够进行系统回滚。因为只要密文和明文永远同时存在,我们就可以通过开关项配置自由将业务查询切换到 cipherColumn 或 plainColumn。 也就是说,如果将系统切到密文列进行查询时,发现系统报错,需要回滚。那么只需将 queryWithCipherColumn = false
,Apache ShardingSphere 将会还原,即又重新开始使用 plainColumn 进行查询。
由于安全审计部门要求,业务系统一般不可能让数据库的明文列和密文列永久同步保留,我们需要在系统稳定后将明文列数据删除。即我们需要在系统迁移后将 plainColumn,即pwd进行删除。那问题来了,现在业务代码都是面向pwd进行编写 SQL 的,把底层数据表中的存放明文的 pwd 删除了, 换用 pwd_cipher 进行解密得到原文数据,那岂不是意味着业务方需要整改所有 SQL,从而不使用即将要被删除的 pwd 列?还记得我们 DBPlusEngine 的核心意义所在吗?
这也正是 DBPlusEngine 核心意义所在,即依据用户提供的加密规则,将用户 SQL 与底层数据库表结构割裂开来,使得用户的SQL编写不再依赖于真实的数据库表结构。而用户与底层数据库之间的衔接、映射、转换交由 DBPlusEngine 进行处理。
是的,因为有 logicColumn 存在,用户的编写 SQL 都面向这个虚拟列,DBPlusEngine 就可以把这个逻辑列和底层数据表中的密文列进行映射转换。于是迁移后的加密配置即为:
-!ENCRYPT
encryptors:
aes_encryptor:
type: AES
props:
aes-key-value: 123456abc
tables:
t_user:
columns:
pwd: # pwd 与 pwd_cipher 的转换映射cipherColumn: pwd_cipher
encryptorName: aes_encryptor
至此,已在线业务加密整改解决方案全部叙述完毕。该解决方案目前已在京东数科不断落地上线,提供对内基础服务支撑。
已上线业务换密钥场景
业务场景分析:已上线业务,运行过程中,定期更换密钥,可以大大提升业务的安全性。解决方案说明:需要提前创建与加密规则配置一致的明文字段,然后执行三条DistSQL就可以完成在线更换密钥不影响线上业务,流程如下
1、开始自动换密钥流程:REENCRYPT TABLE WITH RULE table_name();
2、确认明文数据已经生成后,执行重洗数第二阶段:START REENCRYPTING;
3、确认数据完成替换提交任务,完成换密钥流程:COMMIT REENCRYPTING;
总结
本文从三部分详细了解 SphereEx-DBPLusEngine 数据加密,首先笔者从数据加密的基本概念出发,介绍了逻辑列、密文列、明文列、模糊查询列等概念,随后介绍了数据加密功能的整体架构和原理,一方面,加密算法是数据加密过程中所使用的具体加密方式,可通过 EncryptAlgorithm 和 QueryAssistedEncryptAlgorithm 两种策略来实现加密。
另外一方面,SphereEx-DBPlusEngine 数据加密提供的云端密钥管理、加密洗数、反洗数和重洗数等特色能力,帮助用户提升加密安全性和便捷性。并强调了加密洗数是通过 DistSQL 来触发洗数任务。此外还介绍了使得数据加密更加灵活和可定制化加密配置和加密处理的过程。最后重点分享了 DBPlusEngine 加密可以在新上线业务场景、已上线业务改造场景和已上线业务换密钥场景等不同业务业务阶段的场景进行配置和使用。可以根据业务需求选择相应的加密算法,进行逻辑列和密文列的配置,实现历史数据清洗、增量数据加密处理和在线更换密钥不影响线上业务。
新版本剧透
在即将发布的 1.5.0 中对加密配置做了优化,优化后配置如上,欢迎您的试用对内容有疑问,对产品感兴趣,请拨打电话:400-900-2818,或点击阅读原文,与我们联系!