49

其实,前面两篇翻来覆去只为叨逼叨叨逼叨两件事情:

  • 对称加解密,典型算法有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


拉布拉多拉的多
4.4k 声望2.1k 粉丝