基於jsencrypt的RSA加解密
RSA的詳細就不說了,jsencrypt是一個開源的js庫,大家應該都了解。它本身不支持長字符串(string size>key size)的加解密。
應該說RSA最大加密碼長度為key size,而jsencrypt也沒有提代分段加密的方法,或許為了性能不建議對超長字符串進行加解密。
與其他超長加解密代碼不同
1、支持中文;
中文不需要提前編碼就可以加密,后端也不需要特別解碼。
只要后端同樣依據KEY的最大解密長度進行分段解密就可以了。
2、支持任意密鑰。
由密鑰計算出可以加密的最大長度,而不是寫死在代碼中,所以支持任意的密鑰。
jsencrypt中要添加的代碼
本來是沒有注釋的,為了方便理解,添加了幾行助於理解。
順便把變量名整理了一下。
這里需要自行下載jsencrypt.js並放到相同目錄下。
直接改動jsencrypt.js文件,在文件最下方的 JSEncrypt.version = “3.0.0-rc.1”; 前面,添加下面的代碼:
//任意長度RSA Key分段加密解密長字符串
//獲取RSA key 長度
JSEncrypt.prototype.getkeylength = function () {
return ((this.key.n.bitLength()+7)>>3);
};
// 分段解密,支持中文
JSEncrypt.prototype.decryptUnicodeLong = function (string) {
var k = this.getKey();
//解密長度=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;
}
};
// 分段加密,支持中文
JSEncrypt.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;
}
};
//添加的函數與方法結束
前端使用示例
將下面的代碼保存為 .html 文件,和 .js 放在一起。
這里為了測試已經預置了RSA密鑰、測試文本,可以運行后在文本框中更改。
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>支持中文長字符串的RSA前端加解密代碼</title>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="./jsencrypt.js"></script>
</head>
<body>
<div>
<label for="userdata">加密內容</label>
<br/>
<textarea id="userdata" rows="10" cols="100">網絡上甚至CSDN上有非常多基於jsencrypt的RSA加解密的示例,這些示例在國外的一些網站上也有相同的源碼。
無一例外,他們都不能正確操作中文,或者無法判斷分段的長度,直接使用一個莫名其妙的117來分段。在RSA密鑰長度為1024時,可加密的長度為1024/8=128字節,為了加強加密效果,要求每次加密輸出的結果都不相同,於是會有一個11字節的padding,所以最終可以加密的長度為128-11=117字節。這就是為什么有些同學說,加密解密得到false或者null。
代碼中的var maxLength = ((k.n.bitLength()+7)>>3);給了我靈感,說明網上廣為流傳的代碼原作者,一開始是打算讓代碼適應所有長度的RSA KEY,只是(因為技術上的原因?)最終沒有實現。
分段加密:由於js使用的是Unicode,每個字符所占用的字節數是不同的。所以如果僅僅是統計字符數,是不能正確加密英文字符以外的字符串。所以分段時,必須嚴格控制要加密的字符串的字節長度絕對不能超過最大長度。這里使用for判斷該字符占用的字節數,在臨界值處分段加密。再將最終結果編碼后輸出。
分段解密:將結果解碼后,每2個字符編碼一個字符,所以能夠解密的長度為var maxLength = ((k.n.bitLength()+7)>>3)*2;。再使用正則表達式快速將字符串按長度截取為字符串數組,將數組內容解密輸出。
這里的公鑰和私鑰可以隨意替換為512/1024/2048甚至更長的密鑰對,都可以正確加解密。
</textarea>
<br/>
<label for="data">加密結果</label>
<br/>
<textarea type="text" id="data" rows="10" cols="100"></textarea>
<br/>
<label for="txt">解密結果</label>
<br/>
<textarea type="text" id="txt" rows="10" cols="100"></textarea>
<button id="testme" >加密與解密</button>
<br/>
<label for="privkey">私鑰</label>
<br/>
<textarea id="privkey" rows="10" cols="100">-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDntgxoZuo4CdVc
BV516Cwqn5lg0siSPnwPKGW3O2NoGwdIzuB5sD3+0d/N5RC0kCgF8UmnMmwzMuAd
0P3A4q0HQhA9wPXfhLX2rilPSbXGRewcoBKhrLhVGIpgTdUkPHPT3Kn6KDNDY9U6
OeH65xz6YDk7dMO4c6jr6f/HWNykbK4kNhk8F1oBrw8mlSMlNnuEYlnAHpFzs8gI
BNbqNdcgaXG24t7Rbi/Uwg1JzXfEy4LJWGY8hDP6Hri1FlgtkyBkI4ekDUlg5Caj
soKhbcCK+Z0WieB4vj4ezIDEXZZlCt1cPcElLqm/Bfx9k3hWoYAuO7EHEHgHzc3Q
3/dIKsz7AgMBAAECggEBANvbONq3C/YwHmo6De8CZSXsWbQtTHK3Jy+avSinCSN2
weqroQLV330x1pGej8NEJTW+RIyIo3HRDCY+bwfeDR+d55swxBtZ6O4vQrMg1YFU
RzzCBeux3xWfO201bM/9LEoSTpY2Hq4Kw/+DfJB9Slmng6aOnEcgN1/hn/iesHyx
dCxeBfvcD6N+u8P3a0GgZ0p3im0QibPhErq7tp9oQh5D2xnFsJqlJz9w+ikDg+Vf
DG/oweccTQ5MF1h67VfypGboZTJZ5Mc+u/IUFxNjVMklNQ57XYEzfOzoEDQ4zO+T
47Wf9klT2tqv4d5Rvfnjfln5FtVHqxzInpZa7a+HZEECgYEA9zYMnrm2ubQkPFSx
+rjRbG2rYEkodVClK7cO7Ugk4leKH1+tpvGnit6M6I6gggK0jHZueo8YR+1x7p98
HTLF9HBrdcgQ6Gq9r3u/XLZ0bCnO+mgyeg8O8XtiuHosdD5WEWSTV5P2qJNkGxS5
UKP5nc7cVZXnnCbqT/ibnQ10ExcCgYEA7/LtsVZmxUHbc0QGd4cfXaAwL7BaoZHF
gHAfgNsDvkBNHqSqpyiSCkhHjBq6bSzRfvDHywHGNhvJhJtOp5X9LBw93aWU712d
tUzWGFnaxAJXAXONMtn8xOFw+Vg3yApx7RfkuSooaMnWp+FgrMV0kkDYMMxtZN4j
xBC+U0UiE70CgYBs/pCb3ufYgrtDOlhqYdg8BTJ9NmQ3LUJVvtU++wMAJHaKlKW8
qGklSjA4TMIp8EVodMMLGFItTFxiSEDxorQyrOpEONxzjLRrTZU2rF8yXVCbiRtQ
Q5lkEPGawosdCWrrKjvobh1ff/SwF/gIvPNOh6kPtxMx/tpqPgNmQEtAKwKBgEqU
hVDDfDn/mEghcqkgNJ2TNqb794+UkYC0WPZiHK27qrzFjc1bDNlpUeO4Qw3ACnWc
PV1Z9dPHm0E+TJpGQmS9enU0DDDCNkytzzXOZ/LYj1aCJfcSTkCbmdPGmb/xjyuU
a6Ep+1lmsvOHV9cboHn88bVpNO9PJGrCkYWsTUU5AoGAE2S2Zf5RlRICDd7H5/6y
bMzQUREnLIopUx1yNZSuLAOvsXq/wO1Utj/NTtZdJ0CRDhvUXipL7XK5p+sVVswV
BIXHeMYCML7XZXjPICE1EcMR57IxukbEpd+7p5i/deNshgYIHMSpYeRE6TdqXeT4
Ae3ypZCI0GPOGiMoLTJlySo=
-----END PRIVATE KEY-----</textarea>
<br/>
<label for="pubkey">公鑰</label>
<br/>
<textarea id="pubkey" rows="10" cols="100">-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA57YMaGbqOAnVXAVedegs
Kp+ZYNLIkj58DyhltztjaBsHSM7gebA9/tHfzeUQtJAoBfFJpzJsMzLgHdD9wOKt
B0IQPcD134S19q4pT0m1xkXsHKASoay4VRiKYE3VJDxz09yp+igzQ2PVOjnh+ucc
+mA5O3TDuHOo6+n/x1jcpGyuJDYZPBdaAa8PJpUjJTZ7hGJZwB6Rc7PICATW6jXX
IGlxtuLe0W4v1MINSc13xMuCyVhmPIQz+h64tRZYLZMgZCOHpA1JYOQmo7KCoW3A
ivmdFongeL4+HsyAxF2WZQrdXD3BJS6pvwX8fZN4VqGALjuxBxB4B83N0N/3SCrM
+wIDAQAB
-----END PUBLIC KEY-----</textarea>
<br/>
</div>
<script type="text/javascript">
$(function() {
$('#testme').click(function() {
var encrypt = new JSEncrypt();
encrypt.setKey($('#pubkey').val());
console.log(encrypt.getkeylength());
var data = encrypt.encryptUnicodeLong($('#userdata').val());
$('#data').val(data);
var decrypt = new JSEncrypt();
decrypt.setKey($('#privkey').val());
var txt = decrypt.decryptUnicodeLong(data);
$('#txt').val(txt);
});
});
</script>
</body>
</html>
后記
代碼能夠寫得好一些的,盡量寫好一些。比較懶,不喜歡一直改,這種功能類的,最好是一次搞定,多次復用。
比起將117寫死在代碼中,只能適用於1024長度密鑰的代碼,肯定方便很多。
————————————————
版權聲明:本文為CSDN博主「小傻哥」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lionking1990/article/details/106092990/
博主:親測有效~