一、前言
在PHP中一般有三个密码库(扩展):mcrypt、openssl、sodium
mcrypt 扩展从 PHP 7.1 开始被标记为过时,并从 PHP 7.2 开始被正式移除。
openssl是目前使用最广泛的扩展,提供的函数支持多种多样的自定义参数,但是如果参数使用不当,可能会造成安全问题,对开发者的密码学知识要求较高。
而 sodium 是一个更现代化的扩展,自从 PHP 7.2 开始就被引入到了PHP核心库中,无需安装就可以直接使用。sodium 提供的函数可自定义参数较少(因为它已经帮开发者选择好了最佳的参数配置),使用更加方便,安全性也足够高,对开发者的密码学知识要求较低,但是这个扩展目前使用人数还比较少,网上资料也相对较少。
下面介绍如何使用 sodium 扩展进行非对称加解密,非对称加密算法常见的有 RSA
和 ECC
两种,sodium扩展使用的是 ECC 算法(详见这里),因此如果你需要的是RSA算法,那么就不适合使用 sodium 。
RSA 与 ECC 算法的对比
除了非对称加解密,sodium也支持对称加解密算法,例如AES-256-GCM
非对称加密又分为 “有身份验证” 和 “无身份验证” 两种。身份验证的意思是,通讯双方除了加解密消息外,还要验证消息发送者的真实身份(使用私钥签名的方式),这种场景下,消息发送方 和 消息接收方 都需要各自有一对公私钥;
而无身份验证的场景下,只需消息接收方有一对公私钥即可,然后通过某种安全的方式将公钥分发给发送方,发送方使用公钥对消息进行加密,而接收方可以使用私钥解密消息,但无法识别或验证发送方的身份。
分发公钥最简单、最常见的方法是通过HTTPS连接。例如,发送者可以通过HTTPS从接收者的网站下载接收者的公钥。
由于不存在身份验证机制或nonce值,会容易受到重放攻击。
二、无身份验证的非对称加解密
创建一个 Alice 类,然后模拟给 Alice 发消息:
class Alice
{
private $keypair;
public function __construct()
{
// 生成公私钥
$this->keypair = sodium_crypto_box_keypair();
}
/**
* 获取Alice的私钥
*
* @return string
*/
private function getPrivateKey(): string
{
return sodium_crypto_box_secretkey($this->keypair);
}
/**
* 获取Alice的公钥
*
* @return string
*/
public function getPublicKey(): string
{
return sodium_crypto_box_publickey($this->keypair);
}
/**
* 解密消息
*
* @param string $secretText 密文
*
* @return false|string 解密后的明文
*/
public function receiveMessage(string $secretText)
{
return sodium_crypto_box_seal_open($secretText, $this->keypair);
}
}
function main()
{
// 模拟给Alice发送消息
$Alice = new Alice();
$secretText = sodium_crypto_box_seal("Hello Alice, you don't know who I am", $Alice->getPublicKey());
// Alice解密消息
$plainText = $Alice->receiveMessage($secretText);
echo "Alice接收到了消息:$plainText\n";
}
main();
三、有身份验证的非对称加解密
创建 Alice 和 Bob 两个类,模拟 Alice 和 Bob 之间的通讯。
Alice类
class Alice
{
private $keypair;
public function __construct()
{
// 生成公私钥
$this->keypair = sodium_crypto_box_keypair();
}
/**
* 获取Alice的私钥
*
* @return string
*/
private function getPrivateKey(): string
{
return sodium_crypto_box_secretkey($this->keypair);
}
/**
* 获取Alice的公钥
*
* @return string
*/
public function getPublicKey(): string
{
return sodium_crypto_box_publickey($this->keypair);
}
/**
* 以Alice的身份发送消息
*
* @param string $message 明文消息
* @param string $recipientPubKey 接收者的公钥,用于加密消息
*
* @return array 返回nonce值和密文
*/
public function sendMessageTo(string $message, string $recipientPubKey): array
{
$keypair = sodium_crypto_box_keypair_from_secretkey_and_publickey($this->getPrivateKey(), $recipientPubKey);
$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
$secretText = sodium_crypto_box($message, $nonce, $keypair);
return [
'nonce' => $nonce,
'secret_text' => $secretText,
];
}
/**
* 以Alice的身份接收消息
*
* @param string $secretText 密文
* @param string $nonce nonce值
* @param string $senderPubKey 消息发送者的公钥,用于验证发送者的身份
*
* @return false|string 解密后的明文
*/
public function receiveMessageFrom(string $secretText, string $nonce, string $senderPubKey)
{
$keypair = sodium_crypto_box_keypair_from_secretkey_and_publickey($this->getPrivateKey(), $senderPubKey);
return sodium_crypto_box_open($secretText, $nonce, $keypair);
}
}
Bob类
class Bob
{
private $keypair;
public function __construct()
{
// 生成公私钥
$this->keypair = sodium_crypto_box_keypair();
}
/**
* 获取Bob的私钥
*
* @return string
*/
private function getPrivateKey(): string
{
return sodium_crypto_box_secretkey($this->keypair);
}
/**
* 获取Bob的公钥
*
* @return string
*/
public function getPublicKey(): string
{
return sodium_crypto_box_publickey($this->keypair);
}
/**
* 以Bob的身份发送消息
*
* @param string $message 明文消息
* @param string $recipientPubKey 接收者的公钥,用于加密消息
*
* @return array 返回nonce值和密文
*/
public function sendMessageTo(string $message, string $recipientPubKey): array
{
$keypair = sodium_crypto_box_keypair_from_secretkey_and_publickey($this->getPrivateKey(), $recipientPubKey);
$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
$secretText = sodium_crypto_box($message, $nonce, $keypair);
return [
'nonce' => $nonce,
'secret_text' => $secretText,
];
}
/**
* 以Bob的身份接收消息
*
* @param string $secretText 密文
* @param string $nonce nonce值
* @param string $senderPubKey 消息发送者的公钥,用于验证发送者的身份
*
* @return false|string 解密后的明文
*/
public function receiveMessageFrom(string $secretText, string $nonce, string $senderPubKey)
{
$keypair = sodium_crypto_box_keypair_from_secretkey_and_publickey($this->getPrivateKey(), $senderPubKey);
return sodium_crypto_box_open($secretText, $nonce, $keypair);
}
}
模拟 Alice 和 Bob 通讯
function main()
{
$Alice = new Alice();
$Bob = new Bob();
/* 以Alice的身份给Bob发送消息 */
$msg = $Alice->sendMessageTo('Hello Bob, I am Alice', $Bob->getPublicKey());
// Bob解密消息
$plainText = $Bob->receiveMessageFrom($msg['secret_text'], $msg['nonce'], $Alice->getPublicKey());
echo "Bob接收到了Alice的消息:$plainText\n";
/* 以Bob的身份给Alice发送消息 */
$msg = $Bob->sendMessageTo('Hello Alice, I am Bob', $Alice->getPublicKey());
// Alice解密消息
$plainText = $Alice->receiveMessageFrom($msg['secret_text'], $msg['nonce'], $Bob->getPublicKey());
echo "Alice接收到了Bob的消息:$plainText\n";
}
main();