字節終面:數據庫加密后怎么做模糊查詢?
代碼地址: https://github.com/zuiyu-main/EncryptDemo
在個別特殊領域中,數據的安全問題是非常的重要的,所以需要數據庫存儲的數據是需要加密存儲的。所以也就引申出來本文這個問題,加密之后的密文,還能模糊檢索嗎,如果能檢查,如何做模糊檢索呢?
現在的系統設計中,常見的加密字段有、密碼、身份證號、手機號、住址信息、銀行卡、信用卡以及個別行業的敏感信息。這些信息對加密的要求也不一樣,對于密碼來說,一般使用不可逆的加密算法就可以,一般不會用到檢索。但是對于身份證號或者個別領域中的中文信息,我們是需要支持密文模糊匹配的,下面我們就來看看有哪些實現方式。
本來主要講兩種常規的簡單加密做法,主要目標為能實現密文的模糊查詢。下面來跟我看第一種。
常規分詞加密
常規加密的密文檢索功能根據4位英文字符(半角),2個中文字符(全角)作為一個檢索條件,將一個字段拆分為多個字段。
比如:zuiyu123
使用4個字符為一組的加密方式。
第一組 zuiy,第二組uiyu,第三組iyu1,第四組yu12,第五組u123...如果字符串很長,依次類推下去。
如果需要檢索所有包含檢索條件 uiyu 的數據,加密字符后通過 key like ‘%加密uiyu的密文%’查詢。
所以這種實現方式就會有一種問題就是,隨著加密字符串的增加,密文也會變的越大,所以一般用此處方式需要注意數據庫中的字段長度限制。
需要注意的是,使用此處方式有一定的限制:
- 支持模糊檢索加密,但是加密的密文隨原文長度增長。
- 支持的模糊檢索條件必須大于等于4個英文數字或者2個漢字,不支持短的查詢(自定義該局限性,業界常用的就是4個英文數字或者2個漢字,再短的長度不建議支持,因為分詞組合會增多從而導致存儲的成本增加,反而安全性降低。)。
- 返回的列表不是很精確,需要二次篩選,先解密在進一步篩選。
字符串拆分的代碼如下:
protected List<String> loopEncryptString(String input, int chunkSize) {
int length = input.length();
List<String> strList = new LinkedList<>();
for (int i = 0; i < length; i++) {
StringBuilder chunkBuilder = new StringBuilder();
for (int j = 0; j < chunkSize; j++) {
int index = (i + j) % length;
chunkBuilder.append(input.charAt(index));
}
strList.add(chunkBuilder.toString());
log.info("第 {} 組:[{}]",i+1,chunkBuilder);
// 如果到了最后一個分組,則不再循環第一個字符
if (i + chunkSize >= length) {
break;
}
}
log.info("分詞結果:[{}]",strList);
return strList;
}
對于上述文本zuiyu123分詞效果如下
下面來看下中文的分詞效果:
檢索一下,只要我們使用的是包含上述分詞結果的條件我們就可以檢索的到。
比如我們檢索個蛋白質:
search result:[[{ID=8dac4d97-f05f-472e-94b2-02828aa235d6, CONTENT=ELYJBkZbfiVaJgTdlgglDg==UYwxxmEMQ9hq1jOax+r5rg==WwCBtglEf6clcWajP9sK+A==4sEGCqZ4P8Osr0dW84zFEA==c2AZejHeUp/5gpPkexfNcg==pvh/TcZRO4zwD+kwbE9lHw==1g30dxyz7z+8TQq+8jYH1A==AsWZOeiprypfrzSK3FtOuw==01vpoSuCXOpKCgcPsNlXyQ==79BPmIhSwMaA7hjN3ENDxA==}]]
可以看到,上述的content字段的內容長度非常的長,所以我們要注意數據庫字段長度限制。
除了上面這個方式外,發散一下思維,如果你用過 Elasticsearch 的話,會不會有點想法呢?
因為在中文的場景中,中文既然要分詞,選擇專業的分詞器應該是更合理的啊,所以我們可以使用???
對的,你沒猜錯,既然是要分詞,對于特殊的中文業務場景,直接使用 Elasticsearch 的分詞器分詞不就好了嗎,然后再用 Elasticsearch 的強大檢索能力,不就可以滿足我們的模糊檢索需求了嗎,想到就去做,下面就跟著我一起來看下如果用 Elasticsearch 的分詞實現密文模糊檢索。
分詞器分詞檢索
使用分詞器分詞進行密文檢索的原理:
- 使用 Elasticsearch 自帶的正則分詞器對加密后的密文進行分詞。
- 檢索時使用 Elasticsearch 的match進行檢索。
本文演示使用AES進行加解密,所以分詞器我就直接使用正則匹配,將密文中的內容按照==進行拆分。
下面我們一起進入代碼時間,跟隨著我的腳本來看看分詞密文檢索是什么樣的。
創建一個使用pattern分詞器的索引encrypt
如下創建索引語句為 Elasticsearch 6.8 的語句,如果使用 7+、8+ 的需要修改為對應的版本。
mappings 中的 _doc
put 127.0.0.1:9200/encrypt
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": "=="
}
}
}
},
"mappings": {
"_doc": {
"properties": {
"content": {
"type": "text"
}
}
}
}
}
隨便對于一個密文進行分詞,可以看到,已經按照我們的語氣進行==拆分為多個詞語了
其實不難發現,我們使用 AES 加密,就是對分詞之后的每個詞語進行加密,然后組成一個新的字符串。
還是上面那句話魚肉的蛋白質含量真的高,我們看一下分詞結果。
所以我們按照==拆分之后,檢索式再通過加密之后的密文進行檢索,也就相當于分詞檢索了。
檢索結果如下:
search result:[{"hits":[{"_index":"encrypt","_type":"_doc","_source":{"content":"ELYJBkZbfiVaJgTdlgglDg==9hF4g5NErtZNS9qFJGYeZA==uH9W7jvdoLIKq5gOpFjhWg==4sEGCqZ4P8Osr0dW84zFEA==c2AZejHeUp/5gpPkexfNcg==1g30dxyz7z+8TQq+8jYH1A==01vpoSuCXOpKCgcPsNlXyQ==kIzJL/y/pnUbkZGjIkz4tw=="},"_id":"1713343285459","_score":2.8951092}],"total":1,"max_score":2.8951092}]
總結
密文的模糊查詢就是以空間成本換取的。相比于存儲原文,密文比原文增長了好幾倍。
所以根據你的業務場景,選擇一個合適的加密算法才是最優解。