PHP使用非对称加密算法(RSA)签名及验签、加密及解密示例

两个密钥:公开密钥(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&amp;service=orderPay&amp;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;
?>