伞仙博客 伞仙博客
首页
  • 前端文章

    • HTML-CSS
    • JavaScript
    • Vue
    • Node
  • Python数据分析
  • Git
  • 博客搭建
  • 其他
设计
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
GitHub

伞仙

我是伞仙
首页
  • 前端文章

    • HTML-CSS
    • JavaScript
    • Vue
    • Node
  • Python数据分析
  • Git
  • 博客搭建
  • 其他
设计
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
GitHub
  • HTML-CSS文章

  • JavaScript文章

    • 函数防抖与函数节流
    • 文本选中与复制
    • B KB MB GB的转化方法
    • 前端RSA加密与解密
      • 秘钥生成
        • 1、 命令行工具生成
        • 2、使用Node-RSA生成密钥
        • 3、使用在线生成工具
      • 前端加密
        • jsencrypt介绍
        • 案例
      • 后端(node)解密
        • Node-RSA介绍
        • 案例
        • Node-RSA基础使用
  • Vue文章

  • Node文章

  • 前端
  • JavaScript文章
伞仙
2021-01-02

前端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
1
2
3
4
5

使用bash生成密钥

这样就生成了private.pem 和 public.pem两个文件,可以利用任意文本编辑器进行查看

密钥截图1

# 2、使用Node-RSA生成密钥

安装

npm install node-rsa
yarn add node-rsa
1
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-----
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3、使用在线生成工具

脚本之家RSA密钥生成工具

在线生成工具

# 前端加密

# jsencrypt介绍

jsencrypt就是一个基于rsa加解密的js库

安装

npm install jsencrypt
yarn add jsencrypt
1
2

使用

// webpack环境下
import JSEncrypt from 'jsencrypt'
const encryptor = new JSEncrypt() // 创建加密对象实例
1
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>
1
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>
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

前端RSA加密

# 后端(node)解密

# Node-RSA介绍

Node.js RSA 库

  • 纯粹的JavaScript
  • 不需要OpenSSL
  • 生成密钥
  • 支持长消息的加密/解密
  • 签名和验证

安装

npm install node-rsa
yarn add node-rsa
1
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
1
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}
	- 其他设置
*/
1
2
3
4
5
6
7
8
9
10
11
12
# 创建 “空” 键
const key = new NodeRSA()
1
# 生成新的512位长度密钥
const key = new NodeRSA({b: 512})
1

# 导入/导出密钥

/*
语法:
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)
1
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'
}
*/
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
在 GitHub 上编辑此页
#js #安全
上次更新: 2021/01/03, 17:01:00

← B KB MB GB的转化方法 Vue学习记录 →

最近更新
01
快速注册github账号
01-26
02
解决github图片不显示问题
01-26
03
Git如何删除所有提交历史
01-25
更多文章>
Theme by Vdoing | Copyright © 2020-2021 伞仙 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式