首先是前段时间我在公众号里被人批(dui)评(gang)了,大概意思就是:你别老整那ECDH又是椭圆又是素数啥的,你就说这玩意实际项目中怎么用就完了,我们不想听那些,那些我们都懂都精通,而且你还太监了,你自己看看是不是太监了,ECDH写到上一篇明显还没完,结果到现在了还没下文,你自己说是不是太监了,你自己说。
其次是实际上本篇内容实际上和ECDH没有半毛钱关系,通篇都是DH(少了EC两个字母),不过在项目中实际应用的业务逻辑写法、道理都是一样晒儿的。你现在可以暂时认为DH就是ECDH的“ 少了两个字母版本 ”。用DH的最主要原因是啥呢,因为时间有限,我优先写了DH的常用语言库文件,目前可用,ECDH的一根毛都没有写,所以只能用DH演示。
最后是再次强调一遍,作为一篇正经的文章,我需要再次科普一下DH是啥意思。
很多都以为DH是Daemon Hunter(恶魔猎手)的简称,然而并不是。Daemon Hunter是真实名称叫做伊利丹,是个瞎子同时又是法玛丽奥(就是老鹿)的兄dei。他暗恋白虎(就是那种真的白虎)泰兰德,然而泰兰德却嫁给了老鹿,事情大概就是这么一回事。在我们这里DH则是Diffie-Hellman的简称,二位大爷的照片我以前贴过,现在不得不再贴一遍:
上图告诉我们头发长短与职业无关,douyin上那些自以为get到程序员梗的短视频真的是LOWB到一塌糊涂。
在正式开始前之前,我还是要说明一下用DH的初衷是什么或者说这个东西是来解决什么问题的。接着上篇的故事(点击这里)说:
- 你老板说项目非常牛逼,数据要加密,用牛逼的加密算法
- 你就用RSA非对称加密开发测试操作猛如虎
- 然后,一上线:CPU炸了,成绩1-5
- 然后你找老板审批升级服务器费用,老板给了你300块并让你放心花大胆花
- 你首先把RSA下线了,然后偷偷换成了AES对称加密,CPU不炸了
- 然后三百块偷偷放到了自己腰包里
- 但是AES的对称密钥你写死到客户端,被逆向就完了;如果通过服务器下发,听起来更加扯淡
- 想了想,你拿着三百块钱组了个局儿,你带着钱,我带着陈旭,老赵带着柱子,再加上大彪,正好六人局
- 局上我向你透露出一种方案:将AES对称密钥通过非对称方式协商出来。DH这种神奇的算法可以让你服务器和客户端在不传输该对称密钥的情况下就可以通过心有灵犀地方式各自计算出一个对称密钥,而且可以一样,避免了该密钥在网络上流通,而且你可以随意更换,过期时间定为1分钟,可谓是狠毒至极!
我们引入DH就是为了解决上面的问题。然而,DH或ECDH并不能解决中间人攻击问题,这个要搞明白了。
所以,在正式开始之前,我必须先安利我和东北大嫖客还有巨蛀以及阿尼特写的DH库,github链接是这个,下面我将利用这些DH库们进行demo演示。
https://github.com/ti-dh
(明眼人已经看出来我是来骗star的)
目前这个库提供了纯PHP、C实现的PHP扩展、Java版,列个表格吧:
先说下服务端和客户端进行协商地整体流程,非常非常简单:
整个协商流程中,只有第二步和第三步会发生数据交互。第二步是API下发p、g、server-num给客户端;第三步是客户端向API提交client-num数据;最后一步,对称加解密用的key就已经计算出来用于生产环境了。
下面我用世界上最好的语言演示一下如何使用这个鬼东西,客户端我们用什么演示呢?客户端也依然使用世界上最好的语言来演示。首先,你们把上面github里的库文件集成到你们API里,我这里集成完毕后代码如下:
API demo code:
<?php
class DhController extends BaseController{
private $dh = null;
// 将DH库初始化进来呀...
public function init() {
$this->dh = new Dh();
}
// 这就是上图中的第二步:客户端访问这个API获取g p 和 server-num
public function getdhbasedataAction() {
$ret = $this->dh->getdhbasedata();
echo json_encode( $ret );
}
// 这就是上图中的第三步:客户端通过这个api提交client-num参数
public function postdhclientdataAction() {
if ( $this->getRequest()->isPost() ) {
if ( empty( $_POST['client_number'] ) || !is_numeric( $_POST['client_number'] ) ) {
exit( json_encode( array(
'code' => -1,
'message' => 'wrong parameters',
) ) );
}
$ret = $this->dh->postdhclientdata( $_POST );
echo json_encode( array(
'key' => $ret,
) );
}
}
}
Client demo code:
<?php
require __DIR__ . '/vendor/autoload.php';
use \Curl\Curl;
$curl = new Curl();
// 初始化客户端数据,随机一个即可~
$client_number = mt_rand( 100000, 999999 );
// 1、第一步,获取服务器的p、g和server_number
$ret = $curl->get( 'https://xxxx.ooo/dh/getdhbasedata' );
$ret = json_decode( $ret, true );
$p = $ret['p'];
$g = $ret['g'];
$server_number = $ret['server_number'];
// 2、第二步,根据服务器获取到的数据计算出client-number
$process_client_number = gmp_powm( $g, $client_number, $p );
// 3、第三步,将计算过后的client-number发送给服务器
// 那个demo里已经有完美的演示了,多看代码
$ret = $curl->post( 'https://xxxx.ooo/dh/postdhclientdata', array(
'client_number' => gmp_strval( $process_client_number ),
) );
$ret = json_decode( $ret, true );
// 4、第四步,根据server-number,client-number和p 计算出公共密钥K
$key = gmp_powm( $server_number, $client_number, $p );
echo PHP_EOL."DH非对称密钥产生交换:".PHP_EOL;
echo 'client计算出的public key : '.$key.PHP_EOL;
echo 'server计算出的public key : '.$ret['key'].PHP_EOL.PHP_EOL;
客户端文件保存client.php,然后php client.php执行一下,结果你们感受一下:
一样有没有?!计算出来的都一样,有没有?!!
上图中那么一坨长的不能整的让人看了就觉得恶心呕吐的数字就是API和客户端分别计算出来的对称加解密的密钥了,请注意实际使用过程中,服务器千万不要把这个数据返回给客户端,demo里这么做就是为了演示而已,用的时候自己也需要动动脑子的。
然而,事情往往不会说就是这么简单就可以了,如果在生产环境使用,还是需要继续完善一些细节的。
- 第一个问题就是有些想的比较多的宝贝儿们会不同的两个客户端计算出来的key会不会一样?可能性非常非常非常小
- 第二个问题就是一般客户端登陆的用户都有自己的token或uid之类的,API这里在与一个客户端协商出一个key后可以以 “ token:key ” 格式把key存储到redis中,然后给一个有效时间比如30分钟;客户端也将key保存到手机内存中设置一个30分钟有效期。每次使用key进行加解密前都验证一下是否过期,如果过期了就重新走一遍前面的协商流程
我发誓,这是关于DH或ECDH的最后一篇文章了,以后我再也不会写任何与这两个英文缩写相关的东西了,我说都是真的,我保证说到做到。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。