openssl 下的对称加密和非对称加密

本文介绍PHP中对称加密(如DES、AES)与非对称加密(如RSA)的基本原理及实现方法,包括加密算法的选择、密钥管理、加密解密过程等,并探讨了两种加密方式结合使用的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


对称加密: 在加密和解密过程中使用相同的密钥, 或是两个可以简单地相互推算的密钥的加密算法.

非对称加密: 也称为公开加密, 它需要一个密钥对, 一个是公钥, 一个是私钥, 一个负责加密, 一个负责解密.

对称加密在性能上要优于非对称加密, 但是安全性低于非对称加密.

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.youkuaiyun.com/qq_28205153/article/details/55798628
2. https://www.xxling.com/blog/article/3114.aspx

主流的对称加密方式有 DES, AES. 这两种加密方式都属于分组加密, 先将明文分成多个等长的模块 ( block ), 然后进行加密.

DES 加密

DES 加密的密钥长度为 64 bit, 实际应用中有效使用的是 56 位, 剩余 8 位作为奇偶校验位. 明文按 64 bit ( UTF-8 下为 8 个字节长度 ) 进行分组, 每 64 位分成一组 ( 最后一组不足 64 位的需要填充数据 ), 分组后的明文组和密钥按位替代或交换的方法形成密文组.

 1 class DES
 2 {
 3     private $method = 'DES-CBC';
 4     private $key;
 5 
 6     public function __construct($key)
 7     {
 8         // 密钥长度不能超过64bit(UTF-8下为8个字符长度),超过64bit不会影响程序运行,但有效使用的部分只有64bit,多余部分无效,可通过openssl_error_string()查看错误提示
 9         $this->key = $key;
10     }
11 
12     public function encrypt($plaintext)
13     {
14         // 生成加密所需的初始化向量, 加密时缺失iv会抛出一个警告
15         $ivlen = openssl_cipher_iv_length($this->method);
16         $iv = openssl_random_pseudo_bytes($ivlen);
17 
18         // 按64bit一组填充明文
19         $plaintext = $this->padding($plaintext);
20         // 加密数据
21         $ciphertext = openssl_encrypt($plaintext, $this->method, $this->key, 1, $iv);
22         // 生成hash
23         $hash = hash_hmac('sha256', $ciphertext, $this->key, false);
24 
25         return base64_encode($iv . $hash . $ciphertext);
26 
27     }
28 
29     public function decrypt($ciphertext)
30     {
31         $ciphertext = base64_decode($ciphertext);
32         // 从密文中获取iv
33         $ivlen = openssl_cipher_iv_length($this->method);
34         $iv = substr($ciphertext, 0, $ivlen);
35         // 从密文中获取hash
36         $hash = substr($ciphertext, $ivlen, 64);
37         // 获取原始密文
38         $ciphertext = substr($ciphertext, $ivlen + 64);
39         // hash校验
40         if(hash_equals($hash, hash_hmac('sha256', $ciphertext, $this->key, false)))
41         {
42             // 解密数据
43             $ciphertext = openssl_decrypt($ciphertext, $this->method, '12345678', 1, $iv) ?? false;
44             // 去除填充数据
45             $plaintext = $ciphertext ? $this->unpadding($ciphertext) : false;
46 
47             return $plaintext;
48         }
49 
50         return '解密失败';
51     }
52 
53     // 按64bit一组填充数据
54     private function padding($plaintext)
55     {
56         $padding = 8 - (strlen($plaintext)%8);
57         $chr = chr($padding);
58 
59         return $plaintext . str_repeat($chr, $padding);
60     }
61 
62     private function unpadding($ciphertext)
63     {
64         $chr = substr($ciphertext, -1);
65         $padding = ord($chr);
66 
67         if($padding > strlen($ciphertext))
68         {
69             return false;
70         }
71         if(strspn($ciphertext, $chr, -1 * $padding, $padding) !== $padding)
72         {
73             return false;
74         }
75 
76         return substr($ciphertext, 0, -1 * $padding);
77     }
78 }

 

AES 加密

AES 加密的分组长度是 128 位, 即每个分组为 16 个字节 ( 每个字节 8 位 ). 密钥的长度根据加密方式的不同可以是 128 位, 192 位, 256 位. 密钥长度超过指定长度时, 超出部分无效.

AES密钥长度 ( 位 )分组长度 ( 位 )
AES-128128128
AES-192192128
AES-256256

128

 

 

 

 

 


 

 1 class AES
 2 {
 3     private $key;
 4     private $method = 'aes-128-cbc';
 5 
 6     public function __construct($key)
 7     {
 8         // 是否启用了openssl扩展
 9         extension_loaded('openssl') or die('未启用 OPENSSL 扩展');
10         $this->key = $key;
11     }
12 
13     public function encrypt($plaintext)
14     {
15         if(!in_array($this->method, openssl_get_cipher_methods()))
16         {
17             die('不支持该加密算法!');
18         }
19         $plaintext = $this->padding($plaintext);
20         // 获取加密算法要求的初始化向量的长度
21         $ivlen = openssl_cipher_iv_length($this->method);
22         // 生成对应长度的初始化向量
23         $iv = openssl_random_pseudo_bytes($ivlen);
24         // 加密数据
25         $ciphertext = openssl_encrypt($plaintext, $this->method, $this->key, 1, $iv);
26         $hmac = hash_hmac('sha256', $ciphertext, $this->key, false);
27 
28         return base64_encode($iv . $hmac . $ciphertext);
29     }
30 
31     public function decrypt($ciphertext)
32     {
33         $ciphertext = base64_decode($ciphertext);
34         $ivlen = openssl_cipher_iv_length($this->method);
35         $iv = substr($ciphertext, 0, $ivlen);
36         $hmac = substr($ciphertext, $ivlen, 64);
37         $ciphertext = substr($ciphertext, $ivlen + 64);
38         $verifyHmac = hash_hmac('sha256', $ciphertext, $this->key, false);
39         if(hash_equals($hmac, $verifyHmac))
40         {
41             $plaintext = openssl_decrypt($ciphertext, $this->method, $this->key, 1, $iv)??false;
42             if($plaintext)
43             {
44                 $plaintext = $this->unpadding($plaintext);
45                 echo $plaintext;
46             }
47 
48             return $plaintext;
49         }else
50         {
51             die('数据被修改!');
52         }
53     }
54 
55     private function padding(string $data) : string
56     {
57         $padding = 16 - (strlen($data) % 16);
58         $chr = chr($padding);
59         return $data . str_repeat($chr, $padding);
60     }
61 
62     private function unpadding($ciphertext)
63     {
64         $chr = substr($ciphertext, -1);
65         $padding = ord($chr);
66 
67         if($padding > strlen($ciphertext))
68         {
69             return false;
70         }
71 
72         if(strspn($ciphertext, $chr, -1 * $padding, $padding) !== $padding)
73         {
74             return false;
75         }
76 
77         return substr($ciphertext, 0, -1 * $padding);
78     }
79 }

 

非对称加密函数

$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 也是一种分组加密方式, 但明文的分组长度根据选择的填充方式的不同而不同.

 1 class RSA
 2 {
 3     private $private_key; // 私钥
 4     private $public_key; // 公钥
 5     private $private_res; // 私钥资源
 6     private $public_res; // 公钥资源
 7 
 8     public function __construct()
 9     {
10         extension_loaded('openssl') or die('未加载 openssl');
11         // 生成新的公钥和私钥对资源
12         $config = [
13             'digest_alg' => 'sha256',
14             'private_key_bits' => 1204,
15             'private_key_type' => OPENSSL_KEYTYPE_RSA
16         ];
17         $res = openssl_pkey_new($config);
18         if(!$res)
19         {
20             die('生成密钥对失败');
21         }
22 
23         // 获取公钥, 生成公钥资源
24         $this->public_key = openssl_pkey_get_details($res)['key'];
25         $this->public_res = openssl_pkey_get_public($this->public_key);
26 
27         // 获取私钥, 生成私钥资源
28         openssl_pkey_export($res, $this->private_key);
29         $this->private_res = openssl_pkey_get_private($this->private_key);
30 
31         openssl_free_key($res);
32     }
33 
34     // 加密
35     public function encrypt($plaintext)
36     {
37         $ciphertext = null;
38         openssl_public_encrypt($plaintext, $ciphertext, $this->public_res);
39         return $ciphertext;
40     }
41 
42     // 解密
43     public function decrypt($ciphertext)
44     {
45         $plaintext = null;
46         openssl_private_decrypt($ciphertext, $plaintext, $this->private_res);
47         return $plaintext;
48     }
49 }

在传输重要信息时, 一般会采用对称加密和非对称加密相结合的方式, 而非使用单一加密方式. 一般先通过 AES 加密数据, 然后通过 RSA 加密 AES 密钥, 然后将加密后的密钥和数据一起发送. 接收方接收到数据后, 先解密 AES 密钥, 然后使用解密后的密钥解密数据.

转载于:https://www.cnblogs.com/fxyy/p/8868351.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值