RSA結合DES解決上下行介面的安全性問題
阿新 • • 發佈:2019-01-10
客戶端程式碼:
<?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 的區別只是:客戶端拿了兩把鑰匙裡的兩個公鑰,伺服器拿了兩把鑰匙裡的兩個私鑰。相對的,使用了其鑰匙所對應的加密解密函式。當然,客戶端的邏輯,也需要前端工程師使用自己的語言來實現。
這種方式,對於安全性的考慮,歡迎大家各抒己見,我會積極參與大家的討論。當然,你也可以郵件我。