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

elarity發表於2019-05-09

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

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

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

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

今天我們從一個實際需求作為出發點,比如你是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'
}

然後客戶端執行登陸的虛擬碼如下:

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 ...
} )

伺服器端使用世界上最好的語言來實現的,所以程式碼你會覺得十分眼熟:

<?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到底是個什麼玩意呢?

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

1、元首和古德里安都同時選擇100這個數字,其他人知不知道無所謂
2、元首隨機出了一個數字9,然後將9乘以數字100,得到900,其他人能不能知道無所謂
3、古德里安隨機出了一個數3,然後將3乘以數字100,得到300,其他人能不能知道無所謂
4、元首將900扔給古德里安
5、古德里安將300扔給元首
到這裡後,元首手裡有的資料有100、9、300,古德里安手裡的資料有100、3、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

相關文章