Java的加密的字段模糊查询
1.对于加密字段查询不是很友好,但有这样的需求的,本文提供以下思路
2.如何对加密后的数据进行模糊查询
我整理了一下对加密的数据模糊查询大致分为三类做法,如下所示:
沙雕做法(不动脑思考直男的思路,只管实现功能从不深入思考问题)
常规做法(思考了查询性能问题,也会使用一些存储空间换性能等做法)
超神做法(比较高端的做法从算法层面上思考)
沙雕做法
将所有数据加载到内存中进行解密,解密后通过程序算法来模糊匹配(数据量小可以,数据量大容易内存溢出)
一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间,用DES来举例,13800138000加密后的串HE9T75xNx6c5yLmS5l4r6Q==占24个字节
轻则上百兆,重则上千兆,这样分分钟给应用程序整成Out of memory,这样做如果数据少只有几百、几千、几万条时是完全可以这样做的,但是数据量大就强烈不建议了
常规做法
我们接下来看看常规的做法,也是最广泛使用的方法,此类方法及满足的数据安全性,又对查询友好。
1.在数据库实现加密算法函数,在模糊查询的时候使用decode(key) like '%partial%
2.对密文数据进行分词组合,将分词组合的结果集分别进行加密,然后存储到扩展列,查询时通过key like ‘%partial%’
常规1
在数据库中实现与程序一致的加解密算法,修改模糊查询条件,使用数据库加解密函数先解密再模糊查找,这样做的优点是实现成本低,开发使用成本低,只需要将以往的模糊查找稍微修改一下就可以实现,但是缺点也很明显,这样做无法利用数据库的索引来优化查询,甚至有一些数据库可能无法保证与程序实现一致的加解密算法,但是对于常规的加解密算法都可以保证与应用程序一致。
如果对查询性能要求不是特别高、对数据安全性要求一般,可以使用常见的加解密算法比如说AES、DES之类的也是一个不错的选择。
如果公司有自己的算法实现,并且没有提供多端的算法实现,要么找个算法好的人去研究吃透补全多端实现,要么放弃使用这个办法。
常规2
对密文数据进行分词组合,将分词组合的结果集分别进行加密,然后存储到扩展列,查询时通过key like ‘%partial%’,这是一个比较划算的实现方法,我们先来分析一下它的实现思路。
先对字符进行固定长度的分组,将一个字段拆分为多个,比如说根据4位英文字符(半角),2个中文字符(全角)为一个检索条件,举个例子:
ningyu1使用4个字符为一组的加密方式,第一组ning ,第二组ingy ,第三组ngyu ,第四组gyu1 … 依次类推
淘宝密文字段检索方案:https://open.taobao.com/docV3.htm?docId=106213&docType=1
阿里巴巴文字段检索方案:https://jaq-doc.alibaba.com/docs/doc.htm?treeId=1&articleId=106213&docType=1
拼多多密文字段检索方案:https://open.pinduoduo.com/application/document/browse?idStr=3407B605226E77F2
京东密文字段检索方案:https://jos.jd.com/commondoc?listId=345
这个方法优点就是实现起来不算复杂,使用起来也较为简单,算是一个折中的做法,因为会有扩展字段存储成本会有升高,但是可利用数据库索引优化查询速度,推荐使用这个方法。
简单的实现
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class DESEncryptionWithTokenization {
private static final String DES_ALGORITHM = "DES";
private static final String SECRET_KEY = "yourSecretKey"; // 8 characters for DES
public static String encrypt(String data) {
try {
DESKeySpec desKeySpec = new DESKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encryptedData) {
try {
DESKeySpec desKeySpec = new DESKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
// 示例:加密
String originalData = "example data for encryption";
// todo 利用空格进行分词,实际开发可使用其他分词器
String[] tokens = originalData.split("\\s+");
String[] encryptedTokens = Arrays.stream(tokens)
.map(DESEncryptionWithTokenization::encrypt)
.toArray(String[]::new);
// 示例:解密
String[] decryptedTokens = Arrays.stream(encryptedTokens)
.map(DESEncryptionWithTokenization::decrypt)
.toArray(String[]::new);
// 打印结果
System.out.println("Original Data: " + originalData);
System.out.println("Encrypted Tokens: " + Arrays.toString(encryptedTokens));
System.out.println("Decrypted Tokens: " + Arrays.toString(decryptedTokens));
}
}
基于Lucene的云端搜索与密文基础的模糊查询:https://www.cnblogs.com/arthurqin/p/6307153.html
基于Lucene的思路,对字符进行等长度分词,将分词后的结果集加密后存储,只不过存储的db不一样,一个是关系型数据库,一个是es搜索引擎
3.超神做法
我们接下来看看优秀的做法,此类做法难度较高,都是从算法层面来考虑,有些甚至会设计一个新算法,虽然已有一些现成的算法参考,但是大多都是半成品无法拿来直接使用,所以还是要有人去深入研究和整合到自己的应用中去。本文不做介绍
参考:https://cloud.tencent.com/developer/article/2090031