openssl 下的對稱加密和非對稱加密



對稱加密: 在加密和解密過程中使用相同的密鑰, 或是兩個可以簡單地相互推算的密鑰的加密算法.

非對稱加密: 也稱為公開加密, 它需要一個密鑰對, 一個是公鑰, 一個是私鑰, 一個負責加密, 一個負責解密.

對稱加密在性能上要優於非對稱加密, 但是安全性低於非對稱加密.

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 ), 然后進行加密.

DES 加密

DES 加密的密鑰長度為 64 bit, 實際應用中有效使用的是 56 位, 剩余 8 位作為奇偶校驗位. 如果密鑰長度不足 8 個字節, 將會使用 \0 補充到 8 個字節. 如密鑰為 "12345", 其加密后的密文與密鑰 "12345\0\0\0" 加密后的密文相同. 明文按 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         // 加密數據. 如果options參數為0, 則不再需要上述的填充操作. 如果options參數為1, 也不需要上述的填充操作, 但是返回的密文未經過base64編碼. 如果options參數為2, 雖然PHP說明是自動0填充, 但實際未進行填充, 必須需要上述的填充操作進行手動填充. 上述手動填充的結果和options為0和1是自動填充的結果相同.
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             $plaintext = openssl_decrypt($ciphertext, $this->method, $this->key, 1, $iv) ?? false;
44             // 去除填充數據. 加密時進行了填充才需要去填充
45             $plaintext = $plaintext? $this->unpadding($plaintext) : 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 位. 與 DES 加密一樣. 密鑰長度超過指定長度時, 超出部分無效. 密鑰長度不足時, 會自動以`\0`補充到指定長度.

AES 密鑰長度 ( 位 ) 分組長度 ( 位 )
AES-128 128 128
AES-192 192 128
AES-256 256

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         }
       // options為1, 不需要手動填充
19 //$plaintext = $this->padding($plaintext); 20 // 獲取加密算法要求的初始化向量的長度 21 $ivlen = openssl_cipher_iv_length($this->method); 22 // 生成對應長度的初始化向量. aes-128模式下iv長度是16個字節, 也可以自由指定. 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 密鑰, 然后使用解密后的密鑰解密數據.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM