jsrsasign 前端 RSA 加密 node 端解密

2023-04-23 16:14:49 浏览数 (2)

有些场景下需要前端做加密,比如登录的时候,用户输入的密码需要传输给后端,为了保证安全,最好前端先加密后传输,后端接收到之后,再解密拿到明文。 需要在不同端进行加密解密的话 RSA 非对称加密算法最适合。

一、RSA 简介

RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制。 在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK。 正是基于这种理论,1978年出现了著名的RSA算法,它通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册。为提高保密强度,RSA密钥至少为500位长。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要。 RSA允许你选择公钥的大小。512位的密钥被视为不安全的;768位的密钥不用担心受到除了国家安全管理(NSA)外的其他事物的危害;RSA在一些主要产品内部都有嵌入,像 Windows、网景 Navigator、 Quicken和 Lotus Notes。

二、jsrsasign

RSA 加密的第三方库有很多,用的比较多的是 node-rsa 和 jsrsasign。但是 node-rsa 最近更新已经是三年前了,jsrsasign 更新的比较频繁,几天前才有更新,周下载量30万次,算是比较靠谱的。 jsrsasign 官方文档地址:https://kjur.github.io/jsrsasign/

三、openssl 生成公钥和私钥

加密解密需要用到 pem 格式的公钥和私钥,秘钥可以通过 openssl 自己生成。

openssl 生成私钥

打开终端,输入 openssl回车之后就进入了 OpenSSL 命令行的交互。 生成私钥命令:

代码语言:javascript复制
genrsa -out /your_path/rsa_pricate.pem 2048

-out指定了输出的路径,最后的 2048 表示生成 2048 位的秘钥。

打开生成的 pem 文件,可以看到如下格式的内容。

代码语言:javascript复制
-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxx
-----END RSA PRIVATE KEY-----

根据私钥生成公钥

公钥是根据私钥生成的,继续在 OpenSSL 交互式命令行里输入以下命令

代码语言:javascript复制
rsa -in /your_path/rsa_pricate.pem -pubout -out /your_path/rsa_public.pem

your_path替换成你电脑中的真实路径即可。执行完后得到公钥:

代码语言:javascript复制
-----BEGIN PUBLIC KEY-----
xxxxxxxxxxxxx
-----END PUBLIC KEY-----

四、前端加密

安装依赖

代码语言:javascript复制
npm install jsrsasign jsrsasign-util

如果用了 TS,还需要安装对应的类型提示

代码语言:javascript复制
npm i --save-dev @types/jsrsasign 

然后对密码进行加密。

代码语言:javascript复制
import { KJUR, KEYUTIL, RSAKey } from 'jsrsasign'

function encryptKey(password: string) {
  const keyObj = KEYUTIL.getKey(publicKey);
  const encryptPwd = KJUR.crypto.Cipher.encrypt(password, keyObj as RSAKey, 'RSAOAEP')
  console.log('密文:', encryptPwd)
}

publicKey就是上一节生成的公钥的文本,先通过 KEYUTIL.getKey(publicKey)获取秘钥的对象,再通过 KJUR.crypto.Cipher.encrypt()进行加密。 encrypt(s, keyObj, algName)方法有三个参数:

  • s: 要加密的文字
  • keyObj: 通过 KEYUTIL 获取的公钥的对象
  • algName: 加密的方法名

algName 有以下几种取值:

  • RSA - RSA/ECB/PKCS1Padding (default for RSAKey)
  • RSAOAEP - RSA/ECB/OAEPWithSHA-1AndMGF1Padding
  • RSAOAEP224 - RSA/ECB/OAEPWithSHA-224AndMGF1Padding(*)
  • RSAOAEP256 - RSA/ECB/OAEPWithSHA-256AndMGF1Padding
  • RSAOAEP384 - RSA/ECB/OAEPWithSHA-384AndMGF1Padding(*)
  • RSAOAEP512 - RSA/ECB/OAEPWithSHA-512AndMGF1Padding(*)

调用加密方法加密一串英文:encryptKey('Hello Javascript'),得到密文如下:

代码语言:javascript复制
密文: 9999d156ce02a130c51c3a94c1010aa8ead62499a373f51f57177894b8634baab62a968c12306c46a2aee8358bd224a0138c7d7c38678a56ed2f6537667b67bdf8b5b7fd555091283609b7077e7146a0cd0f3644f2ad1680bb9cf9ee104305b19153eef15950f9d2b3fdd0074ed420169e6cf63d8aa95aeda7a4dc28f201119eef1f6e354e7e18d43c27e528104fdfecf4363fdb676c96ef5ffc8ba1b99c1bff6955970bf71c4220a7f81e34080f854f43738b414d798c70232719e12d22398e8d36f414556c6dc297525e391be63a5542b2e15834c1870af1ab109c74cf2b094d778a4abc0e82ed43ad0664f2a09806c4d98df9d71e9338236e6e8f7115720e

五、node 端解密

node 端依然可以用 jsrsasign 来解密,跟前端一样安装依赖

代码语言:javascript复制
npm install jsrsasign jsrsasign-util

如果用了 TS,还需要安装对应的类型提示

代码语言:javascript复制
npm i --save-dev @types/jsrsasign 

解密方法如下:

代码语言:javascript复制
import { KEYUTIL, KJUR, RSAKey } from 'jsrsasign';

decryptKey(key: string) {
  const keyObj = KEYUTIL.getKey(privateKey);
  const name = KJUR.crypto.Cipher.decrypt(key, keyObj as RSAKey, 'RSAOAEP');
  console.log('明文:', name);
}

privateKey是第三节生成的私钥的文本,通过 KEYUTIL.getKey()获取私钥的对象,然后再通过 KJUR.crypto.Cipher.decrypt()进行解密。需要注意的是第三个参数 algName要与前端加密时的方法一样。 输出:

明文: Hello Javascript

这样前端用公钥加密,node 端用私钥解密就完成了。

六、中文乱码问题解决

通过上面的方法,加密解密英文没问题,但加密中文解密出来会是乱码。 比如原文是 Javascript你好我是密码,解密之后得到的是 Javascript}/Æ,后面的中文乱码了。<br />看网上的解决方法有些是修改解密方法,其实最简单的方法是在加密的时候,先用 encodeURI()对中文进行编码之后再进行加密,解密的时候,先解密再用 decodeURI()`将解密后的文本进行转码得到正确的中文。 完整的代码如下:

代码语言:javascript复制
import { KEYUTIL, KJUR, RSAKey } from 'jsrsasign';

// 加密
function encryptKey(password: string) {
  const keyObj = KEYUTIL.getKey(publicKey);
  const encryptPwd = KJUR.crypto.Cipher.encrypt(encodeURI(password), keyObj as RSAKey, 'RSAOAEP')
  console.log('密文:', encryptPwd)
}

// 解密
decryptKey(key: string) {
  const keyObj = KEYUTIL.getKey(privateKey);
  const name = decodeURI(
    KJUR.crypto.Cipher.decrypt(key, keyObj as RSAKey, 'RSAOAEP') || '',
  );
  console.log('明文:', name);
}

0 人点赞