持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情
最近开发了一个需求,后端用私钥对返回的数据进行加密,前端用对应的公钥进行解密。为什么会这样呢?因为后端返回的数据有手机号、身份证号等明文数据,公司出于安全考虑,所以要对这样的接口进行安全改造。
因为之前,前端在项目中有使用wxapp_rsa.js进行公钥加密,后端私钥解密这样的功能,所以我就尝试用wxapp_rsa.js去解密,但是如果前端私钥解密的话,就会存在私钥泄露的情况,所以最后就定义为后端私钥加密,前端公钥解密。 真正去开发需求的时候还是遇到了很多问题,下面我会一一道来。
出于安全考虑,后端私钥加密,前端公钥解密(虽然这不符合常规,一般都是公钥加密,私钥解密或者验签的时候,私钥加密,公钥验签)
改造如下(注释说明就是修改的部分):
1、修改RSADecrypt函数
function RSADecrypt(b) {
var d = parseBigInt(b, 16);
// 这里doPrivate修改为doPublic
// var m = this.doPrivate(d);
var a = this.doPublic(d);
if (a == null) {
return null
}
return pkcs1unpad2(a, (this.n.bitLength() + 7) >> 3)
}
2、修改pkcs1unpad2函数
function pkcs1unpad2(g, j) {
var a = g.toByteArray();
var f = 0;
while (f < a.length && a[f] == 0) {++f
}
// // 这里将如下三行代码注释
// if (a.length - f != j - 1 || a[f] != 2) {
// return null
// }
++f;
while (a[f] != 0) {
if (++f >= a.length) {
return null
}
}
var e = "";
while (++f < a.length) {
var h = a[f] & 255;
if (h < 128) {
e += String.fromCharCode(h)
} else {
if ((h > 191) && (h < 224)) {
e += String.fromCharCode(((h & 31) << 6) | (a[f + 1] & 63)); ++f
} else {
e += String.fromCharCode(((h & 15) << 12) | ((a[f + 1] & 63) << 6) | (a[f + 2] & 63));
f += 2
}
}
}
return e
}
原因:在本地自测的时候发现,如果数据过长,解密就会报错。
数据量太大,加密解密报错:一般来说密钥长度为1024,加密长度为 128 ,加密长度为 117 ,如果字符超过这个数量就会报错
解决方案:
采用分段加密解密,计算出来需要加密数据的长度
那就需要写两个分段加解密的方法:
1、分段解密的函数
// 分段解密,支持中文
RSAKey.prototype.decryptUnicodeLong = function (string) {
var k = this;
//解密长度=key size.hex2b64结果是每字节每两字符,所以直接*2
var maxLength = ((k.n.bitLength()+7)>>3)*2;
try {
var hexString = b64tohex(string);
var decryptedString = "";
var rexStr=".{1," + maxLength + "}";
var rex =new RegExp(rexStr, 'g');
var subStrArray = hexString.match(rex);
if(subStrArray){
subStrArray.forEach(function (entry) {
decryptedString += k.decrypt(entry);
});
return decryptedString;
}
} catch (ex) {
return false;
}
};
2、分段加密的函数
// 分段加密,支持中文
RSAKey.prototype.encryptUnicodeLong = function (string) {
var k = this.getKey();
//根据key所能编码的最大长度来定分段长度。key size - 11:11字节随机padding使每次加密结果都不同。
var maxLength = ((k.n.bitLength()+7)>>3)-11;
try {
var subStr="", encryptedString = "";
var subStart = 0, subEnd=0;
var bitLen=0, tmpPoint=0;
for(var i = 0, len = string.length; i < len; i++){
//js 是使用 Unicode 编码的,每个字符所占用的字节数不同
var charCode = string.charCodeAt(i);
if(charCode <= 0x007f) {
bitLen += 1;
}else if(charCode <= 0x07ff){
bitLen += 2;
}else if(charCode <= 0xffff){
bitLen += 3;
}else{
bitLen += 4;
}
//字节数到达上限,获取子字符串加密并追加到总字符串后。更新下一个字符串起始位置及字节计算。
if(bitLen>maxLength){
subStr=string.substring(subStart,subEnd)
encryptedString += k.encrypt(subStr);
subStart=subEnd;
bitLen=bitLen-tmpPoint;
}else{
subEnd=i;
tmpPoint=bitLen;
}
}
subStr=string.substring(subStart,len)
encryptedString += k.encrypt(subStr);
return hex2b64(encryptedString);
} catch (ex) {
return false;
}
};
使用第三步写的decryptUnicodeLong函数对长数据进行解密,刚开始本地自测的时候是好的,加大了数据量也是好的。然后发布到测试环境,发现偶尔会出现中文乱码的问题,经过一番搜索之后,发现原因是:加密和解密数据都是要转成byte[] 类型的,字符串占字节为3,所以在分割字节的时候,将一个汉字分割成了两个数组的结尾和开头,这样就会出现乱码的情况。
那怎么解决呢?
探索了一番之后,解决方案如下:
第一步:后端Java进行分段加密
这样返回的就是一个string数组到前台,格式如:{ data: [“密文片段1”, “密文片段2”, “密文片段3”] }
以前的加密方式:
String content = "1234567890";
byte[] data = content.getBytes();
byte[] encodedData = RSAUtil.encrypt(data, publicKey);
String encryptedContent = Base64Util.encode(encodedData);
String content = "1234567890";
List<String> encryptedList = new ArrayList<>();
//每X个字符,加密一次
if (content != null) {
int startIndex = 0;
int endIndex = 0;
int subLength = 50;
while (true) {
endIndex = startIndex + subLength;
if (content.length() <= endIndex) {
endIndex = startIndex + (content.length() - startIndex);
}
//region 将截取到的字符串,进行加密
byte[] data = content.substring(startIndex, endIndex).getBytes();
byte[] encodedData = RSAUtil.encrypt(data, publicKey);
String encryptedStr = Base64Util.encode(encodedData);
encryptedList.add(encryptedStr);
//endregion
startIndex += subLength;
if (startIndex >= content.length()) {
break;
}
}
}
这样返回的就是一个string数组到前台,格式如:{ data: [“密文片段1”, “密文片段2”, “密文片段3”] }
第二步:前端解密+拼接:
const decrypt = new JSEncrypt();
const PrivateKey = "PrivateKey";
decrypt.setPrivateKey(PrivateKey);
let JsonStr = "";
if (encryptedList && encryptedList.length > 0) {
for (let encrypted of encryptedList) {
JsonStr += decrypt.decryptLong(encrypted);
}
}
let jsonObj = JSON.parse(JsonStr) // 这样就得到一个JSON对象
这样将每个密文片段解密后,再按顺序拼接起来,就可得到加密前的原始json字符串。随后直接转化为json对象。
以上就是我做这个需求经历的问题和探索的过程。
这是我之前发表在其他平台上的原创文章。
一万年太久,只争朝夕!
我们下篇文章再见!
参考文献:
www.wjhsh.net/donkeysmall…
blog.csdn.net/qq\_4156621…
上一篇