php-rsa 加密解密

目录
  1. 1. 1.什么是RSA加密
  2. 2. 2.使用场景
  3. 3. 3.生成私钥、公钥
  4. 4. 4.php-rsa 加密解密实例
  5. 5. 5.密钥文件后缀是pfx和cer
  6. 6. 6.其它问题
    1. 6.1. 银行给的公钥是一串字符串
    2. 6.2. 创建字符串格式的密钥对
    3. 6.3. 加密数据长度要求
    4. 6.4. 注意加解密前进行编码和解码
  7. 7. 7.实用的RSA类

1.什么是RSA加密

RSA (详见维基百科)算法是现今使用最广泛的公钥密码算法,也是号称地球上最安全的加密算法,与 md5 和 sha1 不同,到目前为止,也只有极短的RSA加密被破解。
那么什么是公匙密码算法呢,根据密钥的使用方法,可以将密码分为对称密码和公钥密码,接下来我们来简单说明下它们两个。

  • 对称密码:加密和解密使用同一种密钥的方式,常用的算法有 DES 以及 AES。
  • 公钥密码:加密和解密使用不同的密码的方式,因此公钥密码通常也称为非对称密码,常用的算法有 RSA。

由于本文讨论的是 php 的 RSA 加密实例,这里就不详细说明了,对于 RSA 算法有兴趣的朋友可以查看下面的文章
《带你彻底理解RSA算法原理》
对于 php 更多加密方式有兴趣的朋友可以查看下面的文章
《PHP数据加密技术与密钥安全管理》

2.使用场景

  • 为移动端(IOS,安卓)编写 API 接口
  • 进行支付、真实信息验证等安全性需求较高的通信
  • 与其他第三方或合作伙伴进行重要的数据传输

3.生成私钥、公钥

既然 RSA 是非对称加密,那么就先必须生成所需要的私钥和公钥,以 ubuntu 为例。首先下载开源 RSA 密钥生成工具 openssl (通常为 linux 系统自带),如果没有,可以通过以下命令进行安装:
apt-get install openssl
当 openssl 安装完毕后,我们就可以开始生成私钥、公钥了。首先,生成原始 RSA 私钥文件:
openssl genrsa -out rsa_private_key.pem 1024

注:这里生成了一个长度为 1024bit 的密钥,转化为字节就是 128byte

其次,将原始 RSA 私钥转换为 pkcs8 格式:
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem

最后,生成 RSA 公钥:
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
在需要使用的时候,我们将私钥 private_key.pem 放在服务器端,公钥发放给需要与我们进行加密通信的一方就可以了。

4.php-rsa 加密解密实例

现在我们可以使用 php 进行 RSA 的加密解密了,但在此之前,你首先要看看你有没有安装或开启 php 的 openssl 扩展,可以通过文件输出 phpinfo() 或者通过命令行输出 php -m | less 来查看是否安装开启了此扩展,也可以通过 extension_loaded() 函数来判断扩展是否开启,如果没有,则通过该命令进行安装(以 ubuntu 为例):
apt-get install php7.0-openssl
当然,如果你是 Windows 操作系统,可以下载对应版本的 php_openssl.dll。

好了,现在我们来编写一个 php-RSA 的服务器类库,这个类库的工作其实很简单,就是封装一些 php 针对 RSA 操作的函数,方便我们加密解密。注:这个只是最简单的RSA类,实际用处不大

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
class Rsa
{
private $_config = [
'public_key' => '',
'private_key' => '',
];

public function __construct($private_key_filepath, $public_key_filepath) {
$this->_config['private_key'] = $this->_getContents($private_key_filepath);
$this->_config['public_key'] = $this->_getContents($public_key_filepath);
}

/**
* @uses 获取文件内容
* @param $file_path string
* @return bool|string
*/
private function _getContents($file_path) {
file_exists($file_path) or die ('密钥或公钥的文件路径错误');
return file_get_contents($file_path);
}

/**
* @uses 获取私钥
* @return bool|resource
*/
private function _getPrivateKey() {
$priv_key = $this->_config['private_key'];
return openssl_pkey_get_private($priv_key);
}

/**
* @uses 获取公钥
* @return bool|resource
*/
private function _getPublicKey() {
$public_key = $this->_config['public_key'];
return openssl_pkey_get_public($public_key);
}

/**
* @uses 私钥加密
* @param string $data
* @return null|string
*/
public function privEncrypt($data = '') {
if (!is_string($data)) {
return null;
}
return openssl_private_encrypt($data, $encrypted, $this->_getPrivateKey()) ? base64_encode($encrypted) : null;
}

/**
* @uses 公钥加密
* @param string $data
* @return null|string
*/
public function publicEncrypt($data = '') {
if (!is_string($data)) {
return null;
}
return openssl_public_encrypt($data, $encrypted, $this->_getPublicKey()) ? base64_encode($encrypted) : null;
}

/**
* @uses 私钥解密
* @param string $encrypted
* @return null
*/
public function privDecrypt($encrypted = '') {
if (!is_string($encrypted)) {
return null;
}
return (openssl_private_decrypt(base64_decode($encrypted), $decrypted, $this->_getPrivateKey())) ? $decrypted : null;
}

/**
* @uses 公钥解密
* @param string $encrypted
* @return null
*/
public function publicDecrypt($encrypted = '') {
if (!is_string($encrypted)) {
return null;
}
return (openssl_public_decrypt(base64_decode($encrypted), $decrypted, $this->_getPublicKey())) ? $decrypted : null;
}
}

好了,现在我们调用 Rsa 类,对数据进行加密解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$private_key = 'private_key.pem'; // 私钥路径
$public_key = 'rsa_public_key.pem'; // 公钥路径
$rsa = new Rsa($private_key, $public_key);

$origin_data = '这是一条测试数据';

$ecryption_data = $rsa->privEncrypt($origin_data);

$decryption_data = $rsa->publicDecrypt($ecryption_data);

echo '私钥加密后的数据为:' . $ecryption_data;
echo PHP_EOL;
echo '公钥解密后的数据为: ' . $decryption_data;
echo PHP_EOL;

最后要说明的是,公钥、私钥都可以加密,也都可以解密。其中:用公钥加密需要私钥解密,称为“加密”。由于私钥是不公开的,确保了内容的保密,没有私钥无法获得内容;用私钥加密需要公钥解密,称为“签名”。由于公钥是公开的,任何人都可以解密内容,但只能用发布者的公钥解密,验证了内容是该发布者发出的。

注:做支付的时候,一般来说我这边使用银行的工具自己生成一套公钥私钥对,银行机构也有一套公钥私钥对。我要把自己的公钥上传到银行,然后从网站上复制银行机构的公钥到本地。提交订单到银行的时候,有两个必传参数,encryptData和sign。encryptData是把业务参数排序后,我用银行公钥加密数据赋值给encryptData,银行收到后会用银行私钥进行解密。sign是验证数据是否合法的令牌,使用openssl_sign()和我的私钥生成,由银行进行验证。用户付款后,银行会发送一个异步通知给我,我要使用银行公钥和openssl_verify()来验证通知是不是伪造的。要注意的一点是“签名”和“加密”是两码事。

5.密钥文件后缀是pfx和cer

cer文件相当于公钥(可以发给客户端),pfx是密钥(必须严格保存于服务器端不能泄露)。
php服务端利用内置函数生成公钥和密钥,先生成cer文件和pfx文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$dn = array(
"countryName" => 'zh', //所在国家名称
"stateOrProvinceName" => 'GuangDong', //所在省份名称
"localityName" => 'ShenZhen', //所在城市名称
"organizationName" => 'baibai', //注册人姓名
"organizationalUnitName" => 'company', //组织名称
"commonName" => 'bbb', //公共名称
"emailAddress" => '123@.qq.com' //邮箱
);
$privkeypass = 'cf'; //私钥密码
$numberofdays = 3650; //有效时长
$cerpath = "./test.cer"; //生成证书路径
$pfxpath = "./test.pfx"; //密钥文件路径//生成证书
$privkey = openssl_pkey_new();
$csr = openssl_csr_new($dn, $privkey);
$sscert = openssl_csr_sign($csr, null, $privkey, $numberofdays);
openssl_x509_export_to_file($sscert, $cerpath); //导出证书到文件
//openssl_pkcs12_export_to_file($sscert, $pfxpath, $privkey, $privkeypass); //生成密钥文件
openssl_pkey_export_to_file($privkey, $pfxpath); //生成密钥文件

以下是是php端进行简单测试的代码:

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

//使用私钥生成签名
$cer_key = file_get_contents($pfxpath); //获取密钥内容
openssl_pkcs12_read($cer_key, $certs, $privkeypass); //重要:从pfx文件中拿到私钥
openssl_sign($data, $signMsg, $certs['pkey'],OPENSSL_ALGO_SHA1); //注册生成加密信息
$signMsg = base64_encode($signMsg); //base64转码加密信息
//echo $signMsg;

//使用公钥验证签名
$cer_key = file_get_contents($cerpath); //获取证书内容
$unsignMsg=base64_decode($signMsg);//base64解码加密信息
$cer = openssl_x509_read($cer_key); //读取公钥
$res = openssl_verify($data, $unsignMsg, $cer); //验证
echo $res; //输出验证结果,1:验证成功,0:验证失败


// ---------------------------------------------------------

$data = "123456";
$crypted = "";
$key = file_get_contents($cerpath);
//公钥加密
openssl_public_encrypt($data, $crypted, $key);
echo base64_encode($crypted)."<br>";
//echo $crypted."<br>";

//私钥解密
$decrypted = "";
$s = file_get_contents($pfxpath);
//echo "<br>$s<br>";
$key2 = openssl_pkey_get_private(file_get_contents($pfxpath));

if(openssl_private_decrypt($crypted, $decrypted, $key2)){
echo $decrypted;
}else{
echo "failed";
}

6.其它问题

银行给的公钥是一串字符串

银行给的公钥类似于MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCSEPnYEgIBCgCe1n9n7M7WEMJgwcGaFcJgQ3Jv0s3cQIfH9zqtw+bYISJo93ygE6y3aDGSxE51Z6CFSvk9NeCPICAlaE5hzbMm96aeK+N1tjQHGi/mEweFkVbgn8nvUFTrQakVSvXti7cI79IXmuoqgQ+PsNxm7vM1j3I0UbfeQQIDAQAB这样,要怎么使用?

PHP里公钥需要满足以下条件:

  • 公钥中必须有 -----BEGIN PUBLIC KEY----------END PUBLIC KEY-----
  • 公钥必须换行,一般每行64个长度。

转换方法:

1
2
3
4
5
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($str, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
// 返回资源格式的公钥
$pubKey = openssl_pkey_get_public($res);

创建字符串格式的密钥对

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
public function createKeys($key_size = 2048)
{
$config = array(
"private_key_bits" => $key_size,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
$res = openssl_pkey_new($config);
// 将$res当作PEM编码字符串导出并且将之保存到$private_key
openssl_pkey_export($res, $private_key);
$public_key_detail = openssl_pkey_get_details($res);
$public_key = $public_key_detail["key"];

return [
"public_key" => $public_key,
"private_key" => $private_key,
];
}

生成密钥对格式如下:
/*
// 私钥
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCSEPnYEgIBCgCe1n9n7M7WEMJgwcGaFcJgQ3Jv0s3cQIfH9zqt
w+bYISJo93ygE6y3aDGSxE51Z6CFSvk9NeCPICAlaE5hzbMm96aeK+N1tjQHGi/m
EweFkVbgn8nvUFTrQakVSvXti7cI79IXmuoqgQ+PsNxm7vM1j3I0UbfeQQIDAQAB
AoGBAIMv5gWvHseqISaHxgQkBxzyDnJycxyeAGcPDL/sWGJCPvFVVgltEm75S3t1
FCR9pzlOxn9n+oOt39j3nmTgd98ZA6F03vrVQY8yFCgMG8DL5TMLcT4oALNszCwb
tbTMaLz5kdxjOiXIRf75zB7v88B/WbyqLjsuonT2U935WO1FAkEAwZkLSAcLSOkx
Ib2X/A6K0OgPDYNqRacZI2UQKMojCKYcrPC7j6YSDctiAQjvR8YKvmWMuyL0HP/p
jLCfHqYxIwJBAMElz+VGbiWkuX3ZkQNaXqbnGPdOBAcaxwjozZll6C8lgSQ9tgxN
DDkhNRjP3F+1FzNflUgGfB6OD8iYPNtks0sCQBDR+Tby1bop+IjT7iaKJOltoBEu
f8c5c2et2i+REGYp+IfWCpW6egsUxpkMa62yuTA9pnKD4O28FlCQ7+I70j0CQQCa
ndlZ8dKVAdOXokYKyBoDQ/ZhaqugwFfakvqbNM/0hfJmf5wvJrKFPUcTELqaRw5G
itqm6MINd62859m3y1oXAkBOfiYtvFufMsvdKsEKi8Oc9ZZA4x4fJ5FUIPfaTaHc
Yi0VaOGbdg6O9dQMUG9FHcPsudYx8FwspcNlquODDMdD
-----END RSA PRIVATE KEY-----


// 公钥
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCSEPnYEgIBCgCe1n9n7M7WEMJg
wcGaFcJgQ3Jv0s3cQIfH9zqtw+bYISJo93ygE6y3aDGSxE51Z6CFSvk9NeCPICAl
aE5hzbMm96aeK+N1tjQHGi/mEweFkVbgn8nvUFTrQakVSvXti7cI79IXmuoqgQ+P
sNxm7vM1j3I0UbfeQQIDAQAB
-----END PUBLIC KEY-----
*/

加密数据长度要求

openssl_public_encrypt()加密失败,返回False。
以私钥加密为例,加密强度1024的私钥,每次可加密数据长度为 1024位/8=128字节,当使用OPENSSL_PKCS1_PADDING(这个要占用11个字节)填充时,可加密1024/8-11=117字节。如果超出,那么这些openssl加解密函数会返回false。所以当超出时,请分段加密!同理:对于2048长度的密钥,如果要加密的数据长度大于245的话,加密失败,只能进行分段加密。

分段加密方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function encryptBigData($data)
{
$pubKey = 'XXXXXXXXXXXXXXX....';
$crypto = '';
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
$openssl_pub = openssl_pkey_get_public($res);
foreach (str_split($data, 117) as $chunk) {
//一般的加密填充算法默认是OPENSSL_PKCS1_PADDING,可不填。
openssl_public_encrypt($chunk, $encryptData, $openssl_pub, OPENSSL_PKCS1_PADDING);
$crypto .= $encryptData;
}
return base64_encode($crypto);
}

注:这里使用117字节分段,当然对方也可能是64字节进行分段加密,需要和对方核实并保持一致。

注意加解密前进行编码和解码

一般来说加密后的数据是二进制数据,为了便于传输和可见性,通常会将加密后的数据进行base64编码。同样地,解密前需将密文进行base64解码。

注:需和对方核实,此处有可能使用2进制、16进制编码、解码,如bin2hex、hex2bin

7.实用的RSA类

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php
class OpensslRSA
{
public $private_key = '-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC+L7ENzBHxKOqjuFHPFKlAt40BatVZhUAHw/G05XshpTGqm9Rv
8wG0EAbFbdo9PuB8DiXdPQLyIfaqkTgpsPjJ1Ow7WKxmYbqZN5IW/GN+TyFWP+MB
2W6IBLPWBDvAl2NJlmU8j3LCPJW1dH4zP1OitkxZMyUuUYYfPuOtO9RJ2wIDAQAB
AoGAUFCbmJQBT7JTxGfmRGkZQLdC2MJg7rkS3TSmMhpm8UJtwvqjr9MTeRL7iQxn
CU4wRrNC0jcds1sca9N/wDt4FCkCala+bg7mwQuPpg5QhXelfFr88ibRnP8y8LmZ
7PPNqx9c4jivhMzJrzNh3luqg6awjsig2w3+EW1/Ubb30AECQQDshTvyc5mpDgiO
4g8q1ztszszL9eCp+IjlUaN51vC3Nj1eXpjbtdSZ0JVKrDdhKcd3rEZVYzMQN/lI
pyq85e/bAkEAzdmN6TF3Y1h3LouumCy6+61ChTFrl/yjw13CGApmAQHhEVyANHr7
NjoxP06eimzn7KHff/eYxd1Emf1SYA8uAQJBAN1ibFUpLRgXAZ20LNw9r+rNutXi
ZJLUBlcXTjv6G0ByLYkKZGuqy7/ZhBPsFL4GnCUBBKhh/ObebaA6kH9VfmcCQGfg
0WxMOiM4EWy7sG+6ouE+ncL5HYKlSz7boYbgOHlpqVpJg6j4Jq1G0HNSCU9xhdg0
F8VL/RxcfLH41AkFoAECQQCR8NDB3BgHqyJfarKKMWQ3qrXHaLfBKExMrpQ8MDzs
MlSBzFOnucufo110lSgjdRlgr8smtU2hx9gXFIqxvfWF
-----END RSA PRIVATE KEY-----';
//公钥
public $public_key = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L7ENzBHxKOqjuFHPFKlAt40B
atVZhUAHw/G05XshpTGqm9Rv8wG0EAbFbdo9PuB8DiXdPQLyIfaqkTgpsPjJ1Ow7
WKxmYbqZN5IW/GN+TyFWP+MB2W6IBLPWBDvAl2NJlmU8j3LCPJW1dH4zP1OitkxZ
MyUuUYYfPuOtO9RJ2wIDAQAB
-----END PUBLIC KEY-----';
public $pi_key;
public $pu_key;

//判断公钥和私钥是否可用
public function __construct()
{
$this->pi_key = openssl_pkey_get_private($this->private_key);//这个函数可用来判断私钥是否是可用的,可用返回资源id Resource id
$this->pu_key = openssl_pkey_get_public($this->public_key);//这个函数可用来判断公钥是否是可用的
// print_r($this->pi_key);echo "\n";
// print_r($this->pu_key);echo "\n";
}

//私钥加密
/*public function PrivateEncrypt($data){
openssl_private_encrypt($data,$encrypted,$this->pi_key);
$encrypted = $this->urlsafe_b64encode($encrypted);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
return $encrypted;
}*/

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

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

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

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

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

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

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
include 'OpensslRSA.php';
$rsa = new OpensslRSA();
$rst = array(
'ret' => 200,
'code' => 1,
'data' => [
'info' => '11111111111111111111111111111111111111111111111111111111111111regporkje5kmjkm]=67o,l6u-l,7,l78-.l79l.9l7.=8l'
],
'msg' => "success",
);
$ex = json_encode($rst);
//加密
$ret_e = $rsa->PublicEncrypt($ex);
//解密
$ret_d = $rsa->PrivateDecrypt($ret_e);
echo $ret_e;
echo '<pre>';
echo $ret_d;

echo '<pre>';