PHP 在進(jìn)入7.x 時(shí)代后,默認(rèn)就不再附帶 mcrypt 擴(kuò)展,mcrypt 將被 openssl_* 一族函數(shù)所替代。所以,對(duì)于 PHPer 來說,有必要學(xué)習(xí)一下 PHP 的 OpenSSL 擴(kuò)展。
上一篇文章《PHP中OpenSSL擴(kuò)展 - 對(duì)稱加密》 ,介紹了 OpenSSL 擴(kuò)展中對(duì)稱加密的使用方法,本文將介紹非對(duì)稱加密的使用方法。
PHP 的 OpenSSL 擴(kuò)展中,非對(duì)稱加密的相關(guān)函數(shù)有:
- 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
別被這么多函數(shù)嚇倒,經(jīng)過本文的講解,你會(huì)發(fā)現(xiàn)非對(duì)稱加密的過程并不繁瑣。讓我們通過實(shí)例來講解每個(gè)函數(shù)的作用。
1. 生成密鑰對(duì)
首先,想要進(jìn)行非對(duì)稱加密 / 解密,你得有一對(duì)公鑰(Public key)和私鑰(Private key)。在Linux環(huán)境下,公鑰私鑰可以用 openssl 命令生成。PHP的 OpenSSL 擴(kuò)展中,openssl_pkey_new() 函數(shù)可以完成同樣的事:
<?php
// 生成私鑰
$privateKey = openssl_pkey_new();
openssl_pkey_export($privateKey, $out);
echo $out;
上面兩行代碼生成了一個(gè)私鑰,并導(dǎo)出到了 $out 變量中。
延伸一下,如果你打印
$out變量,會(huì)看見一個(gè)由大小寫字母和數(shù)字組成的“亂碼塊”,外層被-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----包裹著。這其實(shí)是 PEM 格式的私鑰,亂碼塊是被 Base64 編碼的二進(jìn)制數(shù)據(jù)。
注意到現(xiàn)在只生成了一個(gè)私鑰,那么公鑰在哪呢?OpenSSL擴(kuò)展并沒有生成公鑰的函數(shù),公鑰是從私鑰當(dāng)中提取出來的,使用 openssl_pkey_get_details() 函數(shù):
從私鑰提取公鑰
<?php
$privateKey = openssl_pkey_new();
$detail = openssl_pkey_get_details($privateKey);
$publicKeyString = $detail['key'];
echo $publicKeyString;
openssl_pkey_get_details() 接受一個(gè)私鑰對(duì)象,返回一個(gè) array 包含私鑰中附帶的相關(guān)信息,比如 RSA 的 n、e1、e2 值。。。不用深究這幾個(gè)值,他們已經(jīng)是密碼學(xué)原理才能解釋的東西了。我們只關(guān)心分析結(jié)果的 key 值,key 值就是提取出來的公鑰啦。
我們將公鑰私鑰分別保存到磁盤上:
<?php
// 如果密鑰已經(jīng)是PEM格式的了,那就直接寫到磁盤上
file_put_contents('public.key', $publicKeyString);
// 否則需要用 openssl_pkey_export()
// 或者openssl_pkey_export_to_file()
// 轉(zhuǎn)換成PEM格式
openssl_pkey_export_to_file($privateKey, 'private.key');
有了一對(duì)公鑰、私鑰之后,就可以進(jìn)行非對(duì)稱加密了。注意 公鑰 可以分發(fā)給別人用的,而 私鑰 只能你自己知道,否則非對(duì)稱加密系統(tǒng)就完全失效了。
2. 非對(duì)稱 加密 與 解密
在不管是加密還是解密,都要先讀取密鑰。上一節(jié)我們保存在磁盤上的密鑰是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'));
另外還有兩個(gè)函數(shù):openssl_get_privatekey() 和 openssl_get_publickey(),只是上述兩個(gè)函數(shù)的別名,完成的功能相同。
介紹了一大堆,終于到了真正的加密解密了。因?yàn)槭欠菍?duì)稱加密,所以公鑰和私鑰是交錯(cuò)使用的:公鑰加密的數(shù)據(jù)用密鑰解密,同樣,私鑰加密的數(shù)據(jù)用公鑰解密。
公鑰加密私鑰解密:
// 加密
$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;
結(jié)果:
??qz?icG?!??N?.?0QM]_+B??i=輬???>1N`Z????¨Z9qr??<z?H???dy??я3T??G?q?HE???SAxd綧h` ??6????£n??Q1?q??????
PHP是世界上最好的語言!
可以看到經(jīng)過加密后,明文已經(jīng)變成完全無法閱讀的亂碼了。經(jīng)過解密后又變回了原文。公鑰加密私鑰解密滿足了最常見的數(shù)據(jù)保密的需求,別人用你的公鑰加密的數(shù)據(jù),只能用你自己的私鑰解開。
私鑰加密公鑰解密
// 加密
$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;
輸出的結(jié)果與公鑰加密類似,就不再贅述了。私鑰加密公鑰解密一般用于簽名,因?yàn)橛媚愕乃借€加密的內(nèi)容,大家只能用你的公鑰解開,從而保證了加密的信息確實(shí)是由你發(fā)出的。