1. 程式人生 > >RSA結合DES解決上下行介面的安全性問題

RSA結合DES解決上下行介面的安全性問題

客戶端程式碼:

<?php
/*
	1:功能描述:
		此demo演示了一個測試介面的【訪問加參】和【返回驗證】
		測試介面的功能是:在請求的引數中新增【time】欄位並返回

	2:基本思路:
		2.1:資料使用des對稱加密方式通過key進行加密
		2.2:des key使用非對稱加密RSA進行加密
		2.3:為避免編碼問題,對資料json_encode的同時進行base64_encode
		2.4:為避免介面返回的明文傳輸,同樣採用base64_encode(json_encode($data));方式

	3:安全性預測:
		3.1:RSA的非對稱性
		3.2:因為【私鑰可以生成公鑰】,交換鑰匙的同時,讓客戶端只儲存兩個【公鑰】。	
		3.3:DES的【金鑰】是實時更新的。
		3.4:試想:
			3.4.1:資料在傳送是被修改:
				如果DES【deskey】鑰匙被修改。那麼不管資料是否被修改,都將無法DES解密資料
				如果資料【data】被修改。同樣的,DES將無解密資料
				如果,修改【deskey】,利用生成的【deskey】重新加密獲得【data】,RSA將無法解析出【deskey】,進而無法獲取請求引數
			3.4.2:資料在返回時被修改亦然。

	4:請求預覽:
		$data = 'api (array)data';
		$key = create_a_random_des_key();

		$post['apiName'] = base64_encode(json_encode(
			'data' => [des object]->encrypt($key, json_encode($data)),
			'deskey' => [rsa object]->publicEncrypt($key)
		));

	5:返回預覽:
		$rtn = json_decode($str, true);
		$rtn = json_decode(base64_decode($rtn['return']), true);
		$deskey = [rsa object]->publicDerypt($rtn['deskey']);
		$rtn = [des object]->decrypt($deskey, $rtn['data']);
*/
	header('Content-Type:text/html;charset="utf-8";');
	require_once('./ClientRSA.class.php');
	require_once('./ClientDES.class.php');

	define('DEBUG', true);

	if(defined('DEBUG') && DEBUG) echo '<h2>Client:</h2>';
	// api data
	$array = array(
		'a' => 1,
		'b' => 2,
		'c' => 3
	);
	// des key , must be 8 length
	$deskey = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), -4).substr(time(), -4);
	// DES object
	$des = new ClientDES($deskey);
	// DES encrypt api data
	$data['data'] = $des->encrypt(json_encode($array));

	if(defined('DEBUG') && DEBUG) var_dump($des->decrypt($data['data']));
	if(defined('DEBUG') && DEBUG) var_dump($des->decrypt('467B65E95C26E409D7D4248428A0EE6C8A24E528AFC9251756D03874959027F2'));
	if(defined('DEBUG') && DEBUG) var_dump($des->decrypt($data['data'].'aaa'));	// 修改des密文,無法解密。false
	if(defined('DEBUG') && DEBUG) var_dump($des->decrypt(str_shuffle($data['data'].'aaa')));// 修改des密文,無法解密。false

	// RSA object
	$rsa = new ClientRSA();
	// RSA encrypt DES key
	$data['deskey'] = $rsa->publicEncrypt($deskey);

	if(defined('DEBUG') && DEBUG) echo '<pre>';
	if(defined('DEBUG') && DEBUG) print_r($data);
	if(defined('DEBUG') && DEBUG) echo '</pre>';
	/*
		$data then maybe like this : 
		Array(
		    [data] => 467B65E95C26E409D7D4248428A0EE6C8A24E528AFC9251756D03874959027F2
		    [deskey] => aWuYzax02btcznGfaWPn+Jk8qIEjKDe0bcxPZP+p0aObEgg6/ieIQXxEZbCcuhckpYY//pUHyd09PhURpcg1zWDpPLnlyy1ezEVpqNDRk4jLMcAJpaA0uhKimOnjz2v2gnkZ2ir4H3bKEMIAfCOUOp6TwctZqipguq0pHzuhWOw=
		)
	*/
	$post['apiTest'] = base64_encode(json_encode($data));
	/*
		then, $post is the last data we used while requesting api. it maybe like this : 
		and the key 'apiTest' is the ACTION_NAME we wanted
		Array(
		    [apiTest] => eyJkYXRhIjoiREVBRkI5RTRENEY1QUZCOUM4NEJDNTdFOThFQ0JEOTk2MENEODBGM0FFQTJBN0ZGNzRBODIxQzhBNzgwRjcwQSIsImRlc2tleSI6Ikd4ZjdhYlpwSFA2RFd2RG5uXC96YkNJYlwvKzcrbmQ2a2ZWK2p5bU05aWt6ZnNzZ3VaNFFkSFdleXJGMmF6eDEzbVVMQ2hXVzdQalE1ZnNmbks0Zlp1a1wvREV2QUhvVkhtM2lWekxCU2RYS3RuZzM5TFZ4aDF1ellvM2hIVW95d3BGWjlPdE9wVXZibzdmQ25ud0pYS25WMlRDVVVqRG1VeVdjNTQ3QmJ5enhZND0ifQ==
		)
	*/
	if(defined('DEBUG') && DEBUG) echo '<pre>';
	if(defined('DEBUG') && DEBUG) print_r($post);
	if(defined('DEBUG') && DEBUG) echo '</pre>';
	if(defined('DEBUG') && DEBUG) echo '<hr/>';
	if(defined('DEBUG') && DEBUG) echo '<h2>Server:</h2>';
	// then, curl_post($post)
	$url = 'http://localhost/api-security/Server/Api.class.php';
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_HEADER, 0);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($curl, CURLOPT_POST, 1);
	curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
	$res = curl_exec($curl);
	curl_close($curl);

	if(defined('DEBUG') && DEBUG) var_dump($res);
	/*
		curl_post result maybe like : 
		string '{"return":"eyJkYXRhIjoiN0YxQzI5ODI3RDA2RjkyMDRGQzZGNTY0QTk5OERDRDhFMUExNDBCNDQzRjAxMTZGRDBEQkY4QzM3MjNFRTg4MjhBNEM3RkYwNDNCNDdGOTNGRUQwOUVFMkEwQzA4MDcxQUZFN0U0ODVCMUFGM0JBRjhDNURBOUM5NjMzQ0EyQkRFREFERDk1MERBQjg3RjczMjI2MjFGRTdBQzNENjQ1OUE5OUNBRDg0NDMxRjY2QzQ3OEU0NTdFOTUxMUQwNjZENjJGMjk5Qjg2MTFBMEIxQUFFMzZCMkRCOTEyRkI3MkUzMEExODYzODUzMDgwODZEMjY2OTREMkM1MzhFQjIyOSIsImRlc2tleSI6ImQ3Ump2SzU2eUo1S1ZlMlBuaUxjamNOdWNRZmpcL2FWcG92RzNOZjZwN0JSZ2dtVlpUT1VBd0N0VXpvWjFpXC91dmNkOFFcL0x0SWNnT0VOa0hpMGd3SE5kR2Jyb3kycmgrO'... (length=621)
	*/
	$rtn = json_decode($res, true);

	if(defined('DEBUG') && DEBUG) echo '<pre>';
	if(defined('DEBUG') && DEBUG) print_r($rtn);
	if(defined('DEBUG') && DEBUG) echo '</pre>';

	$rtn = json_decode(base64_decode($rtn['return']), true);

	if(defined('DEBUG') && DEBUG) echo '<pre>';
	if(defined('DEBUG') && DEBUG) print_r($rtn);
	if(defined('DEBUG') && DEBUG) echo '</pre>';
	/*
		$rtn maybe like this : 
		Array(
		    [data] => 7F1C29827D06F9204FC6F564A998DCD8E1A140B443F0116FD0DBF8C3723EE8828A4C7FF043B47F93FED09EE2A0C08071AFE7E485B1AF3BAF8C5DA9C9633CA2BDEDADD950DAB87F7322621FE7AC3D6459A99CAD84431F66C478E457E9511D066D62F299B8611A0B1AAE36B2DB912FB72E30A186385308086D26694D2C538EB229
		    [deskey] => d7RjvK56yJ5KVe2PniLcjcNucQfj/aVpovG3Nf6p7BRggmVZTOUAwCtUzoZ1i/uvcd8Q/LtIcgOENkHi0gwHNdGbroy2rh+9u4lgTMKUml573sGHXdb8MN/vTfetaVS1v9GUWj4wgRVU7TG16hzDf0RmeuJN6lOkDO4YbSeKG0o=
		)
		now, decrypt it. 
		if(successfully decrypt) 
			data is responsed by server and it has not been modified!
		else
			data is modified by someone!
	*/

	$rtnDeskey = $rsa->publicDecrypt($rtn['deskey']);
	if(defined('DEBUG') && DEBUG) echo $rtnDeskey;

	$des = new ClientDes($rtnDeskey);
	$rtn = json_decode($des->decrypt($rtn['data']), true);
	if(defined('DEBUG') && DEBUG) echo '<pre>';
	if(defined('DEBUG') && DEBUG) print_r($rtn);
	if(defined('DEBUG') && DEBUG) echo '</pre>';

	echo '<hr/>';
	if(count($rtn) != 0){
		echo '<h2>伺服器成功返回:</h2>';
		echo '<pre>';
		print_r($rtn);
		echo '</pre>';
	}else{
		echo '<h2>介面訪問失敗,或者資料被惡意修改!</h2>';
	}

?>

<?php
class ClientRSA{

	// client public key
	private $ckey = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCfRTdcPIH10gT9f31rQuIInLwe
7fl2dtEJ93gTmjE9c2H+kLVENWgECiJVQ5sonQNfwToMKdO0b3Olf4pgBKeLThra
z/L3nYJYlbqjHC3jTjUnZc0luumpXGsox62+PuSGBlfb8zJO6hix4GV/vhyQVCpG
9aYqgE7zyTRZYX9byQIDAQAB
-----END PUBLIC KEY-----';
	
	// server public key
	private $skey = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwuKDdARVjVBKVv/YHl3mFIyyR
RF+UoXVVy7IQPZ1Wy7lEOMKOtNrI1L+NczppZxqIxPg5WACoo9eWWVHPY5QotC1C
jx1RNV+UQpXCtJBvuxtRzLkfwA7zJJ9etb0pN7AaE5ZcgxmxbposdVlMT1PYauLG
Frx2RUxozpN/awdVmQIDAQAB
-----END PUBLIC KEY-----';
	
	private $cp = null;		// client public key object
	private $sp = null;		// server public key object
	
	// init client & server side public key objects
	public function __construct(){
		$this->cp = openssl_pkey_get_public($this->ckey);
		$this->sp = openssl_pkey_get_public($this->skey);
	}

	// encrypting with client public key while requesting api
	public function publicEncrypt($data){
		openssl_public_encrypt($data, $encrypted, $this->cp);
		return base64_encode($encrypted);
	}

	// decrypting whith server public key while api responsing
	public function publicDecrypt($encrypted){
		openssl_public_decrypt(base64_decode($encrypted), $decrypted, $this->sp);
		return $decrypted;
	}

}
?>
<?php
class ClientDES{
	var $key;
	var $iv; //偏移量

	public function __construct( $key, $iv=0 ) {
		//key長度8例如:1234abcd
		$this->key = $key;
		if( $iv == 0 ) {
			$this->iv = $key;
		} else {
			$this->iv = mcrypt_create_iv ( mcrypt_get_block_size (MCRYPT_DES, MCRYPT_MODE_CBC), MCRYPT_DEV_RANDOM );
		}
	}

	public function encrypt($str) {
		//加密,返回大寫十六進位制字串
		$size = mcrypt_get_block_size ( MCRYPT_DES, MCRYPT_MODE_CBC );
		$str = $this->pkcs5Pad ( $str, $size );
		return strtoupper( bin2hex( mcrypt_cbc(MCRYPT_DES, $this->key, $str, MCRYPT_ENCRYPT, $this->iv ) ) );
	}

	public function decrypt($str) {
		//解密
		$strBin = $this->hex2bin( strtolower( $str ) );
		$str = mcrypt_cbc( MCRYPT_DES, $this->key, $strBin, MCRYPT_DECRYPT, $this->iv );
		$str = $this->pkcs5Unpad( $str );
		return $str;
	}

	private function hex2bin($hexData) {
		$binData = "";
		for($i = 0; $i < strlen ( $hexData ); $i += 2) {
		$binData .= chr ( hexdec ( substr ( $hexData, $i, 2 ) ) );
		}
		return $binData;
	}

	private function pkcs5Pad($text, $blocksize) {
		$pad = $blocksize - (strlen ( $text ) % $blocksize);
		return $text . str_repeat ( chr ( $pad ), $pad );
	}

	private function pkcs5Unpad($text) {
		$pad = ord ( $text {strlen ( $text ) - 1} );
		if ($pad > strlen ( $text ))
		return false;
		if (strspn ( $text, chr ( $pad ), strlen ( $text ) - $pad ) != $pad)
		return false;
		return substr ( $text, 0, - 1 * $pad );
	}
}
?>

伺服器端程式碼:

<?php
/*
	1:在此統一了介面地址。如:http://localhost/api-security/Server/Api.class.php
	如果你使用了【單一入口】的php框架,像ThinkPHP、CI,那麼,這個統一地址可能是:
		http://localhost/api-security/index.php/Api/Api/api
	其中:
		第一個Api是分組.Module
		第二個Api是控制器。Controller
		第三個api是動作。Action

	2:當然,驗證資料合法性和返回時,新增引數的過程可以封裝下。

	3:當然,狀態碼,你也可以封裝下。或者是十六進位制的。define形式的。

	4:基本思路在客戶端已經說明了。基本上,客戶端訪問時如何如何,伺服器就是反其道行之。伺服器返回資料,其實亦然。
*/
require_once('./ServerRSA.class.php');
require_once('./ServerDES.class.php');
header('Content-Type:text/json;charset="utf-8";');
class Api{
	private $rsa = null;
	private $des = null;
	private $data = null;
	private $debug = true;
	private $apiName = '';

	const SUCCESS = 0; 								// 操作成功!
	const FAILED = -1;								// 系統錯誤:資料庫操作錯誤
	const ERROR_DATA_FORMAT_ERROR = 1;				// 系統級錯誤:資料格式錯誤

	/**
	 * 構造方法,驗證rsa簽名是否正確	
	 * 
	 * @author GuoJunzhou
	 */
	public function __construct(){
		$keys = array_keys($_POST);
		$values = array_values($_POST);
		if(count($keys) != 1 || count($values) != 1){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', '介面引數應為:{action:data},介面名稱:介面引數.');
		}
		if(!$data = base64_decode(array_pop($values))){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', '介面引數不能base64解編碼。');
		}
		if(!$data = json_decode($data, true)){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', '介面引數base64解編碼後不能json解碼。');
		}
		if(!isset($data['deskey']) || strlen($data['deskey']) == 0){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', '介面引數base64解編碼,json解碼後,不包含DES key引數。');
		}
		$this->rsa = new ServerRSA();
		$deskey = $this->rsa->privateDecrypt($data['deskey']);
		if(!$deskey){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', 'DES key解析錯誤。');
		}
		$this->des = new ServerDES($deskey);
		$data = $this->des->decrypt($data['data']);
		$data = json_decode($data, true);
		if(!$data){
			$this->apiReturn(self::ERROR_DATA_FORMAT_ERROR, '資料格式錯誤!', '', '無法DES解析資料。');
		}
		$this->apiName = array_pop($keys);
		$this->data = $data;
	}

	/**
	 * 介面統一地址,驗證是否指定【動作】並路由到指定函式處理業務邏輯	
	 * 
	 * @author GuoJunzhou
	 */
	public function api(){
		$data = $this->data;
		if(!$this->apiName){
			$this->apiReturn(self::ERROR_ACTION_NOT_PROVIDED, '沒有提供介面名稱! ', '', '呼叫介面: '.$this->apiName.' 不存在.');
		}
		if(strval($this->apiName) == 'api' || !method_exists($this, strval($this->apiName))){
			$this->apiReturn(self::ERROR_ACTION_NOT_EXISTS, '不存在的介面名稱! ', '', '沒有你要請求的介面: '.$this->apiName);
		}
		$action = strval($this->apiName);
		$this->$action();
	}

	

	/**
	 * 測試介面	
	 * 
	 * @author GuoJunzhou
	 */
	private function apiTest(){
		$data = $this->data;
		$data['time'] = time();
		$this->apiReturn(self::SUCCESS, '測試介面成功返回!', $data);
	}

	private function apiReturn($status, $msg, $data, $more=false){
		$array = array(
			'status' => $status,
			'msg' => $msg.($this->debug && $more ? $more : ''),
			'data' => $data
		);
		// des key , must be 8 length
		$deskey = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), -4).substr(time(), -4);
		// DES object
		$des = new ServerDES($deskey);
		// DES encrypt api data
		$rtn['data'] = $des->encrypt(json_encode($array));

		// RSA encrypt DES key
		$rtn['deskey'] = $this->rsa->privateEncrypt($deskey);
		
		echo json_encode(array('return'=>base64_encode(json_encode($rtn))));
		exit();
	}
}

$api = new Api();
$api->api();
?>
<?php
class ServerRSA{

	// client private key
	private $ckey = '-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJ9FN1w8gfXSBP1/
fWtC4gicvB7t+XZ20Qn3eBOaMT1zYf6QtUQ1aAQKIlVDmyidA1/BOgwp07Rvc6V/
imAEp4tOGtrP8vedgliVuqMcLeNONSdlzSW66alcayjHrb4+5IYGV9vzMk7qGLHg
ZX++HJBUKkb1piqATvPJNFlhf1vJAgMBAAECgYA736xhG0oL3EkN9yhx8zG/5RP/
WJzoQOByq7pTPCr4m/Ch30qVerJAmoKvpPumN+h1zdEBk5PHiAJkm96sG/PTndEf
kZrAJ2hwSBqptcABYk6ED70gRTQ1S53tyQXIOSjRBcugY/21qeswS3nMyq3xDEPK
XpdyKPeaTyuK86AEkQJBAM1M7p1lfzEKjNw17SDMLnca/8pBcA0EEcyvtaQpRvaL
n61eQQnnPdpvHamkRBcOvgCAkfwa1uboru0QdXii/gUCQQDGmkP+KJPX9JVCrbRt
7wKyIemyNM+J6y1ZBZ2bVCf9jacCQaSkIWnIR1S9UM+1CFE30So2CA0CfCDmQy+y
7A31AkB8cGFB7j+GTkrLP7SX6KtRboAU7E0q1oijdO24r3xf/Imw4Cy0AAIx4KAu
L29GOp1YWJYkJXCVTfyZnRxXHxSxAkEAvO0zkSv4uI8rDmtAIPQllF8+eRBT/deD
JBR7ga/k+wctwK/Bd4Fxp9xzeETP0l8/I+IOTagK+Dos8d8oGQUFoQJBAI4Nwpfo
MFaLJXGY9ok45wXrcqkJgM+SN6i8hQeujXESVHYatAIL/1DgLi+u46EFD69fw0w+
c7o0HLlMsYPAzJw=
-----END PRIVATE KEY-----';
	
	// server private key
	private $skey = '-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALC4oN0BFWNUEpW/
9geXeYUjLJFEX5ShdVXLshA9nVbLuUQ4wo602sjUv41zOmlnGojE+DlYAKij15ZZ
Uc9jlCi0LUKPHVE1X5RClcK0kG+7G1HMuR/ADvMkn161vSk3sBoTllyDGbFumix1
WUxPU9hq4sYWvHZFTGjOk39rB1WZAgMBAAECgYAfpA75QvZnYGgrc8FDPrW9Rrz2
DX3niLkPcXciPUXsgnFba6Y6EtuRVbYuR1FQCevQTSP8cGs9xogVHUzuiu+9xclX
0j4U5tPqEtujNInHaJMWHweWuNViB4QjtpMexfL2YnJJEQwPBmghIlMSvEu8VYej
BJ1o4Gs6NCPdlpt1eQJBANbassGDkTBI+zB7gKIfxP8X9OFz+2hVM98Ix6OClIP3
gCA8iZTki1tXTHM9ks20VEi6fFXm4UEsQRr0tcYlIycCQQDSkHFxZ4CkxT6k+wPN
eiL7UKrCZDb3EwDVfTZGkkFs3Y1PRdmqKGfzxqjNLQYvUb8vpBDDDOCX/6Jey2Mu
cDk/AkEAlXG/Xqz2A0DwP3PYMGyqqMjwExWKbLK/Bsbjng8I4JX57/JLvF4PtLQP
QCU3BJAHBCN6soGKcrumeWp3OWDBhwJAceqzdScJ8hrESX2EESaCxnRd7c1J72HY
kaesNYHq43r5eGnR2L0DgF9584boUbFoPd4FM4FEAPfMYHyoq/cLAQJBAJqY0dT/
lWl/A+PAemlAQxBWBuD7axY9X1+hq6vgwbyErNUXiwQJ9B9n7iJe4KsFSo8jbhSJ
DiMuwkRSktz2JzE=
-----END PRIVATE KEY-----';
	
	private $sp = null;		// server private key object 
	private $cp = null;		// client private key object
	
	// init client & server side private objects
	public function __construct(){
		$this->sp = openssl_pkey_get_private($this->skey);
		$this->cp = openssl_pkey_get_private($this->ckey);
	}

	// encrypting with server private key while api responsing
	public function privateEncrypt($data){
		openssl_private_encrypt($data, $encrypted, $this->sp);
		return base64_encode($encrypted);
	}

	// decrypting whith client private key while api requesting
	public function privateDecrypt($encrypted){
		openssl_private_decrypt(base64_decode($encrypted), $decrypted, $this->cp);
		return $decrypted;
	}
}
?>
其實,ServerDES.class.php 其實和 ClientDES.class.php 的內容是一致的。只不過,在實際使用時,客戶端往往是,Android、IOS,需要前端工程師使用他們的語言實現一下客戶端邏輯。

ClientRSA.class.php 和 ServerRSA.class.php 的區別只是:客戶端拿了兩把鑰匙裡的兩個公鑰,伺服器拿了兩把鑰匙裡的兩個私鑰。相對的,使用了其鑰匙所對應的加密解密函式。當然,客戶端的邏輯,也需要前端工程師使用自己的語言來實現。

這種方式,對於安全性的考慮,歡迎大家各抒己見,我會積極參與大家的討論。當然,你也可以郵件我。

[email protected]

[email protected]