4

安全协议的设计

 3 years ago
source link: http://blog.shell909090.org/blog/archives/2873/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

设计安全协议前,请先思考一个问题,设计安全协议的目地是什么,为什么需要一个独立的安全协议。如果经过分析,你的答案是,需要一个全功能的安全协议。请转回头去,使用TLS。设计安全协议前,请务必问过自己上面这个问题,并在过程中牢记于心。在设计过程中,经常会有的一种现象是,觉得协议已经实现了自己所需的基本功能,跳一下就能实现一些更酷的功能。这时候,请退出来,再看一遍最上面的话。如果TLS已经变得更适合的话,你前面的工作就是沉没成本,不值一提。因此,请在你自己的协议里专注于解决你自己的问题。

安全协议的基础构成

安全协议最小有两个目标,安全性和可靠性。安全性是说数据不会泄漏,可靠性是说数据不可更改。如果只考虑安全性,最简单的实现是对数据流进行加密。再考虑可靠性的话,可能需要内容验证算法。这是一个安全协议的最简构成。脱离这个构成的话,协议就很难讲是一个“安全协议”。

加密算法包括两部分,算法和模式。我们先说算法,再说模式。

算法可选的其实不多。主流的算法中,DES/3DES已经实际废弃,而且跑的比AES还慢。实际可以选的只有AES(128/192/256)和chacha20/xchacha20两种。建议都支持,不会太麻烦的。一种适用于CPU支持AESNI的场合,另一种相反。

再说模式。对流进行加密的最简单办法就是流式加密算法,最出名的就是CFB/OFB/CTR这三个。

不幸的是,流式算法没有验证,对篡改的抵抗力比较差,容易受到KPA/CCA/CPA这类攻击的影响。如果只是用于简单加密场合还凑合,对于比较严肃的场合来说就不是很严肃了。

然后有什么选项?CCM模式很出名。但从密码学建议来说,AEAD应该是当今首选。

AEAD要注意一个问题。道理上说,每次使用的nonce都需要不同。因此如何设计nonce就是个很有技巧的事。这篇文章简介了TLS各个版本的nonce方案。对TCP来说,TLS1.3的方案是最合适的。TCP不会发生乱序,因此序号是可推测的。对UDP来说,只能选择TLS1.2的方案。注意如果选择发送nonce的话,4字节的空间只有2^32。按照1000Mbps,一个报文1K算一下。只需要9.5小时就会发生回转。因此序列增长的nonce长度不能小于8字节。随机nonce来说,12bytes的nonce只能取2^32次,大致保证不发生碰撞。所以随机nonce应该至少24bytes。(也就只有xchacha20可选了)

速度先不论,加密协议本身最好不要压缩。

如果在加密前压缩,选择内容就可能影响压缩结果或压缩速度,造成侧信道攻击。如果在加密后压缩,原则上就应该没有压缩空间了。如果一个加密算法在完成加密后还能大幅压缩,说明这个算法大概率出了问题。因此,最好不要在协议内设计压缩。

AEAD+record的设计有个比较麻烦的问题。AEAD的nonce是暴露在外的。使用随机nonce还好说。如果使用序列,那么nonce就成为一个明显的record分隔提示。如果头部中又明文包含了长度,这个问题会更加明显。

要对此进行防护,最简单的办法就是对头部再来一个AES128。16bytes足够覆盖8bytes头部+8bytes nonce。这个方法挫了点,但是好在这个AES不用做安全防御,只要保证头部无特征就好。

shared secret

以上算法的诸多参数中,有一些是可以通过发送协商的,另一些则不能。例如加密算法的key,是不能通过网络发送的。iv一般也不行。nonce可以通过网络发送,但建议双方对通过网络传输的nonce进行再处理(例如xor上相同的数据)。这些不能通过网络发送的内容,就叫做shared secret。而如何解决shared secret,就是安全协议的重头戏。

最简单的方法叫PSK(Pre-Shared Key)。通过预设的Key来通讯。这个方法的好处是简单,坏处是不保证前向安全性。所谓前向安全性,是说,如果攻击者保留了你的通讯数据,在未来key泄露的情况下,是否能解出你当时的通讯数据?很显然,PSK一旦获得key,就可以解开过去所有的通讯。

为了保证通讯安全,建议所有shared secret都现场协商解决。这类协商算法,叫做Key eXchange算法,简称Kx。

Kx类算法实际上主要就两类,DH和ECDH。我偏好ECDH,长度比较短一点。其中偏好25519这条曲线,不解释。

ECDH获得的secret一般不长,25519为例,255bits。上面secrets最长可以吃掉60bytes数据,因此有两类方法。握手加宽和KDF。

所谓握手加宽,是将握手数据的宽度加倍,就如同在一个过程里同时做两个独立的Kx算法。最后得到的有效secrets长度就是翻倍的。

对于大多数情况来说,握手加宽没有必要(尽管不增加RTT,只增加报文长度)。256bits高质量随机数已经是个无法枚举的强度,用KDF展开就行。KDF保证即便一个key泄漏,也不会导致所有key一起出问题。当然,KDF的另一个目标——保护原始seed,对我们来说意义不大。因为我们的原始seed是握手结果,是临时的。

另外,KFD有两大类。hkdf和pbkdf2。对于seed质量足够高的情况来说,hkdf已经够用。pbkdf2多次循环,除了增加数据离散度外,另一重目标是减慢从seed到key的速度,使得穷举类攻击的成本变高。这个更针对人类输入的密码,对我们意义不大。

握手一个绕不开的问题就是MITM防御。MITM防御的基本思路是认证。常见有这么几种方案。

  1. 双方share key,发送方使用psk对kx请求数据做HMAC签署。
  2. 一方有多个用户名/key,另一方拥有一个用户名/key。基本过程同1,请求时带上用户名。
  3. 一方拥有另一方公钥。私钥签署,公钥认证。

对于服务器来说,1是不能选择的。服务器不能让所有客户共享一样的shared key。原因很简单。你也许能保证自己不泄漏PSK(也许这点都很难保证)。但是你一定不能保证所有其他用户都不会泄漏PSK。一旦PSK泄漏,攻击者就可以对所有用户实施MITM。

2的话,做客户端验证还可以,做服务器验证就比较罗嗦了。道理上说,双向的PSK最好不同。所以服务器和客户端同时选用方案2会造成一个用户两个密码。因此我会选择服务器用3做验证,客户端用2做验证。而非对称签署算法要快速安全密钥短的话,ECDSA是首选。ECDSA pubkey,在TLS中能够预植入CA。在我们自己的体系里,只能PSK。但是这个PSK是不怕泄漏的。

当然,客户端认证并不一定需要在安全协议里做。如果保持协议的精简性的话,这部分可以留给上层协议。届时可以使用challenge-response或time-sign做。不过在上层协议没有内置考虑的情况下,客户端认证会多出一个RTT来。所以这点请自行权衡。

握手报文也有一定格式,因此也需要做隐藏。当然,靠谱的方法同样是AEAD。但是这次AEAD无法用AES来覆盖头部,因为反而可能加速暴露PSK。因此这里我们选择发送全部nonce。因此,如果我们选用12bytes的nonce,照理说我们只能握手2^32次。虽然说握手这么多次也是挺不容易的,但我们没必要折腾自己。选用xchacha20-ploy1305的话,可以使用24bytes的nonce。照理说是永不碰撞的。

另一个小技巧是,可以用上面的ECDSA pubkey作为PSK。首先,这个值双方一定都有。其次,足够随机。第三,重要性不高。即使被攻击出来,最多使得协议可被侦测,还是不能发起MITM解出协议内容。

一定不要做的事

比起怎么做,一定不要做更重要。

  1. 协议兼容,算法协商。这个对大规模部署是个必需品,但是对我们来说一定不能做。如果你已经在折腾这俩了,请再看本文第一段,然后考虑要不要用回TLS。
  2. 可插拔机制。即上面的那堆AEAD啦,KDF啦,Kx啦,可更换。实际上这个功能没啥用,最多内容的AEAD算法可更换,用来适配CPU。其余算法都是没得选的。Kx算法想短,只能用ECC类。再扔掉NIST的几个曲线,基本只有X25519。ECC签署同样也只有ed25519可用。HMAC靠谱又短的只有HMAC-SHA256和HMAC-SHA512-256。安全性和速度都是后者好,直接选后者就行。Kx的AEAD只有xchacha20可选,其余都有nonce碰撞的困扰。record隐藏头部的block算法,实际上只有AES可选。chacha20依赖nonce,又会绕回来。DES/3DES又不靠谱。这里千万别看TLS有上百种算法可以选,眼馋,觉得跳一下能够上。跳了,就不如用回TLS了。
  3. 签署链。同样,对大规模部署是个必需品,对我们来说一定不能碰。你自己想想,你部署服务的时候用过中间证书么?不都是ca直接签客户证书完事了。没OU,中间证书就用不到。更进一步说,没有同中心授权的多服务器,连ca都用不到。因为上面非对称签署只出现在服务器端认证这里。你自己装1,2,3,4,5几个机器。要么相信都不会漏,大家用同一个prikey。漏了大家一起死。要么相信隔离,然后给客户端的时候需要服务器1,给个域名给个pubkey。5个服务器5个pubkey。这俩都能接受,犯不着非搞个ca,然后给1,2,3,4,5一个个做sign。反正都是你自己,怎么?ca就不会漏么?如果你非要搞ca,不如跳回本文第一段再看一遍。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK