前端RSA长明文分段加密,后端解密偶现失败的解决方案

2,653 阅读3分钟

前言

最近公司一个项目出现了,前端RSA长明文分段加密后,后端分段解密会出现偶现解密失败的情况。看了git提交历史记录,前前后后若干个人改了若干个版本还是没从根本上解决这个问题。

分析

前端RSA加密使用的是 jsencrypt,但是这个库并不支持分段加密。毕竟RSA并不被推荐用来加密太长明文的情况,但是没办法,实际开发中会遇到需要加密很长文本的业务场景。公司项目中,使用的是某搜索引擎出来的结果,直接使用script标签引入,也不是基于jsencrypt的最新版本改造的分段加密。所以我最后想能不能基于jsencrypt的最新版本改造呢。

jsencrypt加密源码分析

以当前的最新版本(3.2.1)为例

jsencrypt encrypt方法源码位置

jsencrypt 加密直接将传入的明文调了this.getKey().encrypt(str)的加密方法,然后再转为base64编码返回。其实this.getKey就是jsencrypt作者各种感谢Tom Wu rsa.js中的RSAKey实例。

rsa加密方法源码位置

注意这个库的填充模式是pkcs#1。

this.n.bitLength是获取公钥的位数,比如常用的1024位。(this.n.bitLength() + 7) >> 3 为计算最大可加密字节数

接着看下填充模式 n < s.length + 11 是校验字符是否超出最大可加密的字节数,11是pkcs#1模式要求,所以1024位公钥最大可以加密的字节数是117

注意这里有个坑,n < s.length + 11, 这个s参数是传进来的明文,但是他直接用字符串的长度来和最大可加密的字节数比较大小了,如果是英文数字之类占一个字节的字符没啥问题,但是像汉字占3个字节的字符,这个判断就很不严谨。比如用1024位公钥加密100个汉字,不会报'Message too long for RSA'的错误,会生成正常的密文,但是后端会出现解密出来的结果不全或失败的情况

分析到这,其实问题就比较清楚了。jsencrypt加密时,一是没有根据明文的字节位数来进行严谨的判断拦截,二是不支持长明文的分段加密。

分段加解密扩展

我们可以依赖jsencrypt最新版本,写个类继承jsencrypt,然后扩展下分段加密和解密。

源码位置

分段加密时,先将字符串转为'字节数组',本质上不是,只是为了记录每个字符占用的字节数,null起个占位的效果。如果字节数组没超最大可加密字节,就直接加密,返回结果。如果超了,就需要分段加密

微信截图_20211208014207.png 源码位置

分段解密也是同样的道理。加密生成的密文位数是和公钥的位数一样的,但是rsa.js生成的密文是16进制,所以需要除以4,然后根据最大可加密生成密文的位数分割。比如1024位生成的16进制的密文长度是256。如果需要分段解密,就进行分割

最终方案

经过了分析,扩展改造和验证后,最终发布为一个npm包 jsencrypt-ext,用法也和jsencrypt库一样。

最后推荐一个RSA加解密的在线工具,这个支持分段加解密,方便调试验证。