对称加密: 在加密和解密过程中使用相同的密钥, 或是两个可以简单地相互推算的密钥的加密算法.
非对称加密: 也称为公开加密, 它需要一个密钥对, 一个是公钥, 一个是私钥, 一个负责加密, 一个负责解密.
对称加密在性能上要优于非对称加密, 但是安全性低于非对称加密.
PHP 7.1 之后的对称加密和非对称加密都需要借助 openssl 扩展实现. mcrypt 库已经被移除.
对称加密函数
openssl_get_cipher_methods() : 返回 openssl 支持的所有加密方式.
openssl_encrypt($data, $method, $key, $options = 0, $iv = '') : 以指定方式 method 和密钥 key 加密 data, 返回 false 或加密后的数据.
data : 明文
method : 加密算法
key : 密钥
options :
0 : 自动对明文进行 padding, 返回的数据经过 base64 编码.
1 : OPENSSL_RAW_DATA, 自动对明文进行 padding, 但返回的结果未经过 base64 编码.
2 : OPENSSL_ZERO_PADDING, 自动对明文进行 0 填充, 返回的结果经过 base64 编码. 但是, openssl 不推荐 0 填充的方式, 即使选择此项也不会自动进行 padding, 仍需手动 padding.
iv : 非空的初始化向量, 不使用此项会抛出一个警告. 如果未进行手动填充, 则返回加密失败.
openssl_decrypt($data, $method, $key, $options = 0, $iv = '') : 解密数据.
openssl_cipher_iv_length($method) : 获取 method 要求的初始化向量的长度.
openssl_random_pseudo_bytes($length) : 生成指定长度的伪随机字符串.
hash_mac($method, $data, $key, $raw_out) : 生成带有密钥的哈希值.
method : 加密算法
data : 明文
key : 密钥
raw_output :
TRUE : 输出原始二进制数据
FALSE : 输出长度固定的小写 16 进制字符串
对称加密
参考文章:
1. https://blog.csdn.net/qq_28205153/article/details/55798628
2. https://www.xxling.com/blog/article/3114.aspx
主流的对称加密方式有 DES, AES. 这两种加密方式都属于分组加密, 先将明文分成多个等长的模块 ( block ), 然后进行加密。
主流看法都认为 AES 相比 DES 加密更具安全性,因此建议使用 AES 加密。
DES 加密
DES 加密的密钥长度为 64 bit, 实际应用中有效使用的是 56 位, 剩余 8 位作为奇偶校验位. 如果密钥长度不足 8 个字节, 将会使用 \0 补充到 8 个字节. 如密钥为 "12345", 其加密后的密文与密钥 "12345\0\0\0" 加密后的密文相同. 明文按 64 bit ( UTF-8 下为 8 个字节长度 ) 进行分组, 每 64 位分成一组 ( 最后一组不足 64 位的需要填充数据 ), 分组后的明文组和密钥按位替代或交换的方法形成密文组.
<?php /** * Class DES * 可逆的加密解密的类 */ class DES { private $method = 'DES-CBC'; private $key = PROJECT . 'www.phpernote.com'; /** * DES constructor. * @param string $key */ public function __construct($key = '') { // 密钥长度不能超过64bit(UTF-8下为8个字符长度),超过64bit不会影响程序运行,但有效使用的部分只有64bit,多余部分无效,可通过openssl_error_string() 查看错误提示 $key && $this->key = $key; } /** * 加密 * @param string $str 需要加密的字符串 * @return string */ public function encrypt($str) { // 生成加密所需的初始化向量, 加密时缺失iv会抛出一个警告 $ivlen = openssl_cipher_iv_length($this->method); $iv = openssl_random_pseudo_bytes($ivlen); // 加密数据. 如果options参数为0, 则不再需要上述的填充操作. 如果options参数为1, 也不需要上述的填充操作, 但是返回的密文未经过base64编码. 如果options参数为2, 虽然PHP说明是自动0填充, 但实际未进行填充, 必须需要上述的填充操作进行手动填充. 上述手动填充的结果和options为0和1是自动填充的结果相同. $ciphertext = openssl_encrypt($str, $this->method, $this->key, 1, $iv); // 生成hash $hash = hash_hmac('sha256', $ciphertext, $this->key, false); return base64_encode($iv . $hash . $ciphertext); } /** * 解密 * @param string $str 需要解密的字符串 * @return bool|false|string */ public function decrypt($str) { $str = base64_decode($str); // 从密文中获取iv $ivlen = openssl_cipher_iv_length($this->method); $iv = substr($str, 0, $ivlen); // 从密文中获取hash $hash = (string)substr($str, $ivlen, 64); // 获取原始密文 $str = substr($str, $ivlen + 64); // hash校验 if (hash_equals($hash, hash_hmac('sha256', $str, $this->key, false))) { // 解密数据 return openssl_decrypt($str, $this->method, $this->key, 1, $iv) ?? false; } //return '解密失败'; return false; } } $aes = new DES('yhm'); $string = $aes->encrypt('www.phpernote.com'); echo '明文:www.phpernote.com', '<br />'; echo '密文:', $string, '<br />'; //密文:9I5V7XirG0ZiMWQ0MmJlZWY0MWI5ZjNkNzE3YmIwZDRiYTlkOWI3NTc2ZjIyMzc5MmEzOTkxYzIyNzY1MzU3NjljMWFlZjIztd1jrvlOS6tDUaMphQBqusOVjBnhgUdV //注意这里的密文每次输出都不一样 echo '解密:', $aes->decrypt($string); //解密:www.phpernote.com
AES 加密
AES 加密的分组长度是 128 位, 即每个分组为 16 个字节 ( 每个字节 8 位 ). 密钥的长度根据加密方式的不同可以是 128 位, 192 位, 256 位. 与 DES 加密一样. 密钥长度超过指定长度时, 超出部分无效. 密钥长度不足时, 会自动以`\0`补充到指定长度.
AES 密钥长度 ( 位 ) 分组长度 ( 位 )
AES-128 128 128
AES-192 192 128
AES-256 256 128
<?php /** * Class AES * 可逆的加密解密的类 */ class AES { private $key = PROJECT . 'www.phpernote.com'; private $method = 'aes-128-cbc'; /** * AES constructor. * @param string $key */ public function __construct($key = '') { extension_loaded('openssl') or die('未启用 OPENSSL 扩展'); $key && $this->key = $key; } /** * 加密 * @param string $str * @return string */ public function encrypt($str) { if (!in_array($this->method, openssl_get_cipher_methods())) { die('不支持该加密算法!'); } // 获取加密算法要求的初始化向量的长度 $ivlen = openssl_cipher_iv_length($this->method); // 生成对应长度的初始化向量. aes-128模式下iv长度是16个字节, 也可以自由指定. $iv = openssl_random_pseudo_bytes($ivlen); // 加密数据 $ciphertext = openssl_encrypt($str, $this->method, $this->key, 1, $iv); $hmac = hash_hmac('sha256', $ciphertext, $this->key, false); return base64_encode($iv . $hmac . $ciphertext); } /** * 解密 * @param string $str * @return bool|false|string */ public function decrypt($str) { $str = base64_decode($str); $ivlen = openssl_cipher_iv_length($this->method); $iv = substr($str, 0, $ivlen); $hmac = substr($str, $ivlen, 64); $str = substr($str, $ivlen + 64); $verifyHmac = hash_hmac('sha256', $str, $this->key, false); if (hash_equals($hmac, $verifyHmac)) { return openssl_decrypt($str, $this->method, $this->key, 1, $iv) ?? false; } //die('数据被修改!'); return false; } } $aes = new AES('yhm'); $string = $aes->encrypt('www.phpernote.com'); echo '明文:www.phpernote.com', '<br />'; echo '密文:', $string, '<br />'; //密文:XJmLnBp3L8tOUYswd7+s3jEzNDZlMjMxNTkwMjNiMDI0MmU2ZWMzNTE4MDk5ZTZkYzc4ODlhMWQ1NWM1YjUzNDNlNjcxMjk1MzI0M2M1ODe5M9dAO4mNXj1rHsypG+u9G8OQBMMJMg8fkmkflvC43g== //注意这里的密文每次输出都不一样 echo '解密:', $aes->decrypt($string); //解密:www.phpernote.com
非对称加密函数
$res = openssl_pkey_new([array $config]) : 生成一个新的私钥和公钥对. 通过配置数组, 可以微调密钥的生成.
digest_alg : 摘要或签名哈希算法.
private_key_bits : 指定生成的私钥的长度.
private_key_type : 指定生成私钥的算法. 默认 OPENSSL_KEYTYPE_RSA, 可指定 OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC.
config : 自定义 openssl.conf 文件的路径.
openssl_pkey_free($res) : 释放有 openssl_pkey_new() 创建的私钥.
openssl_get_md_methods() : 获取可用的摘要算法.
openssl_pkey_export_to_file($res, $outfilename) : 将 ASCII 格式 ( PEM 编码 ) 的密钥导出到文件中. 使用相对路径时, 是相对服务器目录, 而非当前所在目录.
openssl_pkey_export($res, &$out) : 提取 PEM 格式私钥字符串.
openssl_pkey_get_details($res) : 返回包含密钥详情的数组.
openssl_get_privatekey($key) : 获取私钥. key 是一个 PEM 格式的文件或一个 PEM 格式的私钥.
openssl_get_publickey($certificate) : 获取公钥. certificate 是一个 X.509 证书资源或一个 PEM 格式的文件或一个 PEM 格式的公钥.
openssl_private_encrypt($data, &$crypted, $privKey [, $padding = OPENSSL_PKCS1_PADDING]) : 使用私钥加密数据, 并保存到 crypted . 其中填充模式为 OPENSSL_PKCS1_PADDING 时, 如果明文长度不够, 加密时会在明文中随机填充数据. 为 OPENSSL_NO_PADDING 时, 如果明文长度不够, 会在明文的头部填充 0 .
openssl_public_decrypt($crypted, &$decrypted, $pubKey [, $padding]) : 使用公钥解密数据, 并保存到 decrypted .
openssl_public_encrypt($data, &$crypted, $pubKey [, $padding]) : 使用公钥加密数据, 并保存到 crypted .
openssl_private_decrypt($crypted, &$decrypted, $privKey [, $padding]) : 使用私钥解密数据, 并保存到 decrypted .
非对称加密
RSA 也是一种分组加密方式, 但明文的分组长度根据选择的填充方式的不同而不同.
<?php class RSA { private $private_key; // 私钥 private $public_key; // 公钥 private $private_res; // 私钥资源 private $public_res; // 公钥资源 public function __construct() { extension_loaded('openssl') or die('未加载 openssl'); // 生成新的公钥和私钥对资源 $config = [ 'digest_alg' => 'sha256', 'private_key_bits' => 1204, 'private_key_type' => OPENSSL_KEYTYPE_RSA ]; $res = openssl_pkey_new($config); if (!$res) { die('生成密钥对失败'); } // 获取公钥, 生成公钥资源 $this->public_key = openssl_pkey_get_details($res)['key']; $this->public_res = openssl_pkey_get_public($this->public_key); // 获取私钥, 生成私钥资源 openssl_pkey_export($res, $this->private_key); $this->private_res = openssl_pkey_get_private($this->private_key); openssl_free_key($res); } // 加密 public function encrypt($plaintext) { $ciphertext = null; openssl_public_encrypt($plaintext, $ciphertext, $this->public_res); return $ciphertext; } // 解密 public function decrypt($ciphertext) { $plaintext = null; openssl_private_decrypt($ciphertext, $plaintext, $this->private_res); return $plaintext; } } $aes = new RSA('yhm'); $string = $aes->encrypt('www.phpernote.com'); echo '明文:www.phpernote.com', '<br />'; echo '密文:', $string, '<br />'; //密文:密文:O�rZ�v��5 �j��]��9�S��p��(};�ۢf���UejL]��d,� ��v�{ �4'Nq��zr!�iQ��I鷺�T�7 '�����8�$��� �u��������AT����K�U|��� V����܄I���0°���) //注意这里的密文每次输出都不一样,而且还都是乱码,O(∩_∩)O echo '解密:', $aes->decrypt($string); //解密:www.phpernote.com
在传输重要信息时, 一般会采用对称加密和非对称加密相结合的方式, 而非使用单一加密方式. 一般先通过 AES 加密数据, 然后通过 RSA 加密 AES 密钥, 然后将加密后的密钥和数据一起发送. 接收方接收到数据后, 先解密 AES 密钥, 然后使用解密后的密钥解密数据。