首先是前段时间我在公众号里被人批(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 的最后一篇文章了,以后我再也不会写任何与这两个英文缩写相关的东西了,我说都是真的,我保证说到做到。
欢迎来公众号怼我、杠我: