一、敏感信息泄露有哪些途径
- 明文存储,比如直接把手机号、身份证存储到数据库。如果数据的用户和密码被一些不应该的人员看到,获取;就很容易造成泄漏
- 明文传输,比如没有对敏感信息进行RSA或者AES加密,就在网络中进行传输
二、解决敏感信息泄漏的最佳途径
- 明文存储
对数据敏感信息加密后,再进行存储。有这样一个场景:有个用户表除了其他字段外,还有手机号 mobile_no 和身份证 identity_card ,这两个敏感信息存储字段。如果直接储存mobile_no和identity_card明文,就很容易泄漏。
可以对这两个字段进行对称加密或者非对称加密存储,分别定义两个加密字段 mobile_no_encrypted 和 identity_card_encrypted。但是进行加密存储到数据库必然导致以下两个问题:
如何进行精准查询匹配 - 如何进行模糊查询匹配
- 如何进行精准查询匹配?
为了解决这个问题,还得多加一个字段,对于手机号添加 mobile_no_sha 字段,身份证添加 identity_card_sha 字段。这两个字段分别存储手机号和身份证的SHA-1的散列码(也可以使用md5算法)。这样的话,如果精准查询就直接比对SHA-1散列码就行。
select * from t_user where mobile_no_sha = sha-1(mobile_no)如何进行模糊查询匹配?
对应模糊查询就有点麻烦了!!!
通常数据库自带有加解密函数,如MySQL的PASSWORD ,MD5,AES_ENCRYPT等等。对于密码等信息可以采用单向加密,验证的时候用同样的方式加密匹配即可。而对于需要比对原内容的模糊查询,则需要使用双向加密,也即可以解密,在MySQL可以使用自带的AES加密。完成存储加密,读取解密后,就可以实现模糊搜索了:
存储的时候mysql 使用 HEX(AES_ENCRYPT(‘123456’,‘TEST’));
php加密(php加密后存储mysql)
bin2hex(base64_decode(openssl_encrypt(‘123456’,‘aes-128-ecb’,‘SecretKey’))); //结果是 f66a814a5ffeefeb9b0344aefae45326 SecretKey需要是16位字符
php解密(mysql获取后php解密)
openssl_decrypt(base64_encode(hex2bin(‘f66a814a5ffeefeb9b0344aefae45326’)), ‘aes-128-ecb’, ‘SecretKey’);
js 加密(和php加密结果保持一致)
var keyText = ‘ThisIsA16CharacterKey’; // 密钥需要是16/24/32字节长,对应AES-128/192/256
var key = CryptoJS.enc.Utf8.parse(keyText);
var message = “13344455555”;
var encrypted = CryptoJS.AES.encrypt(message, key, { padding: CryptoJS.pad.Pkcs7, mode: CryptoJS.mode.ECB });
// var cipherTextBase64 = encrypted.toString(); // 获取Base64编码的密文
var result = encrypted.ciphertext.toString(CryptoJS.enc.Hex); //获取十六进制形式,和php以及mysql加密结果一致
console.log(result);
mysql 解密查询(支持模糊查询)
加密语句:select HEX(AES_ENCRYPT(‘13344455555’,‘SecretKey’)); /SecretKey必须和php以及js加密秘钥一致,16位字符
查询语句:select * from t_user where AES_DECRYPT(UNHEX(mobile_no_sha
),‘SecretKey’) like ‘133%’;
select *
from t_user
where AES_DECRYPT(UNHEX(mobile_no_sha
),‘SecretKey’)
like ‘xxx%’;
该方案中保存数据时,只对单个用户的数据进行操作,数据量比较小,性能还好。
但模糊查询数据时,每一次都需要通过DES_DECRYPT函数,把数据库中用户某个隐私信息字段的所有数据都解密了,然后再通过解密后的数据,做模糊查询。
如果该字段的数据量非常大,这样每次查询的性能会非常差。
查询性能解决方案: 分段保存
我们可以将一个完整的字符串,拆分成多个小的字符串。
以手机号为例:13344455555,
方案一:按每3位为一组,进行拆分,拆分后的字符串为:133,334,344,444,445,555,这几组数据,然后新建映射表,存储每组加密后的密文。
方案二(推荐):手机号仅支持前三位和后四位或者完整手机号查询,然后新建映射表,存储加密后的密文
然后建一张表:
sql复制代码CREATE TABLE encrypt_value_mapping
(
id
bigint NOT NULL COMMENT ‘系统编号’,
ref_id
bigint NOT NULL COMMENT ‘关联系统编号’,
encrypt_value_start
varchar(255) NOT NULL COMMENT ‘前三位加密后的字符串’,
encrypt_value_end
varchar(255) NOT NULL COMMENT ‘后四位加密后的字符串’
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT=‘分段加密映射表’
这张表有四个字段:
- id:系统编号。
- ref_id:主业务表的系统编号,比如用户表的系统编号。
- encrypt_value_start:前三位加密后的字符串。
- encrypt_value_end:后四位加密后的字符串。
用户在写入手机号的时候,同步把拆分之后的手机号分组数据,也一起写入,可以保证在同一个事务当中,保证数据的一致性。
如果要模糊查询手机号前三位,可以直接通过encrypt_value_mapping的encrypt_value_start查询出用户表的ref_id,再通过ref_id查询用户信息。
具体sql如下:
select s2.id,s2.name,s2.phone
from encrypt_value_mapping s1
inner joinuser
s2 on s1.ref_id=s2.id
where s1.encrypt_value_start = ‘U2FsdGVkX19Se8cEpSLVGTkLw/yiNhcB’
limit 0,20;
如果要模糊查询手机号后四位,可以直接通过encrypt_value_mapping的encrypt_value_end查询出用户表的ref_id,再通过ref_id查询用户信息。
具体sql如下:
select s2.id,s2.name,s2.phone
from encrypt_value_mapping s1
inner join user
s2 on s1.ref_id=s2.id
where s1.encrypt_value_end = ‘U2FsdGVkX19Se8cEpSLVGTkLw/yiNhcB’
limit 0,20;
这样就能轻松的通过模糊查询,搜索出我们想要的手机号了。
注意这里的encrypt_value用的等于号,由于是等值查询,效率比较高。
注意:这里通过sql语句查询出来的手机号是加密的,在接口返回给前端之前,需要在代码中统一做解密处理。