背景

在开发中,通常会偷懒将用户的登录信息(账号密码)直接以明文的方式发送到后端,但我们的项目不是所有都用了https,这在传输过程中存在一定的风险,于是,我们可以通过RSA非对称加密,将用户输入的账号密码进行加密后传输到后端,后端通过私钥解密后得到原始数据,进行后续的逻辑处理。

生成密钥

这里我们可以使用linux自带的openssl来生成密钥,首先生成私钥,通过如下命令生成:

1
openssl genrsa -out rsa_private_key.pem 1024

参数1024表示生成的密钥长度为1024比特,默认为2048,生成私钥后,我们通过私钥生成公钥:

1
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

通过上述命令,即可生成公钥和私钥,对应两个文件,rsa_private_key.pemrsa_public_key.pem

PHP加密解密工具

上述生成了密钥,我们就可以直接用来进行加解密操作了,这里介绍php中实现rsa的加解密,密钥文件,我们可以直接使用,也可以将密钥字符复制到php文件中,这里不直接使用密钥文件。项目基于thinkphp5框架,我们在extra文件夹下新增一个文件rsa_key.php,内容如下:

1
2
3
4
5
6
7
8
9
10
<?php
/**
* rsa加密公钥私钥
*/
return array(
// 私钥
'private_key' => '原样复制私钥文件中的内容',
// 公钥
'public_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
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
<?php

namespace app\common\util;

class RSAUtil
{
public $privateKey;
public $publicKey;

/**
* 构造方法
* @access public
*/
public function __construct()
{
//这个函数可用来判断私钥是否是可用的,可用返回资源id Resource id
//如果直接使用密钥文件,可通过函数file_get_contents()进行读取
$this->privateKey = openssl_pkey_get_private(config('rsa_key.private_key'));
//这个函数可用来判断公钥是否是可用的
$this->publicKey = openssl_pkey_get_public(config('rsa_key.public_key'));
}

public function __call($method, $args)
{
return json(['status' => '01', 'msg' => '方法不存在']);
}

public function privateEncrypt($data)
{
$crypto = '';
foreach (str_split($data, 117) as $chunk) {
openssl_private_encrypt($chunk, $encryptData, $this->privateKey);
$crypto .= $encryptData;
}
//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
$encrypted = $this->urlsafeB64encode($crypto);
return $encrypted;
}

//加密码时把特殊符号替换成URL可以带的内容
function urlsafeB64encode($string)
{
$data = base64_encode($string);
$data = str_replace(array('+', '/', '='), array('-', '_', ''), $data);
return $data;
}

//解密码时把转换后的符号替换特殊符号
function urlsafeB64decode($string)
{
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}

//私钥加密的内容通过公钥可用解密出来
public function publicDecrypt($encrypted)
{
$crypto = '';
foreach (str_split($this->urlsafeB64decode($encrypted), 128) as $chunk) {
openssl_public_decrypt($chunk, $decryptData, $this->publicKey);
$crypto .= $decryptData;
}
return $crypto;
}

//公钥加密
public function publicEncrypt($data)
{
$crypto = '';
foreach (str_split($data, 117) as $chunk) {
openssl_public_encrypt($chunk, $encryptData, $this->publicKey);
$crypto .= $encryptData;
}
$encrypted = $this->urlsafeB64encode($crypto);
return $encrypted;
}

//私钥解密
public function privateDecrypt($encrypted)
{
$crypto = '';
foreach (str_split($this->urlsafeB64decode($encrypted), 128) as $chunk) {
openssl_private_decrypt($chunk, $decryptData, $this->privateKey);
$crypto .= $decryptData;
}
return $crypto;
}
}

到此,后端的加解密就实现了。

JS前端加密传输

在登录界面,用户输入账号密码信息,点击登录,当参数校验通过,我们就可以对参数进行加密后,提交表单,这里使用jsencrypt实现前端的加密。

1
2
3
4
5
6
function encrypt(data) {
const publicKey = {$private_key|json_encode};
let encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey)
return encryptor.encrypt(data)
}

这样,我们可以发现,提交的数据就是被加密过的密文了。

image-20220916152206475

后端接收到参数后,对参数进行解密,即可得到原始数据。

1
2
3
4
// 登录信息解密
$rsaUtil = new RSAUtil();
$user_name = $rsaUtil->privateDecrypt('user_name');
$password = $rsaUtil->privateDecrypt('password');