Rsa 加解密
使用的插件:JSEncrypt.js
加密方式:
let encryptor = new JSEncrypt();
encryptor.setPublicKey(pubkey); // 加密公钥
return encryptor.encrypt(data); // data: 要加密的数据
解密方式:
let decryptor = new JSEncrypt();
decryptor.setPrivateKey(PrivateKey); // 私钥
return decryptor.decrypt(data); // 要解密的字符
上面使用到的公私钥生成方式:
// 生成公私钥
async generateKey() {
//获取密钥对
const getRsaKeys = text => {
return new Promise((resolve, reject) => {
window.crypto.subtle
.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, // 1024, 2048, 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: "SHA-512" } //可以是“SHA-1”、“SHA-256”、“SHA-384”或“SHA-512”
},
true, //密钥是否可提取(即可用于exportKey)
["encrypt", "decrypt"] //必须是[“encrypt”,“decrypt”]或[“wrapKey”,“unwrapKey”]
)
.then(function(key) {
window.crypto.subtle
.exportKey("pkcs8", key.privateKey)
.then(function(keydata1) {
window.crypto.subtle
.exportKey("spki", key.publicKey)
.then(function(keydata2) {
var privateKey = RSA2text(keydata1, 1);
var publicKey = RSA2text(keydata2);
resolve({ privateKey, publicKey });
})
.catch(function(err) {
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
});
};
const RSA2text = (buffer, isPrivate = 0) => {
var binary = "";
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
var base64 = window.btoa(binary);
var text ="";
// "-----BEGIN " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----\n"; // 一般生成的密钥有前后缀,我的项目没有使用到,所以注释
text += base64
.replace(/[^\x00-\xff]/g, "$&\x01")
.replace(/.{64}\x01?/g, "$&\n");
// text +=
// "\n-----END " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----";// 一般生成的密钥有前后缀,我的项目没有使用到,所以注释
return text;
};
return getRsaKeys();
},
注意代码中注释部分!
遇到的问题:
rsa加密有长度限制
rsa算法本身要求加密内容也就是明文长度m必须0<m<密钥长度n。如果小于这个长度就需要进行padding,因为如果没有padding,就无法确定解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难,因为不确定后面的0是内容还是内容结束符。而只要用到padding,那么就要占用实际的明文长度,于是实际明文长度需要减去padding字节长度。我们一般使用的padding标准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。
这样,对于1024长度的密钥。128字节(1024bits)-减去11字节正好是117字节,但对于RSA加密来讲,padding也是参与加密的,所以,依然按照1024bits去理解,但实际的明文只有117字节了。
所以如果要对任意长度的数据进行加密,就需要将数据分段后进行逐一加密,并将结果进行拼接。同样,解码也需要分段解码,并将结果进行拼接。
遵循这一规则,我在项目中使用字符切割加密,但是和后台交付无法正常解密,通过一起排查,原来是
JSEncrypt.js 加密是没有考虑分段加密的,每次加密直接帮我们转了一次base64码 ,而我们分段加密需要把每一段先加密后拼接,再转base64码 才可以正常加解密使用,扩展JSEncrypt.js以达到支持分段加密:
JSEncrypt.prototype.encryptLong = function (str) {
try {
var inputLen = str.length,
offset = 0,
i = 0,
cache =[];
while(inputLen - offset > 0){
if(inputLen - offset > 117){
cache.push(this.getKey().encrypt(str.slice(offset,offset+117)));
}else{
cache.push(this.getKey().encrypt(str.slice(offset,inputLen)));
}
offset = ++i * 117;
}
return hex2b64(cache.join(""));
}
catch (err) {
console.log(err);
return false;
}
};
ASE加解密:
使用的插件:crypto-js
加密方式:
// 加密
/**
*@params:
*text: 加密内容
*iv: 偏移量
*key: 密钥
*/
encrypt(text,iv,key) {
return CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
解密方式:
// 解密
/**
*@params:
*text: 解密内容
*iv: 偏移量 与加密时保持一致
*key: 密钥 与加密时保持一致
*/
decrypt(text,iv,key) {
let decrypted = CryptoJS.AES.decrypt(
text,
CryptoJS.enc.Utf8.parse(key),
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
遇到的问题:
解决方案参考:
以下都是不同的错误报出上面问题的解决方案
1 https://blog.csdn.net/weixin_42124196/article/details/88555847
2 https://www.cnblogs.com/wangyang0210/p/13594019.html
3 https://segmentfault.com/a/1190000017540855
而我这里因为是与后台交互,遇到的问题与第二个有点类似,是key的类型没做处理、和mode设置不一致导致的,具体解决方案代码如下:
// 解密
decrypt(text, key) {
key = CryptoJS.enc.Utf8.parse(key.toString()); // 1 注意把key toString()处理
key = CryptoJS.enc.Base64.stringify(key); // 2
key = CryptoJS.enc.Base64.parse(key); // 3
let decrypted = CryptoJS.AES.decrypt(
text,
key,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
},
注意代码中 注释的1、2、3,对Key处理的部分