PHP 在进入7.x 时代后,默认就不再附带 mcrypt 扩展,mcrypt 将被 openssl_* 一族函数所替代。所以,对于 PHPer 来说,有必要学习一下 PHP 的 OpenSSL 扩展。
上一篇文章《PHP中OpenSSL扩展 - 对称加密》 ,介绍了 OpenSSL 扩展中对称加密的使用方法,本文将介绍非对称加密的使用方法。
PHP 的 OpenSSL 扩展中,非对称加密的相关函数有:
- openssl_pkey_new
- openssl_pkey_export
- openssl_pkey_export_to_file
- openssl_pkey_get_details
- openssl_pkey_free
- openssl_pkey_get_private
- openssl_pkey_get_public
- openssl_get_privatekey
- openssl_get_publickey
- openssl_private_decrypt
- openssl_private_encrypt
- openssl_public_decrypt
- openssl_public_encrypt
别被这么多函数吓倒,经过本文的讲解,你会发现非对称加密的过程并不繁琐。让我们通过实例来讲解每个函数的作用。
1. 生成密钥对
首先,想要进行非对称加密 / 解密,你得有一对公钥(Public key)和私钥(Private key)。在Linux环境下,公钥私钥可以用 openssl
命令生成。PHP的 OpenSSL 扩展中,openssl_pkey_new()
函数可以完成同样的事:
<?php
// 生成私钥
$privateKey = openssl_pkey_new();
openssl_pkey_export($privateKey, $out);
echo $out;
上面两行代码生成了一个私钥,并导出到了 $out
变量中。
延伸一下,如果你打印
$out
变量,会看见一个由大小写字母和数字组成的“乱码块”,外层被-----BEGIN PRIVATE KEY-----
和-----END PRIVATE KEY-----
包裹着。这其实是 PEM 格式的私钥,乱码块是被 Base64 编码的二进制数据。
注意到现在只生成了一个私钥,那么公钥在哪呢?OpenSSL扩展并没有生成公钥的函数,公钥是从私钥当中提取出来的,使用 openssl_pkey_get_details()
函数:
从私钥提取公钥
<?php
$privateKey = openssl_pkey_new();
$detail = openssl_pkey_get_details($privateKey);
$publicKeyString = $detail['key'];
echo $publicKeyString;
openssl_pkey_get_details()
接受一个私钥对象,返回一个 array 包含私钥中附带的相关信息,比如 RSA 的 n、e1、e2 值。。。不用深究这几个值,他们已经是密码学原理才能解释的东西了。我们只关心分析结果的 key 值,key 值就是提取出来的公钥啦。
我们将公钥私钥分别保存到磁盘上:
<?php
// 如果密钥已经是PEM格式的了,那就直接写到磁盘上
file_put_contents('public.key', $publicKeyString);
// 否则需要用 openssl_pkey_export()
// 或者openssl_pkey_export_to_file()
// 转换成PEM格式
openssl_pkey_export_to_file($privateKey, 'private.key');
有了一对公钥、私钥之后,就可以进行非对称加密了。注意 公钥 可以分发给别人用的,而 私钥 只能你自己知道,否则非对称加密系统就完全失效了。
2. 非对称 加密 与 解密
在不管是加密还是解密,都要先读取密钥。上一节我们保存在磁盘上的密钥是PEM格式的,不能直接用,需要用 openssl_pkey_get_public()
或 openssl_pkey_get_private()
读取:
<?php
$privateKey = openssl_pkey_get_private(file_get_contents('private.key'));
$publicKey = openssl_pkey_get_public(file_get_contents('public.key'));
另外还有两个函数:openssl_get_privatekey()
和 openssl_get_publickey()
,只是上述两个函数的别名,完成的功能相同。
介绍了一大堆,终于到了真正的加密解密了。因为是非对称加密,所以公钥和私钥是交错使用的:公钥加密的数据用密钥解密,同样,私钥加密的数据用公钥解密。
公钥加密私钥解密:
// 加密
$publicKey = openssl_pkey_get_public(file_get_contents('public.key'));
openssl_public_encrypt('PHP是世界上最好的语言!', $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
echo $encrypted . PHP_EOL;
// 解密
$privateKey = openssl_pkey_get_private(file_get_contents('private.key'));
openssl_private_decrypt($encrypted, $decrypted, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
echo $decrypted . PHP_EOL;
结果:
qz¬icG߇!?ꕦNׇ.¿0QM]_+Bi=輬©𥆈>¹N`Z㹔̡ד䴹¨Z9qr죡¾<zɥH«dyя³TG¾q»HE𥊮SAxd綧h` 6䡝פ£nQ¹ۉq
PHP是世界上最好的语言!
可以看到经过加密后,明文已经变成完全无法阅读的乱码了。经过解密后又变回了原文。公钥加密私钥解密满足了最常见的数据保密的需求,别人用你的公钥加密的数据,只能用你自己的私钥解开。
私钥加密公钥解密
// 加密
$privateKey = openssl_pkey_get_private(file_get_contents('private.key'));
openssl_private_encrypt('PHP是世界上最好的语言!', $encrypted, $privateKey);
echo $encrypted . PHP_EOL;
// 解密
$publicKey = openssl_pkey_get_public(file_get_contents('public.key'));
openssl_public_decrypt($encrypted, $decrypted, $publicKey);
echo $decrypted . PHP_EOL;
输出的结果与公钥加密类似,就不再赘述了。私钥加密公钥解密一般用于签名,因为用你的私钥加密的内容,大家只能用你的公钥解开,从而保证了加密的信息确实是由你发出的。