两个密钥:公开密钥(public_key)(以下简称公钥)和私有密钥(private_key)(以下简称私钥)。
公钥与私钥是一对,如果用公钥对数据进行签名(或加密),只有用对应的私钥才能验签(或解密);如果用私钥对数据进行签名(或加密),那么只有用对应的公钥才能验签(或解密)。
因为签名(或加密)和验签(或解密)使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
两种应用场景:一种是签名与验签,这个只保证了数据来源的合法性,也就是你收到的数据是由你预期的对方发过来的;另一种是加密与解密,这个可以保证你的数据不会在网上裸奔,即便被劫持了也没关系。(注:传统的MD5只能对数据进行签名和验签,因为MD5是不可逆的,加密了就无法解密)
一般的应用场景都是用私钥签名、用公钥验签,或者用公钥加密、用私钥解密。
以支付宝的支付场景为例,在支付订单的时候,我们把订单交易数据通过我们自己的私钥(rsa_private_key)进行签名然后传给支付宝的服务器,支付宝的服务器接收到数据后会用我们自己的公钥(rsa_public_key)进行验签,这就是为什么要把我们自己的公钥(rsa_public_key)通过支付宝控制台传上去的原因了;而在订单异步通知的时候,支付宝会用他们自己的私钥(alipay_private_key)对订单通知数据进行签名然后传给我们的服务器,我们的服务器在接收到数据后只能用支付宝的公钥(alipay_public_key)进行验签,这就是为什么我们要去支付宝控制台获取到支付宝公钥的原因了。
如果要对传输的数据进行加密,那么我们在发送数据之前要用支付宝的公钥对数据加密,而支付宝那边收到数据后则用他们自己的私钥进行解密;而支付宝那边在发送数据之前就会用我们的公钥对数据进行加密,而我们在收到数据后则用我们自己的私钥进行解密。
PHP的RSA签名与验签及加密与解密代码示例如下:
<?php
/**
* RSA操作类库
* @author Ken(QQ:2480990710)
* @blog http://ken.01h.net/
* @date 2020-3-12
*/
class Rsa{
//RSA or RSA2
public $signType = 'RSA';
//私钥文件路径(与私钥值二选一)
public $rsaPrivateKeyFilePath;
//私钥值
public $rsaPrivateKey;
//公钥文件路径(与公钥值二选一)
public $rsaPublicKeyFilePath;
//公钥值
public $rsaPublicKey;
/**
* 校验$value是否非空
* if not set ,return true;
* if is null , return true;
*/
private function checkEmpty($value) {
if (!isset($value))
return true;
if ($value === null)
return true;
if (trim($value) === "")
return true;
return false;
}
/**
* 获取私钥
* @return bool|resource
*/
private function getPrivateKey()
{
if($this->checkEmpty($this->rsaPrivateKeyFilePath)){
$priKey = $this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
}else {
$priKey = file_get_contents($this->rsaPrivateKeyFilePath);
$res = openssl_pkey_get_private($priKey);
}
return $res;
}
/**
* 获取公钥
* @return bool|resource
*/
private function getPublicKey()
{
if($this->checkEmpty($this->rsaPublicKeyFilePath)){
$pubKey = $this->rsaPublicKey;
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
}else {
$pubKey = file_get_contents($this->rsaPublicKeyFilePath);
$res = openssl_pkey_get_public($pubKey);
}
return $res;
}
/**
* 私钥签名
* @param string $data
* @return null|string
*/
public function sign($data) {
$res = $this->getPrivateKey();
if ("RSA2" == $this->signType) {
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
} else {
openssl_sign($data, $sign, $res);
}
if(!$this->checkEmpty($this->rsaPrivateKeyFilePath)){
openssl_free_key($res);
}
$sign = base64_encode($sign);
return $sign;
}
/**
* 公钥验签
* @param string $data
* @param string $sign
* @return bool
*/
public function verify($data, $sign) {
$res = $this->getPublicKey();
if ("RSA2" == $this->signType) {
$result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
} else {
$result = (bool)openssl_verify($data, base64_decode($sign), $res);
}
if(!$this->checkEmpty($this->rsaPublicKeyFilePath)) {
openssl_free_key($res);
}
return $result;
}
/**
* 公钥加密
* @param string $data
* @return null|string
*/
public function publicEncrypt($data)
{
if (!is_string($data)) {
return null;
}
$res = $this->getPublicKey();
if ("RSA2" == $this->signType) {
$max_encrypt_block = 245;
} else {
$max_encrypt_block = 117;
}
$result = '';
while($data){
$input = substr($data, 0, $max_encrypt_block);
$data = substr($data, $max_encrypt_block);
openssl_public_encrypt($input, $encrypted, $res);
$result .= $encrypted;
}
if(!$this->checkEmpty($this->rsaPublicKeyFilePath)) {
openssl_free_key($res);
}
$result = base64_encode($result);
return $result;
}
/**
* 私钥解密
* @param string $encrypted
* @return null|string
*/
public function privDecrypt($encrypted)
{
if (!is_string($encrypted)) {
return null;
}
$res = $this->getPrivateKey();
if ("RSA2" == $this->signType) {
$max_decrypt_block = 256;
} else {
$max_decrypt_block = 128;
}
$content = base64_decode($encrypted);
$result = '';
for($i = 0; $i < strlen($content)/$max_decrypt_block; $i++ ) {
$data = substr($content, $i * $max_decrypt_block, $max_decrypt_block);
openssl_private_decrypt($data, $decrypt, $res);
$result .= $decrypt;
}
if(!$this->checkEmpty($this->rsaPrivateKeyFilePath)){
openssl_free_key($res);
}
return $result;
}
}
$data = 'version=1.0&service=orderPay&mch_id=1000000000001';
$rsa = new Rsa();
//设置公钥
$rsa->rsaPublicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIaDx0DMC0NxLssTPZg3VZOX8xnW1hu74AK4Kwzv+fpZT9JiliIU/gTBwTJQNCnR2LbFR0DOMRnWUafrbI0IcbA2VA3sO+z/WwfGYTy9/fZnNOXr91Jgg7Hln+7qM7aWy2MoGY4M1s8NvtHbtMc+FZFQwVzgjipGDReEaK3ahGCQIDAQAB';
//设置私钥
$rsa->rsaPrivateKey = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIhoPHQMwLQ3EuyxM9mDdVk5fzGdbWG7vgArgrDO/5+llP0mKWIhT+BMHBMlA0KdHYtsVHQM4xGdZRp+tsjQhxsDZUDew77P9bB8ZhPL399mc05ev3UmCDseWf7uoztpbLYygZjgzWzw2+0du0xz4VkVDBXOCOKkYNF4RordqEYJAgMBAAECgYAxXbfzL+JV1lCtBHyFTcQcZGiZ5iV951od6aFEp8VvW5eN/k+fyfqEbEcEzrJdW0sj+DKgRUGwUEwUyKMSDhW2r8mFr0wlWDvOJDpVl1VYjSxp2yhwjg0H1uTjGZhf9G1GcWwJHnD3U54ZrzgtqGiEvqK64qIl1koGXG5ww5mu4QJBAO4CZfw8lF+wD2+YlT1x4e4ku4NyI9awihadV4LN3NiHs1Ubm2RuW4ca/hcH5oToRrRmypmOT8bxyWMMkrj5CIUCQQCSt8itnsDfxyWdhEX1ecyn+25bkZQo1fn+ujFxc6ZmGI0vym22HZji/ghKUAug+i6Owtn896/O80m5V/ZZ5EC1AkEAiWAjA2Ln9Q8G6c+1HEEWOcFD5gvEec9t9L2eXCZ8eRJiRRZpK5+y/plq3Vo3CLGU1d2axOTqURcPuTbxnQhIjQJAUpQ80HQVTR7S7iigE719UmMzRzjWMnHVZuk3oQqd8sMI3IhMXf+kqMagter90JpgEBxeA4MJoirPKRH4Z7oQLQJAJm5Fol7pjrCAVin5P1yxpD5ZZqPU4vzyk4tOomFYShtjV+6GQrNQNS6jmCZ2ARz4k0b0LrMnk+Iv9JQXFsDKiw==';
//签名
$sign = $rsa->sign($data);
echo "\r\n------------------sign--------------------\r\n";
echo $sign;
//验签
$verify = $rsa->verify($data, $sign);
echo "\r\n------------------verify--------------------\r\n";
var_dump($verify);
//公钥加密
$result = $rsa->publicEncrypt($data);
echo "\r\n------------------publicEncrypt--------------------\r\n";
echo $result;
//私钥解密
$data2 = $rsa->privDecrypt($result);
echo "\r\n------------------privDecrypt--------------------\r\n";
echo $data2;
?>