生活随笔
收集整理的這篇文章主要介紹了
PHP-RSA加密跨域通讯实战
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
PHP-RSA加密跨域通訊實戰 AUTH:PHILO EMAIL:lijianying12 at gmail.com
基于POST GET 的http通訊雖然非常成熟,但是很容易被人監聽。 并且如果使用跨域jsonp的通訊很容易在歷史記錄中發現通訊網址以及參數。為了克服這些問題, 并且降低服務器成本,我們沒有使用SSL而使用 RSA加密。文章中的php加密解密 JS的加密解密 互相加密解密 都能驗證通過。
其中PHP依賴常見的OPENSSL LIB 。 JS依賴 jsencrypt。
我們使用jsonp get RSA加密通訊好處如下:
前后分離適合cdn加速。 安全跨域更適合松散結構的網站。 不用去買ssl證書了。
首先要生成密匙對 openssl genrsa 1024 > private . key openssl rsa - in private . key - pubout > public . key JS的RSA加密流程 下載最新版本請移步到github:jsencrypt?代碼在目錄BIN下面是否用壓縮的根據情況決定。
生成KEY var keySize = 1024 ; //加密強度 var crypt = new JSEncrypt ({ default_key_size : keySize }); //RSA 操作對象 //方法1 (async) crypt . getKey ( function () { crypt . getPrivateKey (); crypt . getPublicKey (); }); //方法2: crypt . getKey (); crypt . getPrivateKey (); crypt . getPublicKey (); 客戶端加密場景: var crypt1 = new JSEncrypt (); //新建rsa對象 var publickey = '\ -----BEGIN PUBLIC KEY-----\ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3N8LJFqlsa6loCgFpgZVMr/Sx\ DMQY7pr0euNQfh2g+UVPbB0MGhoc7nWL0FQhCgDedbjQw/nGFStFx7W1+0o1oRTY\ u5ebNVivZSobraUv7LJvwT8O66Zs8cxbKLqQ/nE/WwJvXomSIckH6R8iOUO8/QT9\ kv6/L0Uma3qA07pmDQIDAQAB\ -----END PUBLIC KEY-----\ ' ; crypt1 . setPublicKey ( publickey ); //添加來自服務端的publickey crypt1 . encrypt ( 'abc' ); //返回值為加密后的結果 客戶端解密場景: var privatekey = '-----BEGIN RSA PRIVATE KEY-----\ MIICXQIBAAKBgQC3N8LJFqlsa6loCgFpgZVMr/SxDMQY7pr0euNQfh2g+UVPbB0M\ Ghoc7nWL0FQhCgDedbjQw/nGFStFx7W1+0o1oRTYu5ebNVivZSobraUv7LJvwT8O\ 66Zs8cxbKLqQ/nE/WwJvXomSIckH6R8iOUO8/QT9kv6/L0Uma3qA07pmDQIDAQAB\ AoGBAKba3UWModbfZXQeSJLxNCqWw9zJp3ydL/keQQ35DLqgyIJAD2QKEWXvtJUT\ sMo19fyicSGOmFXQyYvPCKkmpLkOMAj1XaNpSMtSrcMx+gC01PO6Ey9rsUxW1g3u\ fpqbEk9E3a5AtCS0I61nbUpRL6rqMtR5o2wcNR3TLtJt7pjxAkEA7hlFJKU1zWGp\ OvvkJDnHc2NOCEJoGjqCR9wwv96+/xAykl2laI6WvEbbhjoO0+8+d17oigjhneS5\ 2UKFcfqw7wJBAMT+MCQ5TYLQlvjrBaDMqOdLsqtaDE6CpkrgwV820QMvHOo3R4Xd\ uSbrA2tOr9t2/x+FzF971lRGdPFIch9UYMMCQQCZtO6SDaWCBP3++gX57OL5dq41\ XsldxU+9nERMWTvr5UUAgDv8F7Dvsr6dFHXmE5i77yUnlzwvdi0UOIF1Z2U5AkBV\ wyRKYPgx34Ya0JcerntKV1Zt60I4XADx0G/feAn/DN/VyENHMISPQPm4GgXN0jy4\ CJQ1bcCd6B65fQTSRvXpAkA2Vv5yXzeKDls/AyxHEoros/VYftVc1HOFC++q13Rw\ NH2rnlRT8FMTFEqL9MYRqvvYAFf5VmH0M1Nx5t4LRN+l\ -----END RSA PRIVATE KEY-----\ ' ; var crypt2 = new JSEncrypt (); //新建加密對象 crypt2 . setPrivateKey ( privatekey ); //給加密對象設置privatekey crypt2 . getPublicKey (); //Tip 我們是不需要存儲publickey的直接用private能得到publickey crypt2 . decrypt ( "MeUqWB5LwTh8crzPqbZtEtKuZxYvPWH9CTCChK1qoBzIgIXGPCdzNMbiH0cCYHl5qWSERIDOgDIgv4dXsIMjEJ5q0cp/qNQYHM5va0iw0UvKvQB1E8aWtY2nFEPy4F+ArQ0Mj/ijr/CntEP1jHKC3WU9nu2kYrBIBnbj14Bs+kI=" ); //調用解密方法 但是雖然寫到了這里,加密方面還是不夠用,因為1024長度的RSA加密最多只能加密長度為117的字符串。而URL長度最多為4k因此這里我們要讓加密長度達到2691以達到能用的程度。 那么這種加密長度大概能容納多少數據呢? 我們借助json-generator來幫忙生成JSON
sdata =[ { "_id" : "542f9ac2359c7d881bc0298e" , "index" : 0 , "guid" : "db1dacc1-b870-4e3c-bc1a-80dfd9506610" , "isActive" : false , "balance" : "$1,570.15" , "picture" : "http://placehold.it/32x32" , "age" : 36 , "eyeColor" : "blue" , "name" : "Effie Barr" , "gender" : "female" , "company" : "ZORK" , "email" : "effiebarr@zork.com" , "phone" : "+1 (802) 574-3379" , "address" : "951 Cortelyou Road, Wikieup, Colorado, 4694" , "about" : "Sunt reprehenderit do laboris velit qui elit duis velit qui. Nostrud sit eiusmod cillum exercitation veniam ad sint irure cupidatat sunt consectetur magna. Amet nisi velit laboris amet officia et velit nisi nostrud ipsum. Cupidatat et fugiat esse minim occaecat cillum enim exercitation laboris velit nisi est enim aute. Enim do pariatur\r\n" , "registered" : "2014-05-08T15:26:35 -08:00" , "latitude" : 48.576424 , "longitude" : 146.634137 , "tags" : [ "esse" , "proident" , "quis" , "consectetur" , "magna" , "tempor" , "anim" ], "friends" : [ { "id" : 0 , "name" : "Trisha Cannon" }, { "id" : 1 , "name" : "Todd Bullock" }, { "id" : 2 , "name" : "Eileen Drake" }, { "id" : 3 , "name" : "Ferrell Kelly" }, { "id" : 4 , "name" : "Fischer Blankenship" }, { "id" : 5 , "name" : "Morales Mann" }, { "id" : 6 , "name" : "Brandie Pittman" }, { "id" : 7 , "name" : "Virgie Kerr" } ], "greeting" : "Hello, Effie Barr! You have 1 unread messages." , "favoriteFruit" : "apple" }, { "_id" : "542f9ac21c260d03e763a4f2" , "index" : 1 , "guid" : "9e3a3d8a-26f8-46b7-aca0-336a194808b1" , "isActive" : true , "balance" : "$3,617.89" , "picture" : "http://placehold.it/32x32" , "age" : 31 , "eyeColor" : "brown" , "name" : "Butler Best" , "gender" : "male" , "company" : "SPORTAN" , "email" : "butlerbest@sportan.com" , "phone" : "+1 (905) 428-3046" , "address" : "798 Joval Court, Wanship, Delaware, 8974" , "about" : "Nostrud occaecat id sunt pariatur ad nisi do veniam sit officia non consequat amet fugiat. Est eiusmod labore ut cillum qui eu elit ut eiusmod exercitation. Ut anim nostrud eiusmod voluptate tempor proident id do pariatur. In Lorem ullamco ea irure adipisicing. Quis est dolor ex commodo aliqua nisi elit sit elit anim fugiat sunt amet. Enim consequat ipsum occaecat ipsum tempor deserunt dolor veniam nostrud. Anim cillum ullamco cupidatat aute velit fugiat sit enim in amet anim mollit dolor eiusmod.\r\n" , "registered" : "2014-08-02T06:15:44 -08:00" , "latitude" : - 20.529765 , "longitude" : 2.396578 , "tags" : [ "consequat" , "enim" , "magna" , "sunt" , "Lorem" , "quis" , "commodo" ], "friends" : [ { "id" : 0 , "name" : "Kenya Rice" }, { "id" : 1 , "name" : "Hale Knowles" }, { "id" : 2 , "name" : "Michael Stephens" }, { "id" : 3 , "name" : "Holder Bailey" }, { "id" : 4 , "name" : "Garner Luna" }, { "id" : 5 , "name" : "Alyce Sawyer" }, { "id" : 6 , "name" : "Rivas Owens" }, { "id" : 7 , "name" : "Jan Petersen" } ], "greeting" : "Hello, Butler Best! You have 8 unread messages." , "favoriteFruit" : "banana" } ] ?
表單json能達到這么長已經是很極端的情況了。因此這種方法絕對是夠用的。
長表單內容加解密方法: function encrypt_data ( publickey , data ) { if ( data . length > 2691 ){ return ;} // length limit var crypt = new JSEncrypt (); crypt . setPublicKey ( publickey ); crypt_res = "" ; for ( var index = 0 ; index < ( data . length - data . length % 117 )/ 117 + 1 ; index ++) { var subdata = data . substr ( index * 117 , 117 ); crypt_res += crypt . encrypt ( subdata ); } return crypt_res ; } function decrypt_data ( privatekey , data ) { var crypt = new JSEncrypt (); crypt . setPrivateKey ( privatekey ); datas = data . split ( '=' ); var decrypt_res = "" ; datas . forEach ( function ( item ) { if ( item != "" ){ de_res += crypt . decrypt ( item );} }); return decrypt_res ; } ?##########NextPage[title=]##########
PHP的RSA加密 php加密解密類 首先要檢查phpinfo里面有沒有openssl支持
class mycrypt { ? public $pubkey ; public $privkey ; ? function __construct () { $this -> pubkey = file_get_contents ( './public.key' ); $this -> privkey = file_get_contents ( './private.key' ); } ? public function encrypt ( $data ) { if ( openssl_public_encrypt ( $data , $encrypted , $this -> pubkey )) $data = base64_encode ( $encrypted ); else throw new Exception ( 'Unable to encrypt data. Perhaps it is bigger than the key size?' ); ? return $data ; } ? public function decrypt ( $data ) { if ( openssl_private_decrypt ( base64_decode ( $data ), $decrypted , $this -> privkey )) $data = $decrypted ; else $data = '' ; ? return $data ; } ? } 密匙文件位置問題,是放到訪問接口的附近就可以了如果是CI的話就放到index.php旁邊就行了。 ?但是要注意一點,一定要做訪問設置,不然key會暴出來的,那時候信息一旦截獲就慘了。
類的使用 $rsa = new mycrypt (); echo $rsa -> encrypt ( 'abc' ); echo $rsa -> decrypt ( 'W+ducpssNJlyp2XYE08wwokHfT0bm87yBz9vviZbfjAGsy/U9Ns9FIed684lWjYyyofi/1YWrU0Mp8vLOYi8l6CfklBY=' ); 長數據加密解密 function encrypt_data ( $publickey , $data ) { $rsa = new mycrypt (); if ( $publickey != "" ){ $rsa -> pubkey = $publickey ; } $crypt_res = "" ; for ( $i = 0 ; $i <(( strlen ( $data ) - strlen ( $data )% 117 )/ 117 + 1 ); $i ++) { $crypt_res = $crypt_res .( $rsa -> encrypt ( mb_strcut ( $data , $i * 117 , 117 , 'utf-8' ))); } return $crypt_res ; } function decrypt_data ( $privatekey , $data ) { $rsa = new mycrypt (); if ( $privatekey != "" ){ // if null use default $rsa -> privkey = $privatekey ; } $decrypt_res = "" ; $datas = explode ( '=' , $data ); foreach ( $datas as $value ) { $decrypt_res = $decrypt_res . $rsa -> decrypt ( $value ); } return $decrypt_res ; } JSONP 跨域通訊 我們經過千辛萬苦經過加密終于能做到通訊安全了。 當然我們的下一步是通過JSONP 的get通訊來實現跨域通訊啦。 經過測試:我們的JS中最長的Case url長度是3956 在加上跨域url callbac參數,經過測試正好差20到4095 (一般的URI長度限制為4K)
$ . ajax ({ type : "get" , async : false , // 設置同步通訊或者異步通訊 url : "http://22500e31b5a12457.sinaapp.com/ubtamat/getPubKey?c=hknHQKIy3dyeeajyAwZ+raUkV1ezFbgU8zk+54cNQtrcEGozUjXpYhbC6fxz2hCOgp9feIsM1xKJFm5pkAGQ2UcUOc5EJNCAz6L0mXkZbTBmh3PufWxOE7TaicqRCRtZGGNB2qpm2WruXjYg1lPcrPz/rhFZx4DSJvEHkCm7ZU0=......(加密后的結果太長,省略)" , dataType : "jsonp" , jsonp : "" , }); ?
header ( "Content-type: application/javascript; charset=utf-8" ); $response = "console.log('test response!')" ; $callback = $this -> input -> GET ( 'callback' ); echo $callback . $response ; PHP代碼是CI框架controler中的部分代碼 并且經過了必要的裁剪。 更加細節的參數都放到GET里面就可以了。 處理之后按照上面的形式處理返回值就ok 如果你配置成功了,你將會在網頁的控制臺上看到自己動態的, 或者像我一樣靜態的控制臺輸出。 如果要是想獲取數據到網頁的話還是要借助回調函數 來實現
JSONP跨域獲取通訊結果 請看下面代碼:
客戶端代碼
var global = null ; function jpc ( result ) { global = result . msg ; } ? $ . ajax ({ type : "get" , async : false , // 設置同步通訊或者異步通訊 url : "http://22500e31b5a12457.sinaapp.com/ubtamat/getPubKey" , dataType : "jsonp" , jsonp : "jpc" , }); 服務器端代碼
header ( "Content-type: application/javascript; charset=utf-8" ); $response = "jpc({'msg':123456})" ; $callback = $this -> input -> GET ( 'callback' ); echo $callback . $response ; 此次通訊的結果會在jcp當中調用執行,并且返回的內容會記錄到 global 變量當中。
實戰 從上文中,我們已經找到了整個加密過程方法了,但是距離實戰還是有一定距離的。 首先我們實戰的話需要克服接口比較少,功能比較多,單個接口維護用時比較長的問題。
為了解決上面的問題我們做出如下設計。
客戶端方面: 設計一個通訊類:只管跟服務器通訊。別的業務什么都不管。
//create connection object. var ConnServ = new Object (); ? ConnServ . tmpResponse = "not initial" ; ? //call back function register slot. ConnServ . CallBackFunction = function (){ console . log ( "call back function set error ! U must set a business call back function!" )}; ? //input only encrypted data!!! //send data to server ConnServ . send = function ( data ) { data = data . replace ( /\+/ g , "$" ); //replace all + as $ $ . ajax ({ type : "get" , async : false , url : "http://22500e317.sinaapp.com/ubtamat?c=" + data , dataType : "jsonp" , jsonp : "jpc" }); return "Send Finish" ; } ? //default call back funcation function jpc ( res ) { ConnServ . tmpResponse = res . msg ; ConnServ . CallBackFunction (); } ? ? //public key store. ConnServ . getpublickey = function () { return "\-" + "----BEGIN PUBLIC KEY----- " + ................................................... "-----END PUBLIC KEY-----" ; } 在上面代碼中請注意,RSA加密過后的字符串當中有一個非法字符+要轉換成其他合法字符發送到服務器才可以。 不然參數會錯誤。 等傳輸到服務器中自己轉換回來在解密就好了。
服務器端方面: 首先我們接收到消息之后要對消息進行解密,之后根據報文內容選擇服務器上的功能。然后把其他參數輸入到業務類中執行即可。 因此我們使用了命令模式來實現單一接口的豐富業務功能。 其他的我們需要對CI框架的配置進行調整: 首先global config里面需要調整 $config['global_xss_filtering'] = FALSE; 因為如果傳輸過來的報文解密不了就直接拋棄不進行處理(防止CC攻擊第一層)這樣就從url上防止了攻擊的可能性。 當然我們還是沒有完全避免注入風險這時我們就需要在業務類里面調用安全模塊:
$this -> security -> xss_clean () 來實現第二層的XSS攻擊。這是服務器端設計主要需要說的位置。
服務器獲取數據處理全過程 從get接口獲得參數c的加密數據 對數據進行RSA解密。 判斷數據包時間戳。如果超時直接拋棄(防止從瀏覽器記錄中直接發送request到服務器,下面是安全方面的說明) 首先如果不修改數據只修改時間戳不可能從截獲的數據報文中實現,因為需要重新加密,如果想得到內容需要服務器上的privatekey解密保證安全 如果數據包截獲直接發送數據包在超時范圍內直接獲取數據包內容,也不能實現攻擊,因為在客戶端有臨時RSA密匙對生成并且在發送的時候會同時發送publickey 給服務器做session的存儲內容并且偽裝客戶的客戶端沒有privatekey所以獲取任何關于登陸之后的消息根本無法解析。 對解密后的數據進行xss檢查 解析報文中需要調用什么功能直接調用反射得到業務類的實例 調度業務類,并且把得到的參數賦值給業務執行函數的參數。 服務器處理數據過程只跟業務有關 服務器返回數據全過程 業務處理完成之后針對每一個用戶的登陸情況對返回值進行加密。 response 以上業務涉及的部分代碼(給出的代碼未涉及以上說的安全部分。) //CI 控制器里面的方法 public function index () { header ( "Content-Type: text/html;charset=UTF-8" ); $callback = $this -> input -> GET ( 'callback' ); $input_data = str_replace ( "$" , "+" , $this -> input -> GET ( 'c' )); $input_data = $this -> rsa -> decrypt_data ( $input_data ); if ( $input_data == "" ){ return ;} //如果數據不對解析就會失敗,直接拋棄數據包,避免cracker構造數據包問題 //這里插入時間戳檢查代碼 //這插入xss檢查 $output_data = command ( $input_data ); $response = "jpc({'msg':" . $output_data . "})" ; $callback = $this -> input -> GET ( 'callback' ); echo $callback . $response ; } ?
//命令模式中的業務調度方法 function command ( $input ) { try { $obj_input = json_decode ( $input ); $action = $obj_input -> { "action" }; $business_action = new ReflectionClass ( $action ); $instance = $business_action -> newInstanceArgs (); $output = $instance -> Action ( $obj_input ); //對output變量進行rsa加密 return "'" . $output . "'" ; // here only accept string } catch ( Exception $e ) { return "'" . $e -> getMessage (). "'" ; } } ?
以下是配合業務進行的工具函數: //命令接口定義 interface ICommand { function Action ( $arg_obj ); } //把此函數放到system/core/common.php //實現了輸入一個文件夾就自動加載所有文件夾中的所有的類。 if ( ! function_exists ( 'require_once_dir' )) { function require_once_dir ( $path ) { $dir_list = scandir ( $path ); foreach ( $dir_list as $file ) { if ( $file != ".." && $file != "." ) { require_once ( $path . "/" . $file ); } } } } ? //使用: //在application/config/autoload.php中添加類似如下代碼: require_once_dir ( APPPATH . "/controllers/lib" ); require_once_dir ( APPPATH . "/controllers/actions" ); 以下是實現業務的例子: class register implements ICommand { public function Action ( $arg_obj ) { return "we are do nothing: " . json_encode ( $arg_obj ); } } 通過以上基本方法,我們可以實現,只要業務繼承我們聲明的接口就可以開始寫業務了。 別的什么都不用管,專注于業務即可,其他的安全、IO等問題都已經一并解決。 并且每一個業務都進行了rsa加密xss攻擊過濾偽造數據包攻擊。 以及在response加密只能是固定客戶端才能看到報文內容的全過程。 但是一定要注意一點,注冊這個業務后面要嵌套登陸進行,不然看不到返回值。
數據包必須包含的要素: acton (業務名) req_time (請求時間) public_key (如果是注冊跟登陸時候需要提交臨時公匙) 總結 因為時間倉促所以只能寫到這里了。 如果您發現了我文章中的bug歡迎發email批評指正。非常感謝! 同時本方案也會成為我們開源社區linux52.com后臺系統中的接口設計方案。 當然我們社區所有維護的文檔都會進行反復驗證,如果出問題我們會及時更新。 以維護文檔的正確性。 點擊=這=里=查看文檔最新版本。
原文發布時間:2014-10-11
本文來自云棲合作伙伴“linux中國”
總結
以上是生活随笔 為你收集整理的PHP-RSA加密跨域通讯实战 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。