關於PHP加解密之終扯到ECDH了(API安全增強篇三)

其實,前面兩篇翻來覆去只爲叨逼叨叨逼叨兩件事情:php

  • 對稱加解密,典型算法有AES、DES、3DES等等
  • 非對稱加解密,典型的算法有RSA、DSA、ECDH等等

可是,我知道你們最討厭在看這種文章的時候冒出來的一坨「橢圓曲線」、「素數」、「質數」等等這樣的玩意,反正看也看不懂,理解也理解不了,背也背不過,因此我索性就不寫這些玩意,一點兒都不寫,不裝任何逼(然而實際上我背過了,我最近一直在搞線性代數,因此對數學比原來稍微敏感了一些)。git

寫到這裏後,就有刁民、php泥腿子自覺得已經掌握了高科技,隨便從github上扒兩個庫下來跑了跑test就開始四處裝逼,聲稱本身精通對稱加密算法和非對稱加密算法,尤爲是在面試的時候,上去就是跟面試官一頓糊弄,糊弄住了就要5萬,糊弄不了要5千。然而我要告訴你的是,你應該接繼續往下看,這樣的話你在面試的時候,糊弄住了就能夠張口要8萬,糊弄不了也能最低要8千!比原來要5000整整多了3000!並且我提供的這份裝逼指南仍是免費的!github

今天咱們從一個實際需求做爲出發點,好比你是API開發人員(固然了,做爲只有十來我的的小公司,你還得兼職運維,不過工資只按開發算,運維的活兒算是你友情贊助給老闆的),而後老闆兼PM向你提出了一個比較嚴峻的問題,大概意思就是「公司的項目是個很是牛逼的項目,一年後公司是要上市的,你必需要加密了數據,讓BAT和TMD都沒法抄襲咱們!而後你就能買車買房!」,你表示十分承認。因爲你已經看過了我前面兩篇文章,再加上老闆一再強調「咱們這個是牛逼的項目,早晚要上市」,因此你就準備用高安全性的非對稱加密來解決這個問題。面試

具體作法就是服務器生成一對公私鑰,而後再生成一對公私鑰給全部客戶端公用。好比用戶登錄API,接口文檔大概以下:算法

API : https://www.so.com/api/user/login
METHOD : POST
PROTOCOL : 將數據以JSON形式,所有放入到http body體中,key叫作mzip
DATA : {
  'username' => 'xitele',
  'password' => 'qiangdadaoniyongyuancaibuchulaishiduoshao'
}

而後客戶端執行登錄的僞代碼以下:json

var username string = 'xitele'
var password string = md5('123456')
// 將數據生成json
var data = jsonize( hashMap(
  'username' : username,
  'password' : password
) )
// 用服務器公鑰,將數據加密
var encryptData = RSA.encrypt( '服務端的公鑰', data )
// 再次封裝數據爲json
var lastJson = jsonize( hashMap(
  'mzip' => encryptData
) )
// 提交數據
http.post( 'https://www.so.com/api/user/login', lastJson, function() {
  // ... ... do something ...
} )

服務器端使用世界上最好的語言來實現的,因此代碼你會以爲十分眼熟:api

<?php
$jRawData = file_get_contents( 'php://input' );
$aRawData = json_decode( $jRawData, true );
// 使用服務器私鑰,對mzip中的加密數據進行解密
$jDecryptData = RSA::decrypt( '服務器的私鑰', $aRawData['mzip'] );
// 解密後的數據實際上就是 {"username":"xitele","password":"e10adc3949ba59abbe56e057f20f883e "}
$aDecryptData = json_decode( $jDecryptData, true );
// 進入到咱們最熟悉的增刪改查流程!
$pdo = new PDO();
$sth = $pdo->prepare( "select * from user where username=:username" );
$sth->bindParam( ':username', $aDecryptData['username'], PARAM_STR );
$pdo->execute();
$aUser = $pdo->fetch();
if ( $aUser['password'] != $aDecryptData['password'] ) {
  echo json_encode( array(
    'code' => 0,
    'msg' => '登錄成功'
  ) );
}
else {
  echo json_encode( array(
    'code' => 10002,
    'msg' => '登錄失敗'
  ) );
}

上線後,發現倒也沒啥大問題了,就是明顯服務器CPU負載特別高,客戶端也感受有點兒卡。很明顯,非對稱加密的CPU極大的消耗成了一種瓶頸。因而你找老闆申請服務區費用,老闆當場表示很是理解,大手一揮就給你批了300塊錢並表示隨意揮霍,把服務器升級成最牛逼的服務器。安全

固然了,都跟我學習了這麼久了你應該立刻就意味到300塊表明着什麼,300塊頂多表明能組兩個局兒... ...服務器

固然了,API那裏也好交代,全線降級爲AES對稱,CPU瞬間就下來了,又不是不能用.. ...微信

固然了,300塊組個五人局兒應該仍是能夠的,除了你和我,再拉上柱子跟老趙,最後再帶上陳旭,局兒上除了吃飯,就額外討論一下關於這個問題的解決方案。

局兒後,我神神祕祕地告訴你說「這特麼簡單,我給你講,你服務器先隨機生成一個AES對稱加密用的密鑰,而後利用客戶端的RSA公鑰加密後傳給客戶端,客戶端再經過本身的RSA私鑰解密獲得這個AES對稱密鑰,而後再用這個AES對稱密鑰進行後續的加解密便可,而後你能夠給這個AES密鑰設定一個有效期,好比五分鐘,當過時後,就再次利用上面的流程申請新的AES密鑰便可!這樣,不只保證了AES密鑰的安全,還能解決了性能問題!」

鋪墊這麼長,終於能扯出來今天的討論關鍵點了:密鑰協商/交換!這就是咱們今天的核心話題了。

先說下爲何會出現密鑰協商和交換這種玩意,其實就是爲了不密鑰在網絡上的傳輸被劫持致使的安全問題,前兩句話的潛臺詞就是「這個世界上存在着一種即使我不告訴你,你也能知道我想告訴你什麼的心有靈犀解決方案」。

密鑰協商交換通常經常使用的有以下幾種方案:

  • 利用RSA等非對稱加密技術進行交換,也就是300塊的局兒上那個方案
  • 利用專門伺候密鑰交換需求的交換算法,好比DH算法,全稱叫作Diffie-Hellman密鑰交換。Diffie和Hellman分別是兩個大叔的名字(注意,此二位是數學家),是他們合夥搞出來的這個算法,DH算法先於RSA出現。

其中,利用非對稱加密的方案大概就是我前面說的那樣,僞代碼已經展現過了。那麼DH究竟是個什麼玩意呢?

下面咱們玩一個比較簡單的數字遊戲:

一、元首和古德里安都同時選擇100這個數字,其餘人知不知道無所謂
二、元首隨機出了一個數字9,而後將9乘以數字100,獲得900,其餘人能不能知道無所謂
三、古德里安隨機出了一個數3,而後將3乘以數字100,獲得300,其餘人能不能知道無所謂
四、元首將900扔給古德里安
五、古德里安將300扔給元首
到這裏後,元首手裏有的數據有100、九、300,古德里安手裏的數據有100、三、900,而後兩我的此時只須要默默地作下面這一步:
元首:9 * 300 = 2700
古德里安:3 * 900 = 2700
OK了,就2700了

雙方都在僅僅是遠遠地確認了一下眼神,說了一句話(彼此交換300和900),就已經同時獲得2700這個相同的數字。辣麼,2700就是雙方後面進行通訊時候對數據進行加密的密鑰了。一樣,雙方能夠爲這個密鑰算一個過時時間,好比五分鐘後,而後過時後從新協商出一個新的便可!並且,即使有其餘人知道了雙方選擇的是100,也知道了元首給古德里安傳了900,也知道了古德里安給元首穿了300,然而並無什麼卵用,由於他仍是不知道對方最終使用的密鑰(也就是2700)是多少。

固然了,現實中真正的DH算法選擇公共數字、隨機數字可不是這麼簡單的,並且雙方最終計算這個密鑰的時候也不會像上面那個例子中那麼輕鬆簡單作一下乘法而已。

具體人家怎麼算得,我就不寫了,反正網上處處都有,並且不管我寫出來仍是不寫出來,反正大家都不看,畢竟,這玩意是數學家要搞的玩意。

RSA的庫前面我從github上扒過,也test過了,因此RSA就不演示了。然而,DH的我們一塊兒從github上扒一個下來玩玩來驗證一下咱們剛纔講的簡單理論。

PHP的一個DH庫,GITHUB連接:https://github.com/jcink/diff...

<?php
require_once 'diffie-hellman.php';
$dh = new DiffieHellman();

將上述代碼保存爲index.php,而後php index.php 32執行一下,結果以下,大家感覺一下:

咱們看到這個庫順帶打印了一坨log,做爲歷來不研究底層的廣大泥腿子來講,咱們只須要關注最後一行「Shared Key : 101451040」,這個就是服務端和客戶端協商出來的密鑰了,也就是意味着後面API的通訊過程當中使用101451040對數據加解密便可。

好了,以上是DH算法。其實,圈裏那些仁兄在看到今天標題中含有DH的時候內心就應該有數了,這傻逼每天在微信羣裏安利ECDH,今兒特麼終於看到DH兩個字母了,總算有點兒眉毛了。辣麼,我每天在羣裏安利的ECDH究竟是什麼玩意。

具體原理怎麼回事,反正我此次是真是連背都背不過了,不過,你能夠簡單認爲ECDH是DH的升級版本,畢竟多了兩個字母。其實ECDH是ECC算法和DH算法二合一體,媽蛋,又特麼冒出來一個ECC,好了好了,就當我沒說。

而後仍是老套路,咱們從github上扒一個庫下來簡單跑一下test,這樣之後就能夠出去裝逼要8萬工資了,傳送門:https://github.com/Querdos/EC...

<?php
require_once './autoloader.php';
use Querdos\lib\ECDHCurve25519;

$xitele   = new ECDHCurve25519();
$gudelian = new ECDHCurve25519();

$xitele->computeSecret( $gudelian->getPublic() );
$gudelian->computeSecret( $xitele->getPublic() );

// shareKey1 和 shareKey2 就是協商出來的密鑰
$shareKey1 = $xitele->getSecret();
echo $shareKey1.PHP_EOL;
$shareKey2 = $gudelian->getSecret();
echo $shareKey2.PHP_EOL;

// 咱們用gmp cmp來對比是否爲同一個密鑰
if ( 0 == gmp_cmp( $shareKey1, $shareKey2 ) ) {
  echo "同樣".PHP_EOL;
}
else {
  echo "不同".PHP_EOL;
}

// 除此以外,這個ecdh庫比dh那個庫多了一個驗證數據簽名驗證,能夠檢驗數據是否被篡改!
$msg = "hello world";
$signature = $xitele->signMessage( $msg );
if ( $gudelian->verifySignature( $signature, $xitele->getPublic(), $msg ) ) {
  echo "驗證數據簽名成功".PHP_EOL;
}
else {
  echo "驗證數據簽名失敗".PHP_EOL;
}
exit;

將代碼保存爲index.php,而後php index.php執行結果以下圖所示:

經過上面代碼咱們能夠看出來,能夠直接背誦一個結論,就是DH和ECDH均可以實現密鑰協商交換,可是ECDH還能夠對數據進行簽名,另外一方能夠對數據進行驗籤,從而能夠判斷出數據在傳輸過程當中是否被篡改!

好了,念念已久的ECDH終於入講了!之後我不會再在羣裏再叨叨這個了,祝大家幸福。

最近開了一個微信公衆號:高性能API社區,全部文章都先發這裏

vx_service_qrcode.jpg

相關文章
相關標籤/搜索