开发一些企业级项目时,经常会遇到“安全扫描”提醒你不能明文发送手机号和密码这类敏感信息。这个时候,最简单的混淆方法就是base64。
但base64随便拿个解码网页就能解出来了,所以还要加一点装饰……

给加密过程加个装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function randomChar(length, safeChar = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678") {
let result = "";
for (let i = length; i > 0; i--) {
result += safeChar.charAt(Math.floor(Math.random() * safeChar.length));
}
return result
}
function sensitiveMask(length) {
// 没有传长度时用随机长度
if (!length || length < 1) {
length = Math.floor(Math.random() * 16) % 16
}
// 最长不能超过15,因为16会被4整除,且超过16后需要用更多字符表示长度
if (length > 15) {
length = 15
}
// 长度为1时,取值固定容易暴露装饰内容格式;
// 长度为4的倍数时,用普通base64解码页面会暴露原始内容
if (!(length % 4) || length === 1) {
length++
}
return (16 - length).toString(16) + randomChar(length - 1)
}

这是一个带长度前缀的随机字符串生成函数,作为装饰添加到base64的字符串前面:

1
2
sensitiveMask(5) + btoa('18812345678');
// 返回结果 bpRDYMTg4MTIzNDU2Nzg=

这个拼接后的结果,标准的base64解码页面就不认识了。
真正需要解密的后端人员,只要知道第一位是随机字符串的长度,然后从这个长度开始截取后面的字符进行base64解码就行。

解密也很简单

1
2
3
4
5
6
function descypt(word) {
let length = 16 - parseInt(word.charAt(0), 16);
return atob(word.substring(length));
}
descypt('bpRDYMTg4MTIzNDU2Nzg=');
// 返回结果 18812345678

总结

上面这个加密方案有以下优点:

  • 后端解密简单:读取第一位的装饰长度就能截取需要解密的base64部分。不需要逐字计算也没有密钥需要同步;
  • 随机幅度大,找不到规律:如果不传递length参数,这个装饰结果会让对应字段取值变化巨大,难以猜测规则;
  • 随机字符生成函数可在其他代码中使用,不容易被认定为加密解密算法。