前端RSA加密解密:支持中文 – 基于jsencrypt使用任意长度密钥,对任意长度字符串进行分段加解密

前端RSA加密解密:支持中文 - 基于jsencrypt使用任意长度密钥,对任意长度字符串进行分段加解密

  • 基于jsencrypt的RSA加解密
    • 与其他超长加解密代码不同
      • 1、支持中文;
      • 2、支持任意密钥。
    • jsencrypt中要添加的代码
    • 前端使用示例
    • 后记

基于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”; 前面,添加下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    //任意长度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密钥、测试文本,可以运行后在文本框中更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!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="//i2.wp.com/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长度密钥的代码,肯定方便很多。

中文也不需要特别转码,后端也不用特别解码。

已经很长时间没有分享代码了,就当做个笔记吧。