
ShardingSphere 加密列模糊查询原理与实战
一、文章背景
Apache ShardingSphere 提供了数据加密模块,通过对用户输入的 SQL 进行解析,并依据用户提供的加密规则对 SQL 进行改写,从而实现对原文数据进行加密,并将原文数据(可选)及密文数据同时存储到底层数据库。 在用户查询数据时,它仅从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用户。但是由于加密算法是将整串字符串加密,导致模糊搜索功能无法实现。 数据加密后,很多业务都需要使用模糊搜索功能。目前,在刚刚发布的 5.3.0 版本中:
Apache ShardingSphere 为用户提供了默认的模糊查询算法,支持加密字段的模糊查询功能,模糊查询算法同样支持热插拔,用户可以自定义,并且通过配置即可实现模糊查询。
二、加密场景下『模糊查询』如何实现?
1、加载数据到内存数据库
将所有数据加载到内存数据库中进行解密,这样我们就和查询原文一样了。这个方案虽然可以实现模糊查询,如果数据量小的话可以使用这个方式来做,这样做既简单又实惠,如果数据量大的话那就是灾难。
2、数据库中实现与程序一致的加解密函数
修改模糊查询条件,使用数据库解密函数先解密后再模糊查找,这样做的优点是实现成本低,开发使用成本低,只需要将以往的模糊查找稍微修改一下就可以实现。但是数据库中密文和加密函数存储在一起,无法应对数据库账号泄漏场景。
3、脱敏存储
对密文数据进行脱敏后存储到模糊查询列,该方案会丢失太多精度。
4、分词组合加密存储
对密文数据进行分词组合,将分词组合的结果集分别进行加密。对字符进行固定长度的分组,将一个字段拆分为多个,比如说根据 4 位英文字符,2 个中文字符为一个检索条件,举个例子:
ningyu1 使用 4 个字符为一组的加密方式:第一组 ning,第二组 ingy,第三组 ngyu,第四组 gyu1 以此类推,全部加密之后存储到模糊查询列。如果需要检索所有包含检索条件 4 个字符的数据,比如:ingy
加密字符后,通过 key like“%partial%” 完成查询。
缺点如下 :
存储成本增加
自由分组会增加数据量,加密后长度会增长。
模糊查询长度有限制
由于安全问题,自由组合长度不能太低,不然可以采取彩虹表破解。对模糊查询的字符长度是有要求的。以我上面举的例子,模糊查询字符原文长度,必须大于等于 4 个英文/数字,或者 2 个汉字才能搜索到结果。
5、5.3.0 提供的默认模糊查询算法
(单字符摘要算法)
上述方案都能使用,但是有没有更好的方案。我们发现单字符加密存储是性能和搜索都能兼顾的,但是不符合安全要求,那应该怎么办?受到脱敏算法和密码散列函数的启发,我们发现丢失数据和单向函数这个思路可以借鉴。密码散列函数应该有的四个主要特性:
安全性 : 因为有单向函数,所以是不可以推算原始消息的。因为我们想模糊搜索精确度提升,所以想单字符加密,但是又会被彩虹表破解。所以我们借鉴单向函数(保证每个字符加密之后的字符一致),然后增加碰撞(保证每个字符串逆向结果为 1:N),这样就大大加强了安全性。
三、模糊查询算法
Apache ShardingSphere 内部采用上述单字符摘要算法实现了一个通用的模糊查询算法 org.apache.shardingsphere.encrypt.algorithm.like.CharDigestLikeEncryptAlgorithm。
· 先定义二进制 mask 码用来丢失精度
0b1111_0111_1101 (mask);
· 将常见汉字乱序后,按照顺序存为 map 字典;
· 数字、英文、拉丁文可取单个字符串的 Unicode 码;
· 属于字典的文字获取 index;
· 其他字符取单个字符串的 Unicode 码;
· 然后将上述不同类型获取的数字进行加 1 (delta) 防止任何原文出现在数据库;
· 然后将偏移后的 Unicode 码转换成二进制和 mask 进行与运算,进行 2 位的数字丢失;
· 数字、英文、拉丁文丢失精度之后直接输出;
· 剩余字符失精度后,转成十进加上常见字起始码
(start) 输出。
四、模糊算法演进
第一版算法
单纯采取常用字的 Unicode 与 mask 码进行与运算:
我们发现每个字符串会根据丢失的位数,反推出来的结果为 2^n。因为常见汉字的 Unicode 码在十进制时间隔都很大,可以发现反推出来的汉字基本都不是常用字,反推出原字的几率比较大。
第二版算法
由于常见汉字 Unicode 码间隔没有规律,所以我们准备把汉字 Unicode 码的后几位留下,转成十进作为 index 去常见汉字中取词。这样就解决在知道算法的情况下,反解出现非常见字的问题,干扰项就不再容易排除了。
汉字 Unicode 留下后几位,涉及到一个模糊精确度和反解密复杂度的一个关系,模糊精确度高了相应的解密难度就降低了。下面我们看一下常见汉字在我们算法下的碰撞程度:
- mask=0b0011_1111_1111 时:
- mask=0b0001_1111_1111 时:
汉字尾数留 10 位和留 9 位。10 位的查询精度会更高,因为 10 位的碰撞会小很多,但是 1:1 的字在知道算法和秘钥的的情况下是可以反推出原文的。9 位的查询精度稍弱,因为 9 位的碰撞相对来说大一点,但是 1:1 的汉字较少。仔细观察会发现,不管是留 10 位还是留 9 位虽然我们改变了碰撞,但是由于汉字原本 Unicode 码没有规律,导致分布非常不均衡,不能控制整体的一个碰撞概率。
第三版算法
基于第二版发现分布不均衡的问题,我们把常用字乱序作为字典表。
- 加密文字先在乱序字典表中查找 index,我们用取到的 index 下标代替没有规则 Unicode 码,如果非常用字还是使用 Unicode 码。(让参与计算的 code 尽量分布均衡)
- 第二步是和 mask 进行与运算丢失 2 位精度增加碰撞。
下面我们看一下,常见的汉字在算法下的碰撞程度:
- mask=0b1111_1011_1101 时:
- mask=0b0111_1011_1101 时:
Mask 选择留 11 位的时候,可以看到碰撞分布相对来说集中在 1:4 上,Mask 选择留 10 位的时候,可以看到碰撞分布相对来说集中在1:8上,此时我们只需要调整丢失精度的个数就能控制碰撞是 1:2 还是 1:4 还是 1:8 了。
Mask 选择 1,且知道算法和秘钥的情况下会有一个 1:1 的汉字,因为此时我们计算的是常见字的碰撞程度,如果加上汉字 16 位二进制前面丢失的 4 位,情况就变成了 2^5=32 种情况。由于我们加密是整段文字,即使反推出个别字对于整体安全性影响不大,不会造成大批量数据泄密,同时前提是要知道算法、秘钥、delta 和字典才具备反推的可能,仅从数据库中的数据,无反推可能性。
五、模糊查询的使用
模糊查询需要在加密配置中配置 encryptors (加密算法配置)、likeQueryColumn (模糊查询列名称)、likeQueryEncryptorName (模糊查询列加密算法名称)。参考配置如下,分片算法、数据源等配置请自行添加。
Insert 示例:
Update 示例:
Select 示例:
Select 联表子查询示例:
Delete 示例:
上述示例演示了模糊查询列在不同 SQL 语法中是如何改写 SQL 支持模糊查询的。本文详细介绍了模糊查询的基本实现原理以及具体的使用示例。
相信通过本文,读者朋友们对模糊查询都有了一些基本的了解。大家可以根据自己的需求使用 模糊查询 或者 自定义算法。 如果是通用算法,也欢迎大家向社区提交。在使用过程中遇到任何问题或者有任何想法,都欢迎来社区反馈!
作者简介
熊高祥,科大讯飞工程师
ShardingSphere Contributor
主要负责数据『加密 & 脱敏』研发工作
文章转载自公众号: ShardingSphere官微
