前端RSA加密与解密
# 前端RSA加密与解密
加密传输加密主要有两种方式:对称加密和非对称加密。
对称加密对称加密算法在加密和解密时使用的是同一个秘钥。
对称加密的模式是:- 甲方选择某一种加密规则,对信息进行加密- 乙方使用同一种规则,对信息进行解密客户端和服务端进行通信,采用对称加密,如果只使用一个秘钥,很容易破解;如果每次用不同的秘钥,海量秘钥的管理和传输成本又会比较高。
非对称加密非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
非对称加密的模式则是:- 乙方生成两把密钥(公钥和私钥)。
公钥是公开的,任何人都可以获得,私钥则是保密的- 甲方获取乙方的公钥,然后用它对信息加密- 乙方得到加密后的信息,用私钥解密。
即使黑客拿到了公钥,没有私钥也是没有办法解密,不考虑彩虹表的情况,完全可以长期使用一对秘钥。
RSA算法最经典的非对称加密算法是RSA算法。
目前的应用场景是在用户注册或登录的时候,用公钥对密码进行加密,再去传给后台,后台用私钥对加密的内容进行解密,然后进行密码校验或者保存到数据库。这样做就算数据库被盗,用户保存的明文密码也不会直接泄露。
# RSA加密与解密
- 使用 公钥 加密的数据,利用 私钥 进行解密
- 使用 私钥 加密的数据,利用 公钥 进行解密
# 秘钥生成
# 1、 命令行工具生成
Windows系统使用git命令行工具(Mac系统内置OpenSSL(开源加密库),可以直接在终端上使用命令)
# 生成私钥,密钥长度为1024bit
openssl genrsa -out private.pem 1024
# 从私钥中提取公钥
openssl rsa -in private.pem -pubout -out public.pem
2
3
4
5
这样就生成了private.pem 和 public.pem两个文件,可以利用任意文本编辑器进行查看
# 2、使用Node-RSA生成密钥
安装
npm install node-rsa
yarn add node-rsa
2
生成密钥
const NodeRSA = require('node-rsa');
const key = new NodeRSA({b: 512}); //生成新的512位长度密钥
const publicDer = key.exportKey('public');
const privateDer = key.exportKey('private');
console.log('公钥:',publicDer); // 放在前端用于加密或验证
console.log('私钥:',privateDer); // 放在后台用于解密或签名
/* 输出内容
公钥: -----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIJZqkEOuh8IJwqzVS2kuNR+wkW4M71q
ATntdOr0qP/N2EhtajVfoyrnMawt39ryA7k0v3vz45IF1IvXHJgpVHUCAwEAAQ==
-----END PUBLIC KEY-----
私钥: -----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAIJZqkEOuh8IJwqzVS2kuNR+wkW4M71qATntdOr0qP/N2EhtajVf
oyrnMawt39ryA7k0v3vz45IF1IvXHJgpVHUCAwEAAQJAB4UuymQUHtg0kGx6PJDl
TPUnNiiDa6ki+vmVJj0JRwC+S9mandunSOgPOPKIjq0qDESEzDEcTzyEB0QPR0fS
IQIhAPKgL3NKRdZBM0NczcDn5gObz7XqkbLd13/x6lH5KsY9AiEAiYkaha5Zb3B7
7bNCf7A9LKMChPMXRby5UJ8QklVj4pkCIEZ9A0wbZ+63Qo1viNdiiBDEU7QmUe4F
RXaGce0e1q6BAiBD/pQuItP0VBfwm/70QZz8xFoqgEOxJmw3f2wh7DVFgQIgCtIH
WdT21xyCSESE1/2pPx4qPwGVYE7HWnszvrx7+SI=
-----END RSA PRIVATE KEY-----
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3、使用在线生成工具
# 前端加密
# jsencrypt介绍
jsencrypt就是一个基于rsa加解密的js库
安装
npm install jsencrypt
yarn add jsencrypt
2
使用
// webpack环境下
import JSEncrypt from 'jsencrypt'
const encryptor = new JSEncrypt() // 创建加密对象实例
2
3
<!-- 导入前端RSA加密模块 jsencrypt -->
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.0.0-rc.1/jsencrypt.js"></script>
<script>
const encryptor = new JSEncrypt() // 创建加密对象实例
</script>
2
3
4
5
# 案例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端RSA加密</title>
<!-- 导入前端RSA加密模块 jsencrypt -->
<script src="./node_modules/jsencrypt/bin/jsencrypt.min.js"></script>
</head>
<body>
<h1>RSA加密</h1>
<p>
要加密的内容 [模拟用户输入的密码](明文):<input id="plaintext" type="text" />
</p>
<p>
加密后的内容 [前端发送请求前加密](密文):<textarea id="ciphertext" rows="10" cols="65"></textarea>
</p>
<p>
解密后的内容 [后台接收密文后解密](明文):<input id="decrypt"></input>
</p>
<p>
<button id="btn">加密</button>
<button id="btn1">解密</button>
</p>
<script>
function encryption(){
const encryptor = new JSEncrypt() // 创建加密对象实例
const plaintext = document.getElementById('plaintext').value // 获取要加密的内容(明文)
// 公钥:你生成的公钥,把换行符去掉
const publicKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCI5l9xVRpYqCzH3ykPgwHqjwTFpmWLjMm5lrXpuWynw1+SM1aQ2uuWwuynluGPtaYRcmGI/ZFbboMFn3gZ/xJaqsXZQjW2RxC/h9Xg75L5j+6wBxw8HCv16YZKehA3nSO13y5O5P7+KfH2ES+vEX/dged9Y81nvdkZRg200qXABwIDAQAB-----END PUBLIC KEY-----'
encryptor.setPublicKey(publicKey) // 设置公钥
const ciphertext = encryptor.encrypt(plaintext) // 对内容进行加密
document.getElementById('ciphertext').value = ciphertext
}
// 前端一般情况下用不到解密,这边只是做个示范,请忽略!!!
function decrypt(){
const decrypt = new JSEncrypt() // 创建解密对象实例
const ciphertext = document.getElementById('ciphertext').value // 获取加密后的内容(密文)
// 上面公钥对应的私钥,这边只是做个示范,请不要把私钥放在前端!!!
const privateKey = '-----BEGIN RSA PRIVATE KEY-----MIICXQIBAAKBgQCI5l9xVRpYqCzH3ykPgwHqjwTFpmWLjMm5lrXpuWynw1+SM1aQ2uuWwuynluGPtaYRcmGI/ZFbboMFn3gZ/xJaqsXZQjW2RxC/h9Xg75L5j+6wBxw8HCv16YZKehA3nSO13y5O5P7+KfH2ES+vEX/dged9Y81nvdkZRg200qXABwIDAQABAoGAd65e1id+Ru+PZpTTwrnXXQX3OAvGTn+gg10cX944/VkyHhA/p5ebyktStRiUzRwSuMH0PtzezL4KUUoepyt1EPTKiNS8Wxrk8IcU4hQYrnrdpr8UWBLrgC2pc85PIQx6RyUIgJcECT1rQftNHdt9USCIFrDp1IRqF9qdUlLz/DECQQD77GV3Ci+3NJYYUSX0qf3Mdd3S9SsU5ezk/cgWyGqWtXNjGgQtzf+rbU8aYyXn58QtNf5YEmMBeMMXjNaZccm7AkEAix18jfWmJXs5B4vo+KC/jWgICsNwVD6PPJe69wn/9tJm+97vPHcrlKIpqjEJs06jqFB8kOOTArYyLs1ONnVIJQJBAMGs7BSocCaY9wua12NRjR0zQGZ+tbBLU+R4duuNCOT0etElnzDXvkc8siPHNc0kEV3wtKlg+VyYSuRAEnvFTyECQHtsmNJWTKdacRmZ7wOPkwOBdgkepq2Hp4uJzs5Y5+jzeX0jqLvLuzWuviqKQWH9dkPhzPK7hfXU8icF7ctxOKUCQQDJUKacdwdzXlRFWp3VVFsVewIgUlKSAr1/QPxyNtQBLstjj41FyBl/XO6Drg3dy2t251UDSTMwUvUEOe+6xitU-----END RSA PRIVATE KEY-----'
decrypt.setPrivateKey(privateKey)// 设置私钥
const plaintext = decrypt.decrypt(ciphertext)//解密已加密的内容
document.getElementById('decrypt').value = plaintext
}
// 给按钮添加事件
document.getElementById('btn').addEventListener('click', encryption)
document.getElementById('btn1').addEventListener('click', decrypt)
</script>
</body>
</html>
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
# 后端(node)解密
# Node-RSA介绍
Node.js RSA 库
- 纯粹的JavaScript
- 不需要OpenSSL
- 生成密钥
- 支持长消息的加密/解密
- 签名和验证
安装
npm install node-rsa
yarn add node-rsa
2
# 案例
一个简单的解密案例
const NodeRSA = require('node-rsa');
// 前端公钥对应的私钥,与上面的案例使用同一个密钥
const privateKey = '-----BEGIN RSA PRIVATE KEY-----MIICXQIBAAKBgQCI5l9xVRpYqCzH3ykPgwHqjwTFpmWLjMm5lrXpuWynw1+SM1aQ2uuWwuynluGPtaYRcmGI/ZFbboMFn3gZ/xJaqsXZQjW2RxC/h9Xg75L5j+6wBxw8HCv16YZKehA3nSO13y5O5P7+KfH2ES+vEX/dged9Y81nvdkZRg200qXABwIDAQABAoGAd65e1id+Ru+PZpTTwrnXXQX3OAvGTn+gg10cX944/VkyHhA/p5ebyktStRiUzRwSuMH0PtzezL4KUUoepyt1EPTKiNS8Wxrk8IcU4hQYrnrdpr8UWBLrgC2pc85PIQx6RyUIgJcECT1rQftNHdt9USCIFrDp1IRqF9qdUlLz/DECQQD77GV3Ci+3NJYYUSX0qf3Mdd3S9SsU5ezk/cgWyGqWtXNjGgQtzf+rbU8aYyXn58QtNf5YEmMBeMMXjNaZccm7AkEAix18jfWmJXs5B4vo+KC/jWgICsNwVD6PPJe69wn/9tJm+97vPHcrlKIpqjEJs06jqFB8kOOTArYyLs1ONnVIJQJBAMGs7BSocCaY9wua12NRjR0zQGZ+tbBLU+R4duuNCOT0etElnzDXvkc8siPHNc0kEV3wtKlg+VyYSuRAEnvFTyECQHtsmNJWTKdacRmZ7wOPkwOBdgkepq2Hp4uJzs5Y5+jzeX0jqLvLuzWuviqKQWH9dkPhzPK7hfXU8icF7ctxOKUCQQDJUKacdwdzXlRFWp3VVFsVewIgUlKSAr1/QPxyNtQBLstjj41FyBl/XO6Drg3dy2t251UDSTMwUvUEOe+6xitU-----END RSA PRIVATE KEY-----'
// 前端加密后的内容(密文),这是使用的是前面截图里的 apple 密文
const ciphertext = 'J0dAxlsWEQxSe8FvCEyq4Y//CFFlyEluzjpHxQ/SNC3Qy0VJ4cCy0T2VUlPBZK1/S7n2sG9wKAw8Gr/UiRuwWZ6hSU0PejKW861xpSsMr3D8WoaEO80QtudyNiJmCkV7oXgKaid+o0cJ+45RTx0KePHjLjiwKZ0dtCDxjFH75tk=';
const key = new NodeRSA(privateKey);
// 因为jsencrypt自身使用的是pkcs1加密方案, nodejs需要修改成pkcs1
key.setOptions({encryptionScheme: 'pkcs1'});
decrypted = key.decrypt(ciphertext, 'utf8');
console.log(decrypted); // apple
2
3
4
5
6
7
8
9
10
11
12
# 注意
Base64传输中加号会转变为空格,前端传输到后台是字符串是断开的。
解决方法:
方式一:
后台把空格替换回加号
或者在发送前把加号替换成不常用符号,到后台再替换回来
后台代码:ciphertext = ciphertext.replaceAll(“ ”,”+");
方式二:
通过encodeURIComponent对密文编码,
前端代码:ciphertext = encodeURIComponent(ciphertext);
后台代码:ciphertext = decodeURIComponent(ciphertext);
# Node-RSA基础使用
# 创建实例
const NodeRSA = require('node-rsa');
const key = new NodeRSA([keyData, [format] ], [options])
/*
- keyData {string|buffer|object}
- 用于生成密钥或以支持的格式之一生成密钥的参数
- format {string}
- 导入密钥的格式。查看有关导出/导入部分格式的更多详细信息
- options {object}
- 其他设置
*/
2
3
4
5
6
7
8
9
10
11
12
# 创建 “空” 键
const key = new NodeRSA()
# 生成新的512位长度密钥
const key = new NodeRSA({b: 512})
# 导入/导出密钥
/*
语法:
key.importKey(keyData, [format])
key.exportKey([format])
- keyData - {string|buffer|object} - 可能是:
- 键入PEM字符串
- 包含PEM字符串的缓冲区
- 包含DER编码数据的缓冲区
- 对象包含关键组件
format - {string} - 用于导出/导入的格式
*/
// 导入
key.importKey('私钥XXXXXXXXXXXXXXXX', 'private');
// 导出
const publicDer = key.exportKey('public')
const privateDer = key.exportKey('private')
console.log('公钥:',publicDer)
console.log('私钥:',privateDer)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 加密/解密
/*
key.encrypt(text, [encoding], [source_encoding]) // 加密
key.decrypt(text, [encoding]) // 解密
// 使用私钥进行加密
key.encryptPrivate(text, [encoding], [source_encoding])
// 使用公钥解密
key.decryptPublic(text, [encoding])
*/
// 案例
const NodeRSA = require('node-rsa');
const key = new NodeRSA({b: 512}); //生成新的512位长度密钥
let ciphertext = key.encrypt('apple', 'base64', 'utf8')
let plaintext = key.decrypt(ciphertext, 'utf8')
console.dir({ciphertext, plaintext});
ciphertext = key.encryptPrivate('apple', 'base64', 'utf8')
plaintext = key.decryptPublic(ciphertext, 'utf8')
console.dir({ciphertext, plaintext});
/* console.dir输出
{
ciphertext: 'HSM9EItj6MtvwNIe/1VI3gY/DlviTCQ1ApSEv0Dk5d4U08s4Rn11GtwMeZGbDidW6dV5GIrzWKCvAMt6HZwbeg==',
plaintext: 'apple'
}
{
ciphertext: 'We2+hwsSzN0/VQoSE+6oBaWLNkPGTLvvtuwa5810/atK5IhvHqo6Is4nXV869clLpAqjvap+JaZ5HpVGjLjnXQ==',
plaintext: 'apple'
}
*/
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