HTTPS权威指南
第1章 SSL、TLS和密码学¶
TLS主要目标:
- 加密安全
- 互操作性
- 可扩展性
- 效率
为了避免伪装攻击,TLS和SSL依赖另一个技术:PKI(public key infrastructure)
OSI模型:
层号 | OSI层 | 描述 | 协议示例 |
---|---|---|---|
7 | 应用层 | 应用数据 | HTTP、SMTP、IMAP |
6 | 表示层 | 数据表示、转换和加密 | SSL/TLS |
5 | 会话层 | 多连接管理 | - |
4 | 传输层 | 包或流的可靠传输 | TCP、UDP |
3 | 网络层 | 网络节点间的路由与数据分发 | IP、IPSec |
2 | 数据链路层 | 可靠的本地数据连接(LAN) | 以太网 |
1 | 物理层 | 直接物理数据连接(线缆) | CAT5 |
-
注意:
现实中的协议并非总能与OSI模型完全对应。比如SPDY和HTTP/2因为要对连接进行管理,所以被归入会话层协议,但他们却在数据加密以后生效。第5层及更高层的划分经常是模糊的。
SSL协议早期历史:建议阅读《SSL and TLS: Designing and Building Secure Systems》 - Eric Resorla
TLS工作组:https://datatracker.ietf.org/wg/tls/documents
密码学¶
部署密码是为了解决三个核心问题:
- 机密性
- 真实性
- 完整性
密码学书籍推荐:《深入浅出密码学》
构建基块¶
加密基元(cryptographic primitive):
- 密码学的单位,每个基元完成一个特定的功能。
- 可以将它们组合成scheme和protocol
讨论加密时,经常用到的三个属性:明文(plaintext)、密钥(cipher)和密文(ciphertext)。
对称加密(symmetric encryption)又称私钥加密(private-key cryptography):
- 序列密码(stream cipher)
- 将1字节的plaintext加密输出1字节的ciphertext
-
RC4加密
- RC4 Wiki介绍
- 序列密码觉不能第二次使用相同的密钥,否则会被攻击者揭秘后续部分密文。
- 分组密码(block cipher)
- 每次加密一整块数据。
- 只能加密长度等于加密块大小的数据
- 对于相同输入,输出也是相同的。
- 实践中,人们通过称为分组密码模式(block cipher mode)的加密方案来使用分组密码。
- 分组密码模式可以作为其他加密基元的基础来使用,比如:散列函数、MAC、PRNG、stream cipher。
- 世界上流程的分组密码:AES(advanced encryption standard)
- 填充(padding)
- 分组密码的挑战之一是:处理长度小于加密块大小的数据加密。因此就需要padding了。
- 加密块的最后1字节包含填充长度,填充的每字节都设置称与填充长度字节相同的值。
- 解密后检查最后1字节获取填充长度后删除该自己,然后删除相应的填充字节,同时检查他们是否具有相同的值。
TLS填充示例
- RC4 Wiki介绍
散列函数(hash function):
- 将任意长度的输入转化为定长输出的算法
- 密码学散列函数有以下额外特性:
- 抗原象性
- 给定一个散列,计算机上无法找到或则构造出生成它的消息。
- 抗第二原象性(弱抗碰撞性)
- 给定一条消息和它的散列,计算上无法找到一条不同的消息具有相同的散列
- 强抗碰撞性
- 计算上无法找到两条散列相同的消息
- 抗原象性
- 别名:指纹、消息摘要、摘要
- 生日悖论:散列函数的强度最多只是散列长度的一半
MAC(message authetication code):
- 散列函数可以用于验证数据完整性,但仅在数据的散列与数据本身分开传输的条件下如此。否则攻击者可以同时修改数据和散列,从而轻易的地避开检测。
- 任何散列函数都可以作为MAC的基础,另一个基础是给予散列的消息验证代码(hash-based message antuetication code,HMAC)
分组密码模式:
- 分组密码模式是为了加密任意长度的数据而设计的密码学方案,是针对分组密码的扩展。
- 输出模式通常以首字母来引用:ECB、CBC、CFB、OFB、CTR、GCM等。
- CBC是目前SSL和TLS的主要模式
- GCm是TLS中相对较新的模式,从1.2版本开始使用,它提供了机密性和完整性,是当前可用的最好模式。
- 电码本模式(electronic codebook,ECB)
- 最简单的分组密码模式
- 只支持数据长度正好是块大小的整数倍,不满足的话得事先填充
- 劣势:分组密码是确定的(输入相同、输出也相同)
- 加密块链接模式(cipher block chaining,CBC)
非对称加密(asymmetricencryption):
- 非对称加密又称公钥加密(public key cryptography)
- 一对可互相加解密的密钥,公钥公布,私钥储存。
- 加密缓慢,因此不适用于数据量大的场景,因此往往被部署于进行身份验证和共享秘密的协商,然后后续由快速的对称加密进行。
数字签名(digital signature):
- DS是一个密码学方案,可以验证文档的真实性。
- RSA数字签名
- 签名过程:
- 计算希望签名的文档的散列
- 对结果散列和一些额外的元数据进行编码。
- 使用私钥加密编码过的数据,其结果就是签名,可以追加到文档中作为身份验证的依据。
- 验签:
- 接收文档并使用相同的散列算法独立计算文档散列,
- 用公钥对消息解密,将散列解码出来后确认使用的散列算法是否正确,解密出的散列是否与本地计算的相同。
- 注意: > 并非所有的数字签名算法都与RSA的工作方式一致。事实上,RSA是一个 特例 ,因为它可以同时用于加密和数字签名,其他流程的公钥密码算法则不能用于加密,比如DSA和ECDSA,它们依赖其他方式进行签名。
- 签名过程:
- 随机数生成
- 真随机数生成器(true random number generator,TRNG):不足够可靠,系统的熵可能不够。
- 伪随机数生成器(pseudorandom number generator,PRNG):只是看起来随机,常用于编程,不适用于密码学。
- 加密安全伪随机数生成器(cryptographically secure pseudorandom number generator,CPRNG):是不可预测的PRNG。
其他¶
- 我们通常会说加密被绕过,而不是攻击。因为使用的基元都很坚实,但软件体系不牢固。
- 避免方法:
- 使用完善的协议,不要自己设计
- 使用高级库,避免直接操作加密
- 使用完备的基元,辅以足够强壮的密钥长度
- 避免方法:
- 密码强度
- 对称密码系统的强度按位数增加以几何基数增长,这意味着密码增加1位,强度翻一倍。
- 关于密钥和算法强度的报告
- RSA密钥交换不支持前向保密(forward secrecy),其他密钥密钥交换算法不存在此问题。
- 被动攻击
- 主动攻击
- MITM(中间人攻击)就是主动攻击的一种。
- 主动攻击的攻击力非常强大,但更难扩展。往往用于攻击高价值的个人目标。
第2章 协议¶
- TLS 1.2:RFC 5246
- TLS工作组文档页:https://datatracker.ietf.org/wg/tls/documents
- TLS工作组邮件列表:http://www.ietf.org/mail-archive/web/tls/current/
记录协议¶
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
enum {
change_cipher_spec (20),
alert (21),
handshake (22),
application_data (23)
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 length; /* 最大长度为 2^14 (16 384) 字节 */
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
-
除了这些可见的字段,还会给每一个TLS记录指定唯一的64位序列号,但不会再线路上传输。
-
消息传输 记录协议传输由其他协议层提交给它的不透明数据缓冲区。若缓冲区超过记录的长度限制,记录协议会将其切分成片段。反之亦然。
- 加密以及完整性验证 在一个刚建立起来的连接,最初的消息传输没有收到任何保护。一旦握手完成,记录层就开始按照协商取得的连接参数进行加密和完整性验证。
- 压缩 多余且不完全,不再使用
- 扩展性 记录协议只关注数据传输和加密,而将所有其他特性转交给子协议,因此可以很方便的添加子协议,扩展性好。
- TLS规定了4个核心子协议:
- 握手协议(handshake protocol)
- 密钥规格变更协议(change cipher spec protocol)
- 应用数据协议(application data protocol)
- 警报协议(alert protocol)
握手协议¶
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;
} body;
} Handshake;
概要¶
会话的加密参量由TLS握手协议产生,它工作在TLS记录层之上。当TLS客户单和服务器首次开始通信时,他们商定协议版本,选择加密算法和可选的认证,并用公钥技术去产生一个共享密钥。
TLS握手协议主要涉及以下4个步骤:
- 交换hello消息去商定算法,交换随机值和检查是否恢复会话。
- 交换必要的加密参量去允许客户端和服务器去协商预主密钥。
- 交换证书和秘密信息去允许客户端和服务器去认证他们自己。
- 用预主密钥和交换的随机值来产生一个主密钥。
- 提供安全参量到记录层
- 允许客户端和服务器核实他们彼此计算的安全参量是否相同,并且此次握手过程没有被攻击者干预。
注意:高层不应该过度依赖于TLS总能在端点间建立安全可靠的连接。比如中间人攻击(MITM)可以尝试让两端降低安全参数到他们可以攻破的值。虽然协议已经考虑到该情况且使该风险尽可能的降低,但它仍旧是可能被攻击的。例如攻击者可能阻塞服务端口,或者尝试让对端去协商一个无需认证的连接。
一个基本的原则是高层必须清楚的知道自己要的是什么,并且绝不在不足够安全的通道上传输他们期望安全的数据。
在任何一个套件承若的安全级别下,可以认为TLS协议是安全的。
通过TLS握手协议可以达到安全目标,整个握手过程可以如下概括:
- 客户端发送一个 ClientHello 消息到服务器,服务器必须响应一个 ServerHello 消息,或者返回一个致命错误并关闭连接。ClientHello 和 ServerHello 被用于在客户端和服务器间建立一个安全连接。 ClientHello 和 ServerHello 将构建以下属性:协议版本(Protocol Version)、会话ID(Session ID)、加密套件(Cipher Suite)和压缩算法(Compression Method)。另外还有两个随机值产生并被交换:ClientHello.random和ServerHello.random。
- 当前的密钥交换最多使用以下4个消息:
- the server Certificate
- the ServerKeyExchange
- the client Certificate
- the ClientKeyExchange
- 客户端和服务器通过此来商定一个共享密钥,该密钥必须足够长,当前定义的密钥交换方法交换密钥的长度为46字节以上。
- 当前的密钥交换最多使用以下4个消息:
- 在Hello消息完成后,如果需要被认证的话,服务器将通过 Certificate 发送它自己的证书。接着如果需要的话(如果服务器没有证书,或者它的证书只是用来签名),服务器还会发送一个 ServerKeyExchange 消息。如果服务器通过认证后,它可以通过 CertificateRequest 请求客户端发送证书过来。接着服务器将发送 ServerHelloDone 消息去指示Hello阶段已经完成。接着服务器将等待客户端的响应。
- 如果服务器发送了 CertificateRequest 消息,那么客户端必须发送 Certificate 消息。接着会立刻发送 ClientKeyExchange 消息,该消息的具体内容依赖于在hello消息中商定的公钥算法。如果客户端发送了一个可签名的证书,那么还需要发送被签名的 CertificateVerify 消息去表明这个客户端证书的真实性。
- 此时一个 ChangeCipherSpec 消息被客户端发送,同时客户端会应用新的加密到记录层中。接着客户端会在新的加密背景下发送 Finished 消息。而服务器将发送自己的 ChangeCipherSpec 消息作为应答,同时也应用新的加密到记录层中,并在新的加密背景下发送 Finished 消息。此时握手已经完成,客户端可以开始交换彼此的应用数据。
会话复用:
注意事项
If the session_id field is not empty (implying a session resumption request), this vector MUST include at least the cipher_suite from that session.
Alerts MUST now be sent in many cases.
- 客户端发送一个携带Session ID的 ClientHello 消息,用于恢复会话。
- 服务器用Session ID去和会话缓存匹配
- 如果匹配成功,服务器会响应一个 ServerHello 消息,该消息携带相同的Session ID。
- 此时客户端和服务器都必须发送 ChangeCipherSpec 以及 Finished 消息。
- 此后,客户端和服务器就可以开始交换应用数据了。
ClientHello¶
发送ClientHello
的时机:
- 客户端首次连接到服务器时,客户端发送的第一个消息。
- 响应服务器的HelloRequest消息时发送。
- 在一个已建立的SSL连接中客户端期望主动发起重新协商安全参量时发送。
数据结构如下所示:
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
opaque SessionID<0..32>;
uint8 CipherSuite[2]; /* Cryptographic suite selector */
enum { null(0), (255) } CompressionMethod;
struct {
ProtocolVersion client_version;
Random random;
// uint8 session_id_len;
SessionID session_id;
// uint16 cipher_suites_len;
CipherSuite cipher_suites<2..2^16-2>;
// uint8 compression_methods_len;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
// uint16 extensions_len;
Extension extensions<0..2^16-1>;
};
} ClientHello;
- Client_version:客户端支持的最佳TLS协议版本,通常是支持的最高版本
- 服务器收到后,如果没问题会在"ServerHello"消息中包含期望的版本(可能低于Client_version)
- 如果服务器不支持客户端版本,必须返回一个"protocol_version"Alert消息
- 如果客户端收到"ServerHello"中的版本,发现并不支持时,必须发送一个"protocol_version"Alert消息给服务器
- random:客户端的随机值结构
- gmt_unix_time:该时间无需精准
- random_bytes:28字节随机值
- session_id:如果客户端想要恢复会话(既恢复客户端与服务器间的安全参量)时,携带此数据。该值由服务器产生,通过"ServerHello"消息返回到客户端,一直存储在服务器上直到老化或者出现错误。
-
cipher_suites:这是客户端支持的套件列表,其还指示了客户端的套件偏好(第一个是最喜好的,以此类推从上至下偏好降低)。
-
TLS初始套件: TLS_NULL_WITH_NULL_NULL 是一个SSL连接的初始状态(还未协商加密套件)
// 初始套件 CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 }; // 服务器需要提供一个可签名的RSA证书 CipherSuite TLS_RSA_WITH_NULL_MD5 = { 0x00,0x01 }; CipherSuite TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 }; CipherSuite TLS_RSA_WITH_NULL_SHA256 = { 0x00,0x3B }; CipherSuite TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 }; CipherSuite TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 }; CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A }; CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x2F }; CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x35 }; CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x3C }; CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x3D }; /* DH证书提供DH参量 * DH:服务器证书需要有一个被CA签署的DH参量 * DHE:服务器证书需要有一个被可签名的证书签署的DH参量,且签名证书需要被CA签发 */ CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D }; CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 }; CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 }; CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 }; CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA = { 0x00,0x30 }; CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x31 }; CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA = { 0x00,0x32 }; CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA = { 0x00,0x33 }; CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA = { 0x00,0x36 }; CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x37 }; CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA = { 0x00,0x38 }; CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA = { 0x00,0x39 }; CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = { 0x00,0x3E }; CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x3F }; CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = { 0x00,0x40 }; CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = { 0x00,0x67 }; CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = { 0x00,0x68 }; CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x69 }; CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = { 0x00,0x6A }; CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = { 0x00,0x6B }; // 匿名DH密钥交换:不安全的,TLSv1.2已经不再使用 CipherSuite TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 }; CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B }; CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA = { 0x00,0x34 }; CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA = { 0x00,0x3A }; CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA256 = { 0x00,0x6C }; CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA256 = { 0x00,0x6D };
-
-
compression_methods:支持的压缩算法,默认是null。
- extensions:扩展信息
Hello Request¶
服务器可以在任何时候发送该消息。
它是一个简单的通知消息,用来告知客户端重新协商安全参量。客户端收到该消息后,必须在合适的时候发送一个新的 ClientHello 消息。
在连接建立期间,服务器不应该发送 Hello Request 消息,即使发送了客户端也会忽略。如果在连接建立期间客户端想要重新协商安全参量,就会重新发送一个新的 ClientHello 消息。
当然如果客户端并不想重新协商,那么它可以简单的忽略该消息,或则发送一个no_renegotiation
的警报消息给服务器。
服务器在发送了 Hello Request 后,直到后续的握手消息完成之前不应该重复的发送该请求。如果服务器发送完没有收到任何响应,可以发送 fatal alert 去关闭连接。
数据结构如下所示:
注意
握手消息计算hash的时候不应该包含它,这里涉及的有 Finished 和 Certificate Verify 消息。
ServerHello¶
-
ServerHello消息的意义是将服务器选择的连接参数传送给客户端,与 ClientHello 类似,只是每个字段只包含 一个选项 。
-
数据结构如下所示:
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:
- 可能的版本值范围:客户端建议的最低版本 - 服务器支持的最高版本
- random:该值由服务器产生,且必须利用 ClientHello.random 来独立生成。
- session_id:该值是SSL会话的唯一标识。
- 如果 ClientHello.session_id 是非空的,那么服务器将在缓存中匹配该Session ID,如果 匹配成功 ,则服务器便用该会话的安全参量去响应客户端,来以此恢复TLS会话。
- 如果 匹配失败 ,服务器将返回一个新的Session ID给客户端,来建立一个全新的TLS连接。
- 服务器也可以直接返回一个空的Session ID,来表明该会话将不被缓存,既不会被用来恢复。
- Cipher_suite:从 ClientHello.cipher_suites 中选择的加密套件。
- compression_method:从 ClientHello.compression_methods 中选择的压缩算法。
- extensions:扩展列表,该表只能是在客户端提供列表中存在过的。
- 该结构可查看:扩展结构
- Signature Algorithms:
- server_version:
Server Certificate¶
- 如果服务器选择了一个需要做认证的密钥交换方法,则必须发送该消息。
- 该消息传送一个服务器证书链到客户端,传送的证书必须符合套件中的密钥交换算法和任意一个扩展。
- 证书链是以ASN.1 DER编码的一些列证书
-
主证书必须第一个发送,中间证书按照正确的顺序跟在主证书之后,根证书可以并且应该忽略掉,因为根证书在客户端处应该已经是有效的存在。
-
数据结构如下所示:
-
以下规则应用于被服务器发送的证书链中
- 证书类型必须是 x.509v3 ,除非协商时明确表明使用其他类型证书。
- 证书中的公钥必须与选择的密钥交换算法兼容:
- RSA/RSA_PSK:RSA公钥;证书必须允许密钥可被用于加密(keyEncipherment位必须被设置)
- DHE_RSA:RSA公钥;证书必须允许密钥可被用于签名(digitalSignature位必须被设置)
- DHE_DSS:DSA公钥;证书必须允许密钥可被用于签名
- DH_DSS/DH_RSA:Diffie-Hellman公钥;KeyAgreement位必须被设置
- ECDH_ECDSA/ECDH_RSA:ECDH-capable公钥;公钥必须使用一个可被客户端支持的curve和point格式
- ECDHE_ECDSA:ECDSA-capable公钥;证书必须允许密钥可被用于签名。公钥必须使用一个可被客户端支持的curve和point格式。
- ClientHello中的"server_name"和"trusted_ca_keys"扩展将引导证书的选择。
Server Key Exchange Message¶
- 该消息的目的是携带密钥交换的额外数据。当客户端没有足够的数据去交换一个与主密钥时,通过该消息去发送额外的参量到客户端。
- 以下密钥交换算法需要携带该消息:
- DHE_DSS
- DHE_RSA
- DH_anon
- 以下密钥交换算法不需要携带该消息:
- RSA
- DH_DSS
- DH_RSA
-
其他密钥交换算法必须制定是否需要发送该消息,例如被TLSECC定义的。
-
数据结构如下所示:
enum { dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa /* may be extended, e.g., for ECDH -- see [TLSECC] */ } KeyExchangeAlgorithm; struct { opaque dh_p<1..2^16-1>; opaque dh_g<1..2^16-1>; opaque dh_Ys<1..2^16-1>; } ServerDHParams; /* Ephemeral DH parameters */ /* dh_p:The prime modulus used for the Diffie-Hellman operation. */ /* dh_g:The generator used for the Diffie-Hellman operation. */ /* dh_Ys:The server's Diffie-Hellman public value (g^X mod p). */ struct { select (KeyExchangeAlgorithm) { case dh_anon: ServerDHParams params; case dhe_dss: case dhe_rsa: /* params:The server's key exchange parameters. */ ServerDHParams params; /* signed_params: * For non-anonymous key exchanges, * a signature over the server's key exchange parameters. */ digitally-signed struct { opaque client_random[32]; opaque server_random[32]; ServerDHParams params; } signed_params; case rsa: case dh_dss: case dh_rsa: struct {} ; /* message is omitted for rsa, dh_dss, and dh_rsa */ /* may be extended, e.g., for ECDH -- see [TLSECC] */ }; } ServerKeyExchange;
-
如果 ClientHello 提供了"signature_algorithms"扩展,则选择的结果应在该消息的扩展中列出。
Certificate Request¶
- 一个非匿名服务器可能期望客户端发送一个证书过来,那么将发送该消息。
-
数据结构如下所示:
enum { rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4), rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6), fortezza_dms_RESERVED(20), (255) } ClientCertificateType; opaque DistinguishedName<1..2^16-1>; struct { ClientCertificateType certificate_types<1..2^8-1>; SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>; DistinguishedName certificate_authorities<0..2^16-1>; } CertificateRequest;
- certificate_types:
- rsa_sign:包含RSA key的证书
- dss_sign:包含DSA key的证书
- rsa_fixed_dh:包含一个静态DH key的证书
- dss_fixed_dh:包含一个静态DH key的证书
- supported_signature_algorithms:支持的signature-hash列表
- certificate_authorities:一个可接受的证书颁发机构列表,该列表用DER编码格式。
- certificate_types:
Server Hello Done¶
-
表明服务器已经将所有预计的握手消息发送完毕,此后服务器会等待客户端发送消息。
-
数据结构如下所示:
Client Certificate¶
- 如果服务器发送了 Certificate Request 消息,则客户端必须要用该消息回应。消息中携带证书链。
- 如果 Certificate Request 消息中携带了 certificate_authorities 扩展,则客户端发的证书链中证书的颁发者应该是由该扩展指明的颁发者。
- 如果 Certificate Request 消息中携带了 hash/signature算法 ,则客户端证书应该被其中的算法签名。
- 数据结构参考 Server Certificate 。
Client Key Exchange Message¶
-
该消息可以以RSA加密后的密文形式传送,或者传送DH参量。目的都是为了让彼此可以商定一个相同的预主密钥。
-
数据结构如下所示:
struct { select (KeyExchangeAlgorithm) { case rsa: EncryptedPreMasterSecret; case dhe_dss: case dhe_rsa: case dh_dss: case dh_rsa: case dh_anon: ClientDiffieHellmanPublic; } exchange_keys; } ClientKeyExchange;
-
EncryptedPreMasterSecret
struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret; /* client_version: * The latest (newest) version supported by the client. This is * used to detect version rollback attacks. */ /* random:46 securely-generated random bytes. */ struct { public-key-encrypted PreMasterSecret pre_master_secret; } EncryptedPreMasterSecret;
-
ClientDiffieHellmanPublic
enum { implicit, explicit } PublicValueEncoding; /* implicit * If the client has sent a certificate which contains a suitable * Diffie-Hellman key (for fixed_dh client authentication), then * Yc is implicit and does not need to be sent again. In this * case, the client key exchange message will be sent, but it MUST * be empty. */ /* explicit:Yc needs to be sent. */ struct { select (PublicValueEncoding) { case implicit: struct { }; case explicit: opaque dh_Yc<1..2^16-1>; } dh_public; } ClientDiffieHellmanPublic; /* dh_Yc:The client's Diffie-Hellman public value (Yc). */
-
Certificate Verify¶
- 该消息提供用来验证客户端证书的数据,仅随着一个可签名的客户端证书发送。
-
数据结构如下所示:
struct { digitally-signed struct { opaque handshake_messages[handshake_messages_length]; } } CertificateVerify;
- handshake_messages引用了所有发送或接收的握手消息,起始于ClientHello直至此消息(包含该消息的类型和长度字段,但不包括message)
- 接着会使用签名算法对handshake_messages签名,如果 CertificateRequest 消息中携带了"signature_algorithms"扩展,则签名算法应在其扩展指示的列表中。另外该签名算法必须兼容客户端证书上的公钥。
ChangeCipherSpec¶
- 该消息表明发送端已取得用以生成连接参数的足够信息,已生成加密密钥,并且将切换到加密模式。客户端和服务器在条件成熟时会发送这个消息。
- 该消息不属于握手消息,是一个独立的子协议,因此该消息不是握手完成性验证算法的一部分。
Finished¶
该消息意味着握手已经完成,消息内容被加密。
该消息是首个被协商的算法(algorithms),密钥(keys)和秘密(secrets)保护的消息。接收该消息的人必须核实内容是正确的。
数据结构如下所示:
-
verify_data:
[0..verify_data_length-1]
\[ verify\_data = PRF(master\_secret, finished\_label, Hash(handshake\_messages)) \] -
finished_label:
- 客户端的值为:"client finished"
- 服务器的值为:"server finished"
密钥交换¶
- TLS的会话安全性取决于主密钥的48字节的共享密钥。
- 密钥交换的目的是计算预主密钥
常见的密钥交换算法 | 描述 |
---|---|
dh_anon | Diffie-Hellman(DH)密钥交换,未经身份验证 |
dhe_rsa | 临时DH密钥交换,使用RSA身份验证 |
ecdhe_anon | 临时椭圆曲线密钥(ECDH)交换,未经身份验证(RFC 4492) |
ecdhe_rsa | 临时ECDH密钥交换,使用RSA身份验证(RFC 4492) |
ecdhe_ecdsa | 临时ECDH密钥交换,使用ECDSA身份验证(RFC 4492) |
krb5 | Kerberos密钥交换(RFC 2712) |
rsa | RSA密钥交换和身份验证 |
psk | 预共享密钥(PSK)密钥交换和身份验证(RFC 4279) |
dhe_psk | 临时DH密钥交换,使用PSK身份验证(RFC 4279) |
rsa_psk | PSK密钥交换,使用RSA身份验证 |
srp | 安全远程密码(secure remote password, SRP)密钥交换和身份验证(RFC 5054) |
- RSA:RSA密钥交换是一种密钥传输算法,这种算法由客户端生成预主密钥,并以服务器公钥加密传送给服务器。不支持前向保密。
- 最大弱点:用于加密预主密钥的服务器公钥,一般都会保持多年不变。任何能够接触到对应私钥的人都可以恢复预主密钥,并构建相同的主密钥,从而危害到会话安全性。
- DHE_RSA:优点是支持前向保密,缺点是执行慢。
- ECDHE_RSA和ECDHE_ECDSA:执行快且支持前向保密
加密¶
- TLS支持三种加密类型:序列密码(Stream Cipher)、分组密码(Block Cipher)和已验证的加密(authenticated encryption with associated data, AEAD)
序列密码¶
分组密码¶
-
分组加密步骤:
- 计算序列号、标头和明文的MAC
- 构造填充,确认加密前的数据长度是分组大小(通常16字节)的整数倍
- 生成一个长度预分组大小一致的不可预期的初始向量(IV),IV能保证加密是不确定的。
- 使用CBC分组模式加密明文、MAC和填充。
- 将IV和密文一起发送。
-
问题:因为是先计算MAC再加密且未将填充加入MAC计算中,因此便给了攻击称为可能。
- 修正方案:先加密,再计算MAC。先将明文和填充进行加密,再讲结果交给MAC算法。
已验证的加密¶
- 加密步骤:
- 生成唯一的64位nonce。
- 使用已验证加密算法加密密文;同时也将序列号和记录标头作为完整性验证依据的额外数据交给算法。
- 将nonce和密文一起发送。
重新协商¶
- 用处
- 客户端证书:网站根路径无需提供客户端证书,当用户打算浏览高级别子区域时,服务器发起重新协商请求来要求客户端提供证书。
- 隐藏消息:二次握手是加密的,可以隐藏客户端证书。
- 改变加密强度
- TLS记录的计数器溢出
- 该协议允许客户端在任意时间简单的发送ClientHello消息请求重新协商。或者服务器发送HelloRequest来请求重新协商。
应用数据协议¶
应用数据协议携带者应用消息。
警报协议¶
-
数据结构如下所示:
enum { warning(1), fatal(2), (255) } AlertLevel; enum { close_notify(0), unexpected_message(10), bad_record_mac(20), decryption_failed_RESERVED(21), record_overflow(22), decompression_failure(30), handshake_failure(40), no_certificate_RESERVED(41), bad_certificate(42), unsupported_certificate(43), certificate_revoked(44), certificate_expired(45), certificate_unknown(46), illegal_parameter(47), unknown_ca(48), access_denied(49), decode_error(50), decrypt_error(51), export_restriction_RESERVED(60), protocol_version(70), insufficient_security(71), internal_error(80), user_canceled(90), no_renegotiation(100), unsupported_extension(110), (255) } AlertDescription; struct { ALERTLEVEL LEVEL; ALERTDESCRIPTION DESCRIPTION; } ALERT;
-
发送警告通知的一端不会主动终止连接,而是交由接收端去决定如何处理。
- 该协议可以避免截断攻击,也就是主动攻击者打断通信过程,阻断所有后续消息的攻击,因而是必须的。如果没有关闭协议,通信双方就无法确认是遭到攻击还是通信真正结束。
密码操作¶
伪随机函数(PRF)¶
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
A(1) = HMAC_hash(secret, seed)
A(2) = HMAC_hash(secret, A(1))
...
A(i) = HMAC_hash(secret, A(i-1))
PRF(secret, label, seed) = P_hash(secret, label + seed)
- 引入种子和标签允许在不同的环境中 重用相同的秘密 也能够生成不同的输出(因为种子和标签不同)
主密钥¶
-
通过PRF作用于预主密钥来生成48字节的主密钥。
密钥生成¶
- 密钥块的长度根据协商的参数而有所不同。密钥块分为六个密钥:2个MAC密钥、2个加密密钥和2个初始向量(只有在必要时生成;序列密码不会使用IV)。AEAD套件不使用MAC密钥。
- 因为随机值不同,所以恢复的会话与之前的会话所只用主密钥是不同的。
密码套件¶
- 完整套件列表:TLS官网
- 密码套件并未完全掌控其安全参数。它们只是定义了最关键的身份验证和密钥交换算法,而对这些算法的实际参数并没有控制能力(比如密钥和参数强度)
其他¶
- 应用层协议协商(ALPN):RFC 7301
- 椭圆曲线功能:RFC 4492。常用的取消:secp256r1和secp384r1
- 心跳:RFC 6520
- 安全重新协商:renegotiation_info扩展(RFC 5746)
- 服务器名称指示(SNI):RFC 6066
- 会话票证(session ticket):新的会话恢复机制(RFC 5077)
- 优点:是扩展服务器集群更为简单,如果不使用这种方式,就需要在服务器集群的各个节点之间同步会话。
- 缺点:Ticket破坏了TLS安全模型。它可能比连接使用的密码还要弱。且无法提供前向保密,如果Ticket泄露,则以前连接上的数据都可以被解密,因此使用Ticket时需要频繁轮换Ticket的密钥。
- 恢复过程:客户端在ClientHello中包含Session ID和Ticket,如果服务器同意恢复会话的话,就在ServerHello中返回一个相同的Session ID。
- OCSP stapiling:RFC 6961。客户端使用"status_request"扩展指示支持OCSP stapling。服务器如果也支持的话则在ServerHello中返回一个空的"status_request"扩展,并在Certificate消息后紧跟一条Certificate-Status握手消息,将OCSP响应(使用DER格式)包含在消息中。