5. handshake 协定
handshake protocol 重要而繁琐。
TLS 1.3 对握手做了大批改,上面先讲 TLS 1.2,讲完再介绍一下剖析 TLS 1.3.
5.1.handshake 的总体流程
handshake protocol 用于产生给 record protocol 应用的 SecurityParameters。
在 handshake 中:
- 客户端和服务器端协商 TLS 协定版本号和一个 CipherSuite,
- 认证对端的身份(可选,个别如 https 是客户端认证服务器端的身份),
- 并且应用密钥协商算法生成共享的 master secret。
步骤如下:
- 替换 Hello 音讯,协商出算法,替换 random 值,查看 session resumption.
- 替换必要的密码学参数,来容许 client 和 server 协商出 premaster secret。
- 替换证书和密码学参数,让 client 和 server 做认证,证实本人的身份。
- 从 premaster secret 和替换的 random 值,生成出 master secret。
- 把 SecerityParameters 提供被 record 层。
- 容许 client 和 server 确认对端得出了雷同的 SecurityParameters,并且握手过程的数据没有被攻击者篡改。
Handshake 的后果是在单方建设雷同的 Session,Session 蕴含下列字段:
- session identifier
session id,用来惟一标识一个 session,在 session 复原的时候,也要用到 - peer certificate
对端的 X509v3 格局证书. 如果不须要认证对端的身份,就为空。 - compression method
压缩算法,个别被禁用 - cipher spec
CipherSuite,如上文介绍,蕴含:用于生成 key 的 pseudorandom function (PRF) , 块加密算法例如 AES, MAC 算法 (例如 HMAC-SHA256). 还包含一个 mac_length 字段,在后文的 we 握手协定介绍 - master secret
48 字节的,client 和 server 共享密钥。 - is resumable
一个标记位,用来标识以后 session 是否能被复原。
以上字段,随后被用于生成 record 层的 SecurityParameters,多个连贯能够通过握手协定的 session 复原性能来复用同一个 session。
握手协定应用 非对称加密 / 密钥协商 / 数字签名 3 类算法,
因而要求读者对这 3 类算法概念清晰,能精确辨别。
在此廓清一下,:
非对称的算法分为 3 类:
,
- 非对称加密,有:RSAES-PKCS1-v1_5,RSAES-OAEP ,Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5 等
- 非对称密钥协商,有:DH,DHE,ECDH,ECDHE 等
- 非对称数字签名:RSASSA-PKCS1-v1_5,RSASSA-PSS,ECDSA,DSA,ED25519 等
另外,非对称加密算法,能够当作密钥协商算法来用,所以 RSAES-PKCS1-v1_5,RSAES-OAEP 也能够当作密钥协商算法来用。
插播一段 RSA:
RSA 的理论工程利用,要遵循 PKCS#1 规范,见 https://www.ietf.org/rfc/rfc3447
其中的 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 是应用 RSA 算法的两种不同 scheme(体制)。
RSAES 示意 RSA Encryption schemes,即非对称加密,
RSAES 有:RSAES-OAEP,RSAES-PKCS1-v1_5 两种,其中 RSAES-OAEP 更新更平安
RSASSA 示意 Signature schemes with appendix,即 appendix 模式 (appendix 和 recovery 的区别请参看密码学教材) 的非对称数字签名算法。
RSASSA 有:RSASSA-PSS, RSASSA-PKCS1-v1_5 两种,其中 RSASSA-PSS 更新更平安
RSA 还有一个缺点,就是很容易被工夫侧通道攻打,所以当初的 RSA 实现都要加 blinding,后文有介绍。
能够看到,RSA 是一种很非凡的算法,既能够当非对称加密算法应用,又能够当非对称数字签名应用。这一点很有迷惑性,其实很多用 RSA 的人都分不清本人用的是 RSA 的哪种模式。
相比之下,ECC(椭圆曲线)这一块的算法就很清晰,ECDSA 只能用作数字签名,ECDH 只能用作密钥替换。
分分明 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 有什么用涅?
PKCS#1 标准解释:
A generally good cryptographic practice is to employ a given RSA
key pair in only one scheme. This avoids the risk that
vulnerability in one scheme may compromise the security of the
other, and may be essential to maintain provable security.
FIPS PUB 186-3 美国标准规定:
An RSA key pair used for digital signatures shall only be used for one
digital signature scheme (e.g., ANS X9.31, RSASSA-PKCS1 v1.5 or
RSASSA-PSS; see Sections 5.4 and 5.5). In addition, an RSA digital
signature key pair shall not be used for other purposes (e.g., key
establishment).
一对密钥只做一个用处,要么用作非对称加解密,要么用作签名验证,别混着用!
一对密钥只做一个用处,要么用作非对称加解密,要么用作签名验证,别混着用!
一对密钥只做一个用处,要么用作非对称加解密,要么用作签名验证,别混着用!
这个要求,决定了一个协定的 PFS(前向安全性),在斯诺登曝光 NSA 的“今日捕捉,明日破解”政策后,越发重要。
https://news.ycombinator.com/…
http://news.netcraft.com/arch…
https://lwn.net/Articles/572926/
https://www.eff.org/deeplinks…
http://www.wired.com/2013/10/…
PFS 反映到密钥协商过程中,就是:
- 不要应用 RSA 做密钥协商,肯定只用 RSA 做数字签名。
- 不要把 ECDH 的公钥固定内置在客户端做密钥协商
后文能够看到这一准则在 TLS 1.3, QUIC,Apple 的 iMessage 等协定中一再贯彻。
非对称 RSA/ECC 这个话题比拟大了,前面有空再写文章吧,读者能够先看一下参考资料,外面有清晰的介绍。
插播完结,持续 TLS。
因为设计的时候,就要思考兼容性,而且理论历史悠久,所以 TLS 协定 90 年代已经应用的一些算法,当初曾经被破解了,例如有的被发现破绽(rc4),有的密钥长度过短(例如已经美帝有进口限度,限度 RSA 在 512 比特以下,对称加密密钥限度 40 比特以下,起初 2005 年限度被勾销),然而思考到兼容,当初的 TLS 实现中,还是蕴含了这种曾经被破解的老算法的代码。这样,如果攻击者能够烦扰握手过程,诱使 client 和 server 应用这种曾经被破解的算法,就会威逼 TLS 协定的平安,这被称为“降级攻打”。
为了在握手协定解决降级攻打的问题,TLS 协定规定:client 发送 ClientHello 音讯,server 必须回复 ServerHello 音讯,否则就是 fatal error,当成连贯失败解决。ClientHello 和 ServerHello 音讯用于建设 client 和 server 之间的平安加强能力,ClientHello 和 ServerHello 音讯建设如下属性:
- Protocol Version
- Session ID
- Cipher Suite
- Compression Method.
另外,产生并替换两个 random 值 ClientHello.random 和 ServerHello.random
密钥协商应用四条:server 的 Certificate,ServerKeyExchange,client 的 Certificate,ClientKeyExchange。TLS 规定当前如果要新增密钥协商办法,能够订制这 4 条音讯的数据格式,并且指定这 4 条音讯的应用办法。密钥协商得出的共享密钥 必须足够长 , 以后定义的密钥协商算法生成的密钥长度必须大于 46 字节。
在 hello 音讯之后,server 会把本人的证书在一条 Certificate 音讯外面发给客户端(如果须要做服务器端认证的话,例如 https)。并且,如果需要的话,server 会发送一条 ServerKeyExchange 音讯,(例如如果服务器的证书只用做签名,不必做密钥替换,或者服务器没有证书)。client 对 server 的认证实现后,server 能够要求 client 发送 client 的证书,如果这是协商进去的 CipherSuite 容许的。下一步,server 会发送 ServerHelloDone 音讯,示意握手的 hello 音讯局部曾经完结。而后 server 会期待一个 client 的响应。如果 server 曾经发过了 CertificateRequest 音讯,client 必须发送 Certificate 音讯。而后发送 ClientKeyExchange 音讯,并且这条音讯的内容取决于 ClientHello 和 ServerHello 音讯协商的算法。如果 client 发送了有签名能力的证书,就显式发送一个通过数字签名的 CertificateVerify 音讯,来证实本人领有证书私钥。
而后,client 发送一个 ChangeCipherSpec 音讯,并且 client 拷贝待定的 Cipher Spec 到以后的 Cipher Spec。而后 client 立刻用新算法 + 新 key+ 新密钥 发送 Finished 音讯。收到后,server 发送本人的 ChangeCipherSpec 音讯,作为响应,并且拷贝待定的 Cipher Spec 到以后的 Cipher Spec。此时,握手就实现了,client 和 server 能够开始替换应用层数据(如下图所示)。应用层数据不得在握手实现前发送。
援用一个来自网络的图片:
Client Server
ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
Figure 1. Message flow for a full handshake
* 示意可选的音讯,或者依据上下文在某些状况下会发送的音讯。Indicates optional or situation-dependent messages that are not
always sent.
注:为了帮忙解决管道阻塞的问题,ChangeCipherSpec 是一个独立的 TLS protocol content type,并不是一个握手音讯。
TLS 的残缺握手过程,要进行 RSA/ECDH/ECDSA 等非对称计算,非对称计算是很慢的。对于非对称的性能:
例如在 2015 年的服务器 cpu:Intel(R) Xeon(R) CPU E3-1230 V2 @ 3.30GHz 上,
应用如下命令测试:
openssl speed rsa2048
openssl speed ecdsap256
openssl speed ecdhp256
openssl speed aes-128-cbc
openssl speed -evp aes-128-cbc
后果如下表:
算法 | 性能 | 性能 |
---|---|---|
RSA-2048 | 私钥运算 723.7 次 / 秒 | 公钥运算 23505.8 次 / 秒 |
256 bit ecdsa (nistp256) | 签名 8628.4 次 / 秒 | 验证 2217.0 次 / 秒 |
256 bit ecdh (nistp256) | ECDH 协商 2807.8 次 / 秒 | |
aes-128-cbc | 加密 121531.39 K/ 秒 | |
aes-128-cbc 应用 aesni 硬件加速 | 加密 683682.13 K/ 秒 |
注:非对称的单位是 次 / 秒,这是因为非对称个别只用于解决一个 block,
对称的单位是 K/ 秒,因为对称个别用于解决大量数据流,所以单位和流量一样。
能够给非对称的 次 / 秒 乘以 block size,就能够和对称做比拟了。例如 rsa-2048,723.7*2048/8/1024=185.2672 K/ 秒,
故 RSA-2048 私钥运算性能 是 aes-128-cbc 的 1.5/1000。是 aesni 的 2.6/10000。
如上,性能数据惨不忍睹,几乎不能忍!!!
有鉴于此,TLS 从设计之初,就采纳了万能伎俩—加 cache,有 2 种 cache 伎俩:session id,和 session ticket。把握手的后果间接 cache 起来,绕过握手运算。
当 client 和 server 决定复原一个之前的 session,或复用一个已有的 session 时(能够不必协商一个新的 SecurityParameters),音讯流程如下:
客户端应用要被复原的 session,发送一个 ClientHello,把 Session ID 蕴含在其中。server 在本人的 session cache 中,查找客户端发来的 Session ID,如果找到,sever 把找到的 session 状态复原到以后连贯,而后发送一个 ServerHello,在 ServerHello 中把 Session ID 带回去。而后,client 和 server 都必须 ChangeCipherSpec 音讯,并紧跟着发送 Finished 音讯。这几步实现后,client 和 server 开始替换应用层数据(如下图所示)。如果 server 在 session cache 中没有找到 Session ID,那 server 就生成一个新的 session ID 在 ServerHello 里给客户端,并且 client 和 server 进行残缺的握手。
流程图如下:
Client Server
ClientHello -------->
ServerHello
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data
Figure 2. Message flow for an abbreviated handshake
5.2. handshake 协定外层构造
从音讯格局来看,TLS Handshake Protocol 在 TLS Record Protocol 的下层. 这个协定用于协商一个 session 的平安参数。Handshake 音讯(例如 ClientHello,ServerHello 等)被包装进 TLSPlaintext 构造外面,传入 TLS record 层,依据以后 session 状态做解决,而后传输。
如下:
enum {hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255)
} HandshakeType;struct {
HandshakeType msg_type; /* handshake type */
uint24 length; /* bytes in message */
select (HandshakeType) { case hello_request: HelloRequest; case client_hello: ClientHello; case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
case session_ticket: NewSessionTicket; /* NEW */
} body;
} Handshake;
TLS 协定规定,handshake 协定的音讯必须依照规定的程序发,收到不按程序来的音讯,当成 fatal error 解决。也就是说,TLS 协定能够当成状态机来建模编码。
上面依照音讯发送必须遵循的程序,一一解释每一条握手音讯。
handshake 协定的外层字段,见这个抓包:
5.3. handshake — ClientHello,ServerHello,HelloRequest
Hello 音讯有 3 个:ClientHello, ServerHello,HellloRequest
一一阐明:
5.3.1 Client Hello
当客户端第一次连贯到服务器时,第一条 message 必须发送 ClientHello。
另外,rfc 里规定,如果客户端和服务器反对重协商,在客户端收到服务器发来的 HelloRequest 后,也能够回一条 ClientHello,在一条曾经建设的连贯上开始重协商。(重协商是个很少用到的个性。)
音讯构造:
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
opaque SessionID<0..32>;
uint8 CipherSuite[2];enum {null(0), (255) } CompressionMethod;struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {case false: struct {}; case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
Random 其中:
gmt_unix_time 是 unix epoch 工夫戳。
random_bytes 是 28 字节的,用密码学平安随机数生成器 生成进去的随机数。
密码学平安的随机数生成,这是个很大的话题,也是一个大陷阱,目前最好的做法就是用 /dev/urandom,或者 openssl 库的 RAND_bytes()
历史上,恰好就在 SSL 的 random_bytes 这个字段,NetScape 浏览器晚期版本被爆出过随机数生成器破绽。
被爆菊的随机数生成器应用 pid + 工夫戳 来初始化一个 seed,并用 MD5(seed)得出后果。
见 http://www.cs.berkeley.edu/~d…,
倡议读者检查一下本人的随机数生成器。
client_version
: 客户端反对的最高版本号。
random
: 客户端生成的 random。
ClientHello.session_id 惟一标识一个 session,用来做 session cache。如果为空,示意不做复用,要求服务器生成新的 session。
session_id 的起源有:
- 之前的协商好的连贯的 session_id
- 以后连贯的 session_id
- 以后也在应用中的另一条连贯的 session_id
其中第三种容许不做从新握手,就同时建设多条独立的平安连贯。这些独立的连贯可能程序创立,也能够同时创立。一个 SessionID 当握手协商的 Finished 音讯实现后,就非法可用了。存活直到太旧被移除,或者 session 关联的某个连贯产生 fatal error。SessionID 的内容由服务器端生成。
注:因为 SessionID 的传输是不加密,不做 MAC 爱护的,服务器不容许把私密信息发在外面,不能允许伪造的 SessionID 在服务器造成平安问题。(握手过程中的数据,整体是受 Finished 音讯的爱护的)
ClientHello.cipher_suites 字段,蕴含了客户端反对的 CipherSuite 的列表,依照客户端心愿的优先级排序,每个 CipherSuite 有 2 个字节,每个 CipherSuite 由:一个密钥替换算法,一个大量数据加密算法 (须要制订 key length 参数),一个 MAC 算法,一个 PRF 形成。服务器会从客户端发过来的列表中抉择一个;如果没有能够承受的抉择,就返回一个 handshake failure 的 alert,并敞开连贯。如果列表蕴含服务器不意识,不反对,或者禁用的 CipherSuite,服务器必须疏忽。
如果 SessionID 不为空,则 cipher_suites 外面起码要蕴含客户端 cache 的 session 外面的那个 CipherSuite
compression_methods,相似地,ClientHello 外面蕴含压缩算法的列表,依照客户端优先级排序。当然,如前介绍,服务器个别禁用 TLS 的压缩。
compression_methods 前面能够跟一组扩大(extensions),extensions 都是可选的,比拟有用的扩大如:SNI, session ticket,ALPN,OCSP 等,后文介绍。
客户端发送了 ClientHello 后,服务器端必须回复 ServerHello 音讯,回复其余音讯都会导致 fatal error 敞开连贯。
5.3.2 Server Hello
当收到客户端发来的 ClientHello 后,失常解决完后,服务器必须回复 ServerHello。
音讯构造:
struct {
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
select (extensions_present) {case false: struct {}; case true:
Extension extensions<0..2^16-1>;
};
} ServerHello;
server_version
: 服务器抉择 ClientHello.client_version 和 服务器反对的版本号 中的最小的。
random
: 服务器生成的 random,必须确保和客户端生成的 random 没有关联。
session_id
: 服务器为本连贯调配的 SessionID。如果 ClientHello.session_id 不为空,服务器会在本人的本地做查找。
- 如果找到了匹配,并且服务器决定复用找到的 session 建设连贯,服务器应该把 ClientHello.session_id 同样的 session id 填入 ServerHello.session_id,这示意复原了一个 session,并且单方会立刻发送 Finished 音讯。
- 否则,回复一个和 ClientHello.random_id 不同的 Serverhello.session_id,来标识新 session。服务器能够回复一个空的 session_id,来通知客户端这个 session 不要 cache,不能复原。
如果一个 session 被复原了,那必须复原成之前协商的 session 外面的 CipherSuite。要留神的是,并不要求服务器肯定要复原 session,服务器能够不做复原。
在实践中,session cache 在服务器端要求 key-value 模式的存储,如果 tls 服务器不止一台的话,就有一个存储怎么共享的问题,要么存储同步到所有 TLS 服务器的内存里,要么专门搞服务来反对存储,并应用 rpc 拜访,
无论如何,都是很麻烦的事件,相比之下,后文要介绍的 session ticket 就简略多了,所以个别优先应用 session ticket。
cipher_suite
: 服务器选定的一个 CipherSuite。如果是复原的 session,那就是 session 里的 CipherSuite。
compression_method
: 跟下面相似。
extensions
: 扩大列表。要留神的是,ServerHello.extensions 必须是 ClientHello.extensions 的子集。
5.3.3 Hello Extensions
The extension 的格局是:
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;enum {signature_algorithms(13), (65535)
} ExtensionType;
其中:
- “extension_type”标识是哪一个扩大类型。
- “extension_data”一坨二进制的 buffer,扩大的数据体,各个扩大本人做解析。
extension_type 只能呈现一次,ExtensionType 之间不指定程序。
extensions 可能在新连贯创立时被发送,也可能在要求 session 复原的时候被发送。所以各个 extension 都须要规定本人再残缺握手和 session 复原状况下的行为。
这些状况比拟琐碎而奥妙,具体案例要具体分析。
5.3.4 Hello Request
服务器任何时候都能够发送 HelloRequest 音讯。
HelloRequest 的意思是,客户端应该开始协商过程。客户端应该在不便的时候发送 ClientHello。服务器不应该在客户端刚创立好连贯后,就发送 HelloRequest,此时应该让客户端发送 ClientHello。
客户端收到这个音讯后,能够间接疏忽这条音讯。
服务器发现客户端没有响应 HelloRequest 后,能够发送 fatal error alert。
音讯构造:
struct {} HelloRequest;
HelloRequest 不蕴含在握手音讯的 hash 计算范畴内。