PHP使用Openssl進行RSA加密

一、應用背景:

     在php服務端與客戶端交互或開放api時,通常需要對敏感的部分api數據傳輸進行數據加密。

二、公鑰私鑰加解密的作用:

    (1)、公鑰加密

    假設一下,我找了兩個數字,一個是1,一個是2。我把2保留起來,不告訴你們,當做我的私鑰,然後我告訴大家,1是我的公鑰。

    我有一個文件,不能讓別人看,我就用1加密了。別人找到了這個文件,但是他不知道2就是解密的私鑰啊,所以他解不開,只有我可以用數字2,就是我的私鑰,來解密。這樣我就可以保護數據了。

    我的好朋友x用我的公鑰1加密了字符a,加密後成了b,放在網上。別人偷到了這個文件,但是別人解不開,因爲別人不知道2就是我的私鑰,只有我才能解密,解密後就得到a。這樣,我們就可以傳送加密的數據了。

    (2)、私鑰簽名

    如果我用私鑰加密一段數據(當然只有我可以用私鑰加密,因爲只有我知道2是我的私鑰),結果所有的人都看到我的內容了,因爲他們都知道我的公鑰是1,那麼這種加密有什麼用處呢?

    但是我的好朋友x說有人冒充我給他發信。怎麼辦呢?我把我要發的信,內容是c,用我的私鑰2,加密,加密後的內容是d,發給x,再告訴他解密看是不是c。他用我的公鑰1解密,發現果然是c。
    這個時候,他會想到,能夠用我的公鑰解密的數據,必然是用我的私鑰加的密。只有我知道我得私鑰,因此他就可以確認確實是我發的東西。

    這樣我們就能確認發送方身份了。這個過程叫做數字簽名。當然具體的過程要稍微複雜一些。用私鑰來加密數據,用途就是數字簽名。

三、總結

  •     公鑰和私鑰是成對的,它們互相解密。
  •     公鑰加密,私鑰解密。
  •     私鑰加密,公鑰解密(簽名的一種方式)。
  •     私鑰簽名,公鑰驗證。

四、簡單測試:

<?php

$config = array(
    "digest_alg"    => "sha512",
    "private_key_bits" => 4096,           //字節數  512 1024 2048  4096 等 ,不能加引號,此處長度與加密的字符串長度有關係,可以自己測試一下
    "private_key_type" => OPENSSL_KEYTYPE_RSA,   //加密類型
  );
$res =    openssl_pkey_new($config); 

//提取私鑰
openssl_pkey_export($res, $private_key);

//生成公鑰
$public_key = openssl_pkey_get_details($res);
// var_dump($public_key);

$public_key=$public_key["key"];

//顯示數據
var_dump($private_key);    //私鑰
var_dump($public_key);     //公鑰

//要加密的數據
$data = "http://www.cnblogs.com/wt645631686/";
echo '加密的數據:'.$data."\r\n";  

//私鑰加密後的數據
openssl_private_encrypt($data,$encrypted,$private_key);

//加密後的內容通常含有特殊字符,需要base64編碼轉換下
$encrypted = base64_encode($encrypted);
echo "私鑰加密後的數據:".$encrypted."\r\n";  

//公鑰解密  
openssl_public_decrypt(base64_decode($encrypted), $decrypted, $public_key);
echo "公鑰解密後的數據:".$decrypted,"\r\n";  
  
//----相反操作。公鑰加密 
openssl_public_encrypt($data, $encrypted, $public_key);
$encrypted = base64_encode($encrypted);  
echo "公鑰加密後的數據:".$encrypted."\r\n";
  
openssl_private_decrypt(base64_decode($encrypted), $decrypted, $private_key);//私鑰解密  
echo "私鑰解密後的數據:".$decrypted."n";

五、封裝:

<?php

/**
 * RSA算法類【支持:公鑰加密-私鑰解密;私鑰加密-公鑰解密;私鑰簽名-公鑰驗籤】
 * 簽名及密文編碼:base64字符串/十六進制字符串/二進制字符串流
 * 填充方式: PKCS1Padding(加解密)/NOPadding(解密)
 *
 * Notice:Only accepts a single block. Block size is equal to the RSA key size!
 * 如密鑰長度爲1024 bit,則加密時數據需小於128字節,加上PKCS1Padding本身的11字節信息,所以明文需小於117字節
 *
 * 用openssl生成rsa密鑰對(私鑰/公鑰)命令:
 * openssl genrsa -out rsa_private_key.pem 1024
 * openssl rsa -pubout -in rsa_private_key.pem -out rsa_public_key.pem
 */

class Rsa
{
    private $pubKey = null;
    private $priKey = null;
    private $isFile = null;

    /**
     * 構造函數
     * @param string 公鑰文件(驗籤和加密時傳入)
     * @param string 私鑰文件(簽名和解密時傳入)
     * @param string
     */
    public function __construct($public_key_file = '', $private_key_file = '', $isFile = true)
    {
    	// 證書是否是文件
    	$this->isFile = $isFile;
    	
    	// 若未指定公鑰,則使用默認公鑰
    	$public_key_file = $public_key_file ? $public_key_file : __DIR__ . DS . 'cert' . DS . 'public.pem';
    	$this->_getPublicKey($public_key_file); // 從文件中提取公鑰
        
    	// 若未指定私鑰,則使用默認私鑰
    	$private_key_file = $private_key_file ? $private_key_file : __DIR__ . DS . 'cert' . DS . 'private.pem';
    	$this->_getPrivateKey($private_key_file);  // 從文件中提取私鑰

    }
    
    /**
     * 【私鑰簽名】
     * @param string 簽名材料
     * @param string 簽名編碼(base64/hex/bin)
     * @return 簽名值
     */
    public function sign($data, $code = 'base64')
    {
    	$ret = false;
    	if (openssl_sign($data, $ret, $this->priKey)) {
    		$ret = $this->_encode($ret, $code);
    	}
    	return $ret;
    }
    
    /**
     * 【公鑰驗籤】
     * @param string 簽名材料(被簽名數據)
     * @param string 簽名值(已經簽名的字符串)
     * @param string 簽名編碼(base64/hex/bin)
     * @return bool
     */
    public function verify($data, $sign, $code = 'base64')
    {
    	$ret = false;
    	$sign = $this->_decode($sign, $code);
    	if ($sign !== false) {
    		switch (openssl_verify($data, $sign, $this->pubKey)) {
    			case 1:
    				$ret = true;
    				break;
    			case 0:
    			case -1:
    			default:
    				$ret = false;
    		}
    	}
    	return $ret;
    }
    
    /**
     * 【私鑰加密】
     * @param string 明文
     * @param string 密文編碼(base64/hex/bin)
     * @param int 填充方式(貌似php有bug,所以目前僅支持OPENSSL_PKCS1_PADDING)
     * @return string 密文
     */
    public function prienc($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING)
    {
    	$ret = false;
    	if (!$this->_checkPadding($padding, 'en')) $this->_error('padding error');
    	if (openssl_private_encrypt($data, $result, $this->priKey, $padding)) {
    		$ret = $this->_encode($result, $code);
    	}
    	return $ret;
    }
    
    /**
     * 【公鑰解密】
     * @param string 密文
     * @param string 密文編碼(base64/hex/bin)
     * @param int 填充方式(OPENSSL_PKCS1_PADDING / OPENSSL_NO_PADDING)
     * @param bool 是否翻轉明文(When passing Microsoft CryptoAPI-generated RSA cyphertext, revert the bytes in the block)
     * @return string 明文
     */
    public function pubdec($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING, $rev = false)
    {
    	$ret = false;
    	$data = $this->_decode($data, $code);
    	if (!$this->_checkPadding($padding, 'de')) $this->_error('padding error');
    	if ($data !== false) {
    		if (openssl_public_decrypt($data, $result, $this->pubKey, $padding)) {
    			$ret = $rev ? rtrim(strrev($result), "\0") : '' . $result;
    		}
    	}
    	return $ret;
    }
    
    /**
     * 【公鑰加密】
     * @param string 明文
     * @param string 密文編碼(base64/hex/bin)
     * @param int 填充方式(貌似php有bug,所以目前僅支持OPENSSL_PKCS1_PADDING)
     * @return string 密文
     */
    public function pubenc($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING)
    {
    	$ret = false;
    	if (!$this->_checkPadding($padding, 'en')) $this->_error('padding error');
    	if (openssl_public_encrypt($data, $result, $this->pubKey, $padding)) {
    		$ret = $this->_encode($result, $code);
    	}
    	return $ret;
    }
    
    /**
     * 【私鑰解密】
     * @param string 密文
     * @param string 密文編碼(base64/hex/bin)
     * @param int 填充方式(OPENSSL_PKCS1_PADDING / OPENSSL_NO_PADDING)
     * @param bool 是否翻轉明文(When passing Microsoft CryptoAPI-generated RSA cyphertext, revert the bytes in the block)
     * @return string 明文
     */
    public function pridec($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING, $rev = false)
    {
    	$ret = false;
    	$data = $this->_decode($data, $code);
    	if (!$this->_checkPadding($padding, 'de')) $this->_error('padding error');
    	if ($data !== false) {
    		if (openssl_private_decrypt($data, $result, $this->priKey, $padding)) {
    			$ret = $rev ? rtrim(strrev($result), "\0") : '' . $result;
    		}
    	}
    	return $ret;
    }
    
    /**
     * 【公鑰加密長數據】
     */
    public function longpubenc($data){
    
    	$crypto = '';
    
    	foreach (str_split($data, 117) as $chunk) {
    
    		openssl_public_encrypt($chunk, $result, $this->pubKey);
    
    		$crypto .= $result;
    	}
    
    	return base64_encode($crypto);
    }
    
    /**
     * 【私鑰解密長數據】
     */
    public function longpridec($data){
    
    	$crypto = '';
    
    	foreach (str_split(base64_decode($data), 128) as $chunk) {
    
    		openssl_private_decrypt($chunk, $result, $this->priKey);
    
    		$crypto .= $result;
    	}
    
    	return $crypto;
    }
    
    /**
     * 創建證書
     * @return boolean
     */
    public static function makeCert(){
    	// 根據字節數、加密類型創建祕鑰及公鑰
    	$config = ["digest_alg"=>"sha512", "private_key_bits"=>1024, "private_key_type"=>OPENSSL_KEYTYPE_RSA];
    	$result = openssl_pkey_new($config);
    	if (! $result){
    		$this->_error(openssl_error_string());
    	}
    	// 提取私鑰及公鑰
    	openssl_pkey_export($result, $private_key);
    	$public_key = openssl_pkey_get_details($result);
    	$public_key = $public_key["key"];
    	// 校驗目錄是否存在且可寫
    	$path = __DIR__ . DS ."cert" . DS;
    	$check_path = is_dir($path) ? is_writable($path) : mkdir($path, 0755, true);
    	if (! $check_path){
    		$this->_error("文件不可寫入或目錄無法創建:{$check_path}!");
    	}
    	// 導出私鑰及公鑰到文件中
    	file_put_contents("{$path}cert_public.key", $public_key);
    	file_put_contents("{$path}cert_private.pem", $private_key);
    	openssl_free_key($result);
    }
    
    /**
     * 釋放資源
     */
    public function __destruct()
    {
    	is_resource($this->priKey) && @openssl_free_key($this->priKey);
    	is_resource($this->pubKey) && @openssl_free_key($this->pubKey);
    }
    
    /**
     * 自定義錯誤處理
     */
    private function _error($msg)
    {
        die('RSA Error:' . $msg); //TODO
    }

    /**
     * 檢測填充類型
     * 加密只支持PKCS1_PADDING
     * 解密支持PKCS1_PADDING和NO_PADDING
     *
     * @param int 填充模式
     * @param string 加密en/解密de
     * @return bool
     */
    private function _checkPadding($padding, $type)
    {
        if ($type == 'en') {
            switch ($padding) {
                case OPENSSL_PKCS1_PADDING:
                    $ret = true;
                    break;
                default:
                    $ret = false;
            }
        } else {
            switch ($padding) {
                case OPENSSL_PKCS1_PADDING:
                case OPENSSL_NO_PADDING:
                    $ret = true;
                    break;
                default:
                    $ret = false;
            }
        }
        return $ret;
    }

    /**
     * 將加密後的數據轉換成base64|hex|bin格式
     * @param string $data 加密後的數據
     * @param string $code 數據格式
     * @return string
     */
    private function _encode($data, $code)
    {
        switch (strtolower($code)) {
            case 'base64':
                $data = base64_encode('' . $data);
                break;
            case 'hex':
                $data = bin2hex($data);
                break;
            case 'bin':
            default:
        }
        return $data;
    }

    /**
     * 將需要解密的數據轉換成base64|hex|bin格式
     * @param string $data 需要解密的數據
     * @param string $code 數據格式
     * @return string
     */
    private function _decode($data, $code)
    {
        switch (strtolower($code)) {
            case 'base64':
                $data = str_replace(' ', '+', $data);
                $data = base64_decode($data);
                break;
            case 'hex':
                $data = $this->_hex2bin($data);
                break;
            case 'bin':
            default:
        }
        return $data;
    }

    /**
     * 提取公鑰
     * @param string $file
     */
    private function _getPublicKey($file)
    {
        $key_content = $this->_readFile($file);
        if ($key_content) {
        	self::_formatPubkey($key_content); // 轉換成標準公鑰格式
            $this->pubKey = openssl_get_publickey($key_content);
        }
    }

    /**
     * 提取私鑰
     * @param string $file
     */
    private function _getPrivateKey($file)
    {
        $key_content = $this->_readFile($file);
        if ($key_content) {
        	self::_formatPrikey($key_content); // 轉換成標準祕鑰格式
            $this->priKey = openssl_get_privatekey($key_content);
        }
    }

    /**
     * 讀取公鑰文件
     * @param string $file
     * @return unknown|Ambigous <boolean, string>
     */
    private function _readFile($file)
    {
        $ret = false;
        if (!$this->isFile) return $file;
        if (!file_exists($file)) {
            $this->_error("The file {$file} is not exists");
        } else {
            $ret = file_get_contents($file);
        }
        return $ret;
    }
    
	/**
	 * 格式化祕鑰(轉換祕鑰格式)
	 * @param unknown $prikey
	 */
    private function _formatPrikey(&$prikey)
    {
    	// 如果沒有BEGIN PRIVATE KEY 以及 END PRIVATE KEY 標識,則將此祕鑰進行格式化
    	if (! (strpos($prikey, 'PRIVATE KEY') !== false)){
    		$prikey = chunk_split($prikey, 64, "\n");
            $prikey = "-----BEGIN RSA PRIVATE KEY-----\n" . $prikey . "-----END RSA PRIVATE KEY-----\n";
    	}
    }
    
    /**
     * 格式化公鑰(轉換公鑰格式)
     * @param unknown $prikey
     */
    private function _formatPubkey(&$pubkey)
    {
    	// 如果沒有BEGIN PRIVATE KEY 以及 END PRIVATE KEY 標識,則將此祕鑰進行格式化
    	if (! (strpos($pubkey, 'PUBLIC KEY') !== false)){
    		$pubkey = chunk_split($pubkey, 64, "\n");
            $pubkey = "-----BEGIN PUBLIC KEY-----\n" . $pubkey . "-----END PUBLIC KEY-----\n";
    	}
    }

    /**
     * 將十六進制數據轉換成ASCII字符
     * @param string $hex
     * @return Ambigous <boolean, string>
     */
    private function _hex2bin($hex = false)
    {
        $ret = $hex !== false && preg_match('/^[0-9a-fA-F]+$/i', $hex) ? pack("H*", $hex) : false;
        return $ret;
    }
    
}

文章參考:PHP中使用OpenSSL生成RSA公鑰私鑰及進行加密解密示例(非對稱加密)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章