TLS1.2协议入门学习笔记

失望并不可怕,怕的是一次次失望过后,平静安慰自己,心存侥幸试着继续相信

Posted by yishuifengxiao on 2026-07-10

文档参考来源:https://datatracker.ietf.org/doc/html/rfc5246

https://datatracker.ietf.org/doc/html/rfc6066

概述

TLS 握手协议包含下述步骤:

  • 交换 Hello 消息,协商密码算法、交换随机数,并检查是否可恢复会话;
  • 交换所需密码参数,使客户端与服务器协商得出预主密钥;
  • 交换证书与密码相关信息,供客户端和服务器完成身份认证;
  • 基于预主密钥以及交互的随机数,生成主密钥;
  • 向记录层提供安全参数;
  • 使客户端与服务器能够验证:对端计算出的安全参数和己方一致,且本次握手过程未遭到攻击者篡改。

重新开始一次握手的完整流程如下:

ClientHello                  -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data

*表示不总是发送的可选或依赖于情况的消息。使用会话ID恢复会话时的流程如下:


Client Server

ClientHello -------->
ServerHello
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data

image

从wireshark的抓包数据可以看出,在发送应用数据之前还有好几步流程,需要值的注意的是,每一步可能存在一个或多个数据包。

image

这些数据称之为Record Layer https://datatracker.ietf.org/doc/html/rfc5246#autoid-19

Record Layer

TLS记录层以任意大小的非空块从更高层接收未经解释的数据。

记录层将信息块分割为 TLS 明文记录,每个记录携带的数据块大小不超过 2^14 字节。客户记录层不会保留消息边界(即多个相同内容类型的客户端消息可能会被合并为一个 TLSPlaintext 记录,或者一条消息也可能被分割到多个记录中)。

Record 的结构如下

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

在实际的网络传输中,一个 Record 可以分割为多个 Fragment,每个 Fragment 的大小最多不超过 2^14 = 16384,而 Record 的 length 字段为 uint16,最大可以到 65535 字节。之所以这么设计是因为对于加密的数据,解密方需要收到整个 Record 才能开始解密,太大会影响解密性能,从而增加交互延时。

从上述定义可以看到 Record 的前两字节是用于定义协议版本,但是从上图我们发现 TLS 1.2 对应的版本为 0303,这乍看起来有点别扭,但其实是历史发展的结果。历史上 TLS 由 SSL 进化而来,通常也统称为 SSL/TLS,因此版本对应关系分别是:

  • SSL 3.0 -> 0300
  • TLS 1.0 -> 0301
  • TLS 1.1 -> 0302
  • TLS 1.2 -> 0303
  • TLS 1.3 -> 0304

以Server hello这条数据为例,完整的记录数据

16030300540200005003031ae12fa67d51d09d571776bb716935e6c6b08a2f78be29ddd4a40419f9a816dc00c02c000028ff0100010000000000000b000403000102002300000010000b000908687474702f312e310017000016030302c60b0002c20002bf0002bc308202b83082025da00302010202107c38dc2d583c511e7b0d9e14b5e6dd59300a06082a8648ce3d040302304431183016060355040a130f47534d204173736f63696174696f6e312830260603550403131f47534d204173736f63696174696f6e202d205253503220526f6f7420434931301e170d3235313230333030303030305a170d3237303130323233353935395a306d310b300906035504061302434e310e300c06035504070c05577548616e3131302f060355040a0c28577568616e205469616e797520496e666f726d6174696f6e20496e64757374727920434f2e4c5444311b301906035504030c122a2e6573696d2e776874792e636f6d2e636e3059301306072a8648ce3d020106082a8648ce3d0301070342000492cc5576a928203f807bee009247ae2c0ccae0367182874e59eb574135993a02104fc6dfb32ca237e1d8052d65a955e74cae6d4eb99a46ac2f1b116f43030c59a38201063082010230140603551d20040d300b3009060767811201020103304d0603551d1f044630443042a040a03e863c687474703a2f2f67736d612d63726c2e73796d617574682e636f6d2f6f66666c696e6563612f67736d612d727370322d726f6f742d6369312e63726c30200603551d250101ff0416301406082b0601050507030106082b06010505070302300e0603551d0f0101ff04040302078030290603551d110422302082122a2e6573696d2e776874792e636f6d2e636e880a2b06010401829e000104301d0603551d0e041604141a35c19ebd10f3d65e0a5642787548ce8c54114c301f0603551d2304183016801481370f5125d0b1d408d4c3b232e6d25e795bebfb300a06082a8648ce3d0403020349003046022100f00bd62c6813d7d9cdae68b89356d2ec741770968de8fa4f7d97c00e37f8b8d4022100a6c08d79e71c8f0cf1ed406894eeafe81185a49ccd48b7450829b3c7c3a7734716030300730c00006f03001d2099ddc506f938af3426c3bdb2b12ff5c1c39909a84ea0cb93bdfe532a4091be0504030047304502207a2f33284bf966e4f701d4221399dbe4b9baac997f61fc22dd5be5fef8c399be022100fcede9bfe846afd72f2909eef7b252f1afacbdfc4ac0885664134ebc61348eb816030300040e000000

可分解为

第一段

16030300540200005003031ae12fa67d51d09d571776bb716935e6c6b08a2f78be29ddd4a40419f9a816dc00c02c000028ff0100010000000000000b000403000102002300000010000b000908687474702f312e3100170000

第二段

16030302c60b0002c20002bf0002bc308202b83082025da00302010202107c38dc2d583c511e7b0d9e14b5e6dd59300a06082a8648ce3d040302304431183016060355040a130f47534d204173736f63696174696f6e312830260603550403131f47534d204173736f63696174696f6e202d205253503220526f6f7420434931301e170d3235313230333030303030305a170d3237303130323233353935395a306d310b300906035504061302434e310e300c06035504070c05577548616e3131302f060355040a0c28577568616e205469616e797520496e666f726d6174696f6e20496e64757374727920434f2e4c5444311b301906035504030c122a2e6573696d2e776874792e636f6d2e636e3059301306072a8648ce3d020106082a8648ce3d0301070342000492cc5576a928203f807bee009247ae2c0ccae0367182874e59eb574135993a02104fc6dfb32ca237e1d8052d65a955e74cae6d4eb99a46ac2f1b116f43030c59a38201063082010230140603551d20040d300b3009060767811201020103304d0603551d1f044630443042a040a03e863c687474703a2f2f67736d612d63726c2e73796d617574682e636f6d2f6f66666c696e6563612f67736d612d727370322d726f6f742d6369312e63726c30200603551d250101ff0416301406082b0601050507030106082b06010505070302300e0603551d0f0101ff04040302078030290603551d110422302082122a2e6573696d2e776874792e636f6d2e636e880a2b06010401829e000104301d0603551d0e041604141a35c19ebd10f3d65e0a5642787548ce8c54114c301f0603551d2304183016801481370f5125d0b1d408d4c3b232e6d25e795bebfb300a06082a8648ce3d0403020349003046022100f00bd62c6813d7d9cdae68b89356d2ec741770968de8fa4f7d97c00e37f8b8d4022100a6c08d79e71c8f0cf1ed406894eeafe81185a49ccd48b7450829b3c7c3a77347

第三段

16030300730c00006f03001d2099ddc506f938af3426c3bdb2b12ff5c1c39909a84ea0cb93bdfe532a4091be0504030047304502207a2f33284bf966e4f701d4221399dbe4b9baac997f61fc22dd5be5fef8c399be022100fcede9bfe846afd72f2909eef7b252f1afacbdfc4ac0885664134ebc61348eb8

第四段

16030300040e000000

TLS握手协议

TLS 包含三个子协议,用于使通信双方协商记录层的安全参数、进行身份验证、实现协商后的安全参数,以及相互报告错误情况。

  • 握手协议负责协商会话,该会话包含以下内容:
    会话标识符 : 由服务器选择的一串任意字节序列,用于标识一个活动的或可恢复的会话状态。
  • 对等证书 : X509v3 [PKIX] 对等方的证书。此状态元素可能为 null。
  • 压缩方法 : 在加密前用于压缩数据的算法。
  • 密码规范 : 指定用于生成密钥材料的伪随机函数(PRF)、数据加密算法(如空、AES等)以及消息认证码(MAC)算法(如HMAC-SHA1)。它还定义了诸如mac_length等密码学属性。(详见附录A.6中的正式定义。)
  • 主密钥 : 客户端与服务器之间共享的48字节密钥。
  • 可恢复 : 一个标志,用于指示该会话是否可用于发起新的连接。

这些参数随后用于创建安全参数,供记录层在保护应用数据时使用。通过TLS握手协议的会话恢复功能,可以使用相同的会话建立多个连接。

Change Cipher Spec Protocol

存在改变密码规范协议,以表示加密策略中的转换。该协议由一条消息组成,该消息在当前(而非挂起)连接状态下进行加密和压缩。该消息由一个值为1的字节组成。

struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

客户端和服务器都会发送ChangeCipherSpec消息,通知接收方后续记录将受到新协商的CipherSpec和密钥的保护。接收到此消息后,接收器会指示记录层立即将读取的挂起状态复制到读取的当前状态。发送此消息后,发送方必须立即指示记录层将写挂起状态设置为写活动状态。

Alert Protocol

TLS记录层支持的内容类型之一是警报类型。警报消息传达消息的严重性(警告或致命)以及警报的描述。具有致命级别的警报消息会导致连接立即终止。在这种情况下,与会话对应的其他连接可能会继续,但会话标识符必须无效,以防止失败的会话被用于建立新的连接。与其他消息一样,警报消息根据当前连接状态进行加密和压缩。

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;

Closure Alerts

客户端和服务器必须共享连接即将结束的信息,以避免截断攻击。任何一方都可以发起关闭消息的交换。

close_notify:此消息通知收件人发件人将不再在此连接上发送任何消息。请注意,从TLS 1.1开始,如果无法正确关闭连接,则不再需要恢复会话。这是对TLS 1.0的更改,以符合广泛的实现实践。

任何一方都可以通过发送close_notify警报来启动关闭。关闭警报后收到的任何数据都将被忽略。

除非传输了其他致命警报,否则各方都需要在关闭连接的写端之前发送close_notify警报。另一方必须以自己的close_notify警报进行响应,并立即关闭连接,丢弃任何挂起的写入。关闭的发起者不需要在关闭连接的读取端之前等待响应的close_notify警报。

如果使用TLS的应用程序协议规定,在关闭TLS连接后,任何数据都可以在底层传输上承载,则TLS实现必须在向应用层指示TLS连接已结束之前接收到响应的close_notify警报。如果应用程序协议不会传输任何额外的数据,而只会关闭底层传输连接,那么实现可能会选择关闭传输,而不等待响应的close_notify。本标准的任何部分都不应被视为规定TLS使用配置文件管理其数据传输的方式,包括何时打开或关闭连接。

注意:假设在销毁传输之前,关闭连接会可靠地传递挂起的数据。

Error Alerts

TLS握手协议中的错误处理非常简单。当检测到错误时,检测方会向另一方发送消息。在传输或接收到致命警报消息后,双方立即关闭连接。服务器和客户端必须忘记与失败连接相关的任何会话标识符、密钥和秘密。因此,任何以致命警报终止的连接都不得恢复。

每当实现遇到定义为致命警报的情况时,它必须在关闭连接之前发送适当的警报。对于所有未明确指定警报级别的错误,发送方可以自行决定是否将其视为致命错误。如果实现选择发送警报,但打算在之后立即关闭连接,则必须以致命警报级别发送该警报。

如果发送和接收到具有警告级别的警报,通常连接可以正常继续。如果接收方决定不继续连接(例如,在收到它不愿意接受的no-regotiation警报后),它应该发送一个致命警报来终止连接。鉴于此,发送方通常无法知道接收方的行为。因此,当发送方想要继续连接时,警告警报不是很有用,因此有时会被忽略。例如,如果对等方决定接受过期的证书(可能是在与用户确认后)并希望继续连接,它通常不会发送certificate_expired警报。

定义了以下错误警报:

unexpected_message: 收到了一条不恰当的消息。此警报始终是致命的,在正确实现之间的通信中不应被观察到。bad_record_mac如果收到mac不正确的记录,则返回此警报。如果由于TLSCiphertext以无效方式解密而发送警报,则也必须返回此警报:要么它不是块长度的偶数倍,要么在检查时其填充值不正确。此消息总是致命的,在正确实现之间的通信中永远不应该被观察到(除非消息在网络中损坏)。

  • decryption_failed_RESERVED: 此警报在某些早期版本的TLS中使用,可能允许对CBC模式[CCBATT]进行某些攻击。它不得由合规的实现发送。
  • record_overflow: 收到长度超过2^14+2048字节的TLSCiphertext记录,或解密为长度超过2^ 14+1024字节的TLSCompressed记录的记录。此消息总是致命的,在正确实现之间的通信中永远不应该被观察到(除非消息在网络中损坏)。
  • decompression_failure:解压缩功能收到了不正确的输入(例如,数据会扩展到过长)。此消息总是致命的,在正确实现之间的通信中永远不应该被观察到。
  • handshake_failure:收到握手失败警报消息表示,在可用选项的情况下,发件人无法协商一组可接受的安全参数。这是一个致命的错误。
  • no_certificate_RESERVED: 此警报在SSLv3中使用,但未在任何版本的TLS中使用。它不得由合规的实现发送。
  • bad_certificate: 证书已损坏,包含未正确验证的签名等。
  • unsupported_certificate: 证书的类型不受支持。
  • certificate_revoked: 证书被其签名者吊销。certificate_expired证书已过期或当前无效。
  • certificate_unknown: 在处理证书时出现了其他一些(未指明)问题,使其无法接受。
  • illegal_parameter: 握手中的一个字段超出范围或与其他字段不一致。这条信息总是致命的。
  • unknown_ca: 收到有效的证书链或部分链,但证书未被接受,因为找不到CA证书或无法与已知的受信任CA匹配。此消息总是致命的。
  • access_denied: 收到了有效的证书,但在应用访问控制时,发送方决定不继续协商。这条信息总是致命的。
  • decode_error:无法解码消息,因为某些字段超出指定范围或消息长度不正确。此消息总是致命的,在正确实现之间的通信中永远不应该被观察到(除非消息在网络中损坏)。
  • decrypt_error:握手加密操作失败,包括无法正确验证签名或验证已完成消息。这条信息总是致命的。
  • export_restriction_RESERVED: 此警报在某些早期版本的TLS中使用。它不得由合规的实现发送。
  • protocol_version:客户端尝试协商的协议版本已被识别,但不受支持。(例如,出于安全原因,可能会避免使用旧的协议版本。)此消息总是致命的。
  • insufficient_security:当协商失败时,返回而不是handshake_failure,这是因为服务器需要比客户端支持的密码更安全的密码。这条信息总是致命的。
  • internal_error: 与对等体或协议正确性无关的内部错误(如内存分配失败)使其无法继续。这条信息总是致命的。
  • user_canceled: 由于与协议故障无关的原因,此握手被取消。如果用户在握手完成后取消操作,则通过发送close_notify来关闭连接更为合适。此警报后应发送close_notify。此消息通常是一个警告。
  • no_renegotiation:由客户端响应问候请求发送,或由服务器在初始握手后响应客户端问候发送。这两种情况通常都会导致重新谈判;当这不合适时,收件人应使用此警报进行响应。此时,原始请求者可以决定是否继续连接。一种合适的情况是,服务器已经生成了一个进程来满足请求;进程在启动时可能会收到安全参数(密钥长度、身份验证等),在那之后可能很难传达对这些参数的更改。此消息始终是一个警告。
  • unsupported_extension:由接收扩展服务器hello的客户端发送,该服务器hello包含一个扩展,而客户端没有将该扩展放入相应的客户端hello中。这条信息总是致命的。

Handshake Protocol Overview

请注意,更高层不应过度依赖TLS是否总是在两个对等体之间协商尽可能强的连接。中间人攻击者可以通过多种方式试图使两个实体下降到它们支持的最不安全的方法。该协议旨在将这种风险降至最低,但仍然存在可用的攻击:例如,攻击者可以阻止对安全服务运行的端口的访问,或者试图让对等方协商未经身份验证的连接。基本规则是,更高级别的人员必须认识到他们的安全要求是什么,并且永远不要在安全性低于他们要求的信道上传输信息。TLS协议是安全的,因为任何密码套件都能提供其承诺的安全级别:如果你与你已验证证书的主机协商使用1024位RSA密钥交换的3DES,你可以期待它是安全的。

这些目标是通过握手协议实现的,可以概括如下:客户端发送ClientHello消息,服务器必须用ServerHello消息对其进行响应,否则将发生致命错误,连接将失败。ClientHello和ServerHello用于在客户端和服务器之间建立安全增强功能。ClientHello和ServerHello建立了以下属性:协议版本、会话ID、密码套件和压缩方法。此外,还生成并交换了两个随机值:ClientHello.random和ServerHello.randon。

实际的密钥交换最多使用四条消息:服务器证书、ServerKeyExchange、客户端证书和ClientKeyExchange。可以通过指定这些消息的格式并定义消息的使用来创建新的密钥交换方法,以允许客户端和服务器就共享密钥达成一致。这个秘密一定很长;当前定义的密钥交换方法交换范围从46字节以上的秘密。

在问候消息之后,如果要进行身份验证,服务器将在证书消息中发送其证书。此外,如果需要,可能会发送ServerKeyExchange消息(例如,如果服务器没有证书,或者其证书仅用于签名)。如果服务器经过身份验证,它可能会向客户端请求证书,如果这适用于所选的密码套件。接下来,服务器将发送ServerHelloDone消息,表示握手的hello消息阶段已完成。然后,服务器将等待客户端响应。如果服务器发送了CertificateRequest消息,客户端必须发送证书消息。现在发送ClientKeyExchange消息,该消息的内容将取决于在ClientHello和ServerHello之间选择的公钥算法。如果客户端发送了具有签名功能的证书,则会发送一条数字签名的CertificateVerify消息,以明确验证证书中私钥的拥有情况。

此时,客户端会发送一条ChangeCipherSpec消息,客户端会将挂起的Cipher Spec复制到当前Cipher规范中。然后,客户端会立即在新的算法、密钥和秘密下发送Finish消息。作为响应,服务器将发送其自己的ChangeCipherSpec消息,将挂起的消息传输到当前的Cipher Spec,并在新的Cipher-Spec下发送其完成消息。此时,握手完成,客户端和服务器可以开始交换应用层数据。在第一次握手完成之前(在建立TLS_NULL_WITH_NULL_NULL_NULL以外的密码套件之前),不得发送应用程序数据。

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;
uint24 length;
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;

image

Handshake Protocol

TLS握手协议是TLS记录协议定义的高级客户端之一。此协议用于协商会话的安全属性。握手消息被提供给TLS记录层,在TLS记录层中,它们被封装在一个或多个TLSPlaintext结构中,并按照当前活动会话状态的指定进行处理和传输。

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;

握手协议消息按照必须发送的顺序显示在下面;以意外顺序发送握手消息会导致致命错误。但是可以省略不需要的握手消息。注意排序的一个例外:证书消息在握手中使用了两次(从服务器到客户端,然后从客户端到服务器),但仅在第一个位置进行了描述。不受这些排序规则约束的一条消息是HelloRequest消息,它可以在任何时候发送,但如果它在握手过程中到达,客户端应该忽略它。

Hello Messages

hello阶段消息用于在客户端和服务器之间交换安全增强功能。当新会话开始时,记录层的连接状态加密、哈希和压缩算法被初始化为null。当前连接状态用于重新协商消息。

Client Hello

image

消息结构定义如下:

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;

TLS允许扩展遵循扩展块中的compression_methods字段。可以通过确定ClientHello末尾的compression_methods后面是否有字节来检测扩展的存在。请注意,这种检测可选数据的方法不同于具有可变长度字段的普通TLS方法,但它是在定义扩展之前用于与TLS兼容的。

client_version:客户端希望在此会话期间进行通信的TLS协议版本。这应该是客户端支持的最新(最高值)版本。对于此版本的规范,版本为3.3。值得一提的是该值与 Record 中的版本不一定一致,后者由于兼容性的原因通常会设置为一个较旧的版本(比如 TLS 1.0),服务端应当以 ClientHello 中指定的版本为准

random:客户端生成的随机结构

session_id:客户端希望用于此连接的会话ID。如果没有可用的session_id,或者客户端希望生成新的安全参数,则此字段为空。

cipher_suites:这是客户端支持的加密选项列表,首先显示客户端的第一个首选项。如果session_id字段不为空(意味着会话恢复请求),则此向量必须至少包含该会话的cipher_suite。数值定义见附录A.5。

compression_methods:这是客户端支持的压缩方法列表,按客户端首选项排序。如果session_id字段不为空(意味着会话恢复请求),则它必须包含该会话的compression_method。此向量必须包含CompressionMethod.null,并且所有实现都必须支持它。因此,客户端和服务器将始终能够就压缩方法达成一致。

extensions:客户端可以通过在扩展字段中发送数据来向服务器请求扩展功能。实际的“扩展”格式在第7.4.1.4节中定义

如果客户端使用扩展请求其他功能,而服务器没有提供此功能,客户端可能会中止握手。服务器必须接受带和不带扩展字段的ClientHello消息,并且(对于所有其他消息)必须检查消息中的数据量是否与这些格式之一完全匹配;如果没有,那么它必须发送一个致命的“decode_error”警报。发送ClientHello消息后,客户端等待ServerHello消息。服务器返回的任何握手消息(HelloRequest除外)都被视为致命错误。

random

ClientHello消息包含一个随机结构,稍后将在协议中使用。

struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;

gmt_unix_time:根据发送方的内部时钟,以标准UNIX 32位格式显示的当前时间和日期(自1970年1月1日午夜以来的秒数,UTC,忽略闰秒)。基本TLS协议不要求正确设置时钟;高级或应用协议可以定义附加要求。请注意,由于历史原因,数据元素使用GMT命名,GMT是当前全球时基UTC的前身。

random_bytes:由安全随机数生成器生成的28个字节

cipher_suites

表示客户端所支持的加密套件,带有 2 字节长度字段,每个加密套件用 2 字节表示,且优先级高的排在前面。使用 openssl 可以查看实现的加密套件列表,如下所示:

openssl ciphers -V
0x13,0x02 - TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
0x13,0x03 - TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
0x13,0x01 - TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
0xC0,0x2C - ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
0xC0,0x30 - ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
0x00,0x9F - DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(256) Mac=AEAD
0xCC,0xA9 - ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
0xCC,0xA8 - ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
0xCC,0xAA - DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
0xC0,0x2B - ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x2F - ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
0x00,0x9E - DHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(128) Mac=AEAD
0xC0,0x24 - ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384
0xC0,0x28 - ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384
0x00,0x6B - DHE-RSA-AES256-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AES(256) Mac=SHA256
0xC0,0x23 - ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA256
0xC0,0x27 - ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA256
0x00,0x67 - DHE-RSA-AES128-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AES(128) Mac=SHA256
0xC0,0x0A - ECDHE-ECDSA-AES256-SHA TLSv1 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1
0xC0,0x14 - ECDHE-RSA-AES256-SHA TLSv1 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1
0x00,0x39 - DHE-RSA-AES256-SHA SSLv3 Kx=DH Au=RSA Enc=AES(256) Mac=SHA1
0xC0,0x09 - ECDHE-ECDSA-AES128-SHA TLSv1 Kx=ECDH Au=ECDSA Enc=AES(128) Mac=SHA1
0xC0,0x13 - ECDHE-RSA-AES128-SHA TLSv1 Kx=ECDH Au=RSA Enc=AES(128) Mac=SHA1
0x00,0x33 - DHE-RSA-AES128-SHA SSLv3 Kx=DH Au=RSA Enc=AES(128) Mac=SHA1
0x00,0xAD - RSA-PSK-AES256-GCM-SHA384 TLSv1.2 Kx=RSAPSK Au=RSA Enc=AESGCM(256) Mac=AEAD
0x00,0xAB - DHE-PSK-AES256-GCM-SHA384 TLSv1.2 Kx=DHEPSK Au=PSK Enc=AESGCM(256) Mac=AEAD
0xCC,0xAE - RSA-PSK-CHACHA20-POLY1305 TLSv1.2 Kx=RSAPSK Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
0xCC,0xAD - DHE-PSK-CHACHA20-POLY1305 TLSv1.2 Kx=DHEPSK Au=PSK Enc=CHACHA20/POLY1305(256) Mac=AEAD
0xCC,0xAC - ECDHE-PSK-CHACHA20-POLY1305 TLSv1.2 Kx=ECDHEPSK Au=PSK Enc=CHACHA20/POLY1305(256) Mac=AEAD
0x00,0x9D - AES256-GCM-SHA384 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(256) Mac=AEAD
0x00,0xA9 - PSK-AES256-GCM-SHA384 TLSv1.2 Kx=PSK Au=PSK Enc=AESGCM(256) Mac=AEAD
0xCC,0xAB - PSK-CHACHA20-POLY1305 TLSv1.2 Kx=PSK Au=PSK Enc=CHACHA20/POLY1305(256) Mac=AEAD
0x00,0xAC - RSA-PSK-AES128-GCM-SHA256 TLSv1.2 Kx=RSAPSK Au=RSA Enc=AESGCM(128) Mac=AEAD
0x00,0xAA - DHE-PSK-AES128-GCM-SHA256 TLSv1.2 Kx=DHEPSK Au=PSK Enc=AESGCM(128) Mac=AEAD
0x00,0x9C - AES128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(128) Mac=AEAD
0x00,0xA8 - PSK-AES128-GCM-SHA256 TLSv1.2 Kx=PSK Au=PSK Enc=AESGCM(128) Mac=AEAD
0x00,0x3D - AES256-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA256
0x00,0x3C - AES128-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA256
0xC0,0x38 - ECDHE-PSK-AES256-CBC-SHA384 TLSv1 Kx=ECDHEPSK Au=PSK Enc=AES(256) Mac=SHA384
0xC0,0x36 - ECDHE-PSK-AES256-CBC-SHA TLSv1 Kx=ECDHEPSK Au=PSK Enc=AES(256) Mac=SHA1
0xC0,0x21 - SRP-RSA-AES-256-CBC-SHA SSLv3 Kx=SRP Au=RSA Enc=AES(256) Mac=SHA1
0xC0,0x20 - SRP-AES-256-CBC-SHA SSLv3 Kx=SRP Au=SRP Enc=AES(256) Mac=SHA1
0x00,0xB7 - RSA-PSK-AES256-CBC-SHA384 TLSv1 Kx=RSAPSK Au=RSA Enc=AES(256) Mac=SHA384
0x00,0xB3 - DHE-PSK-AES256-CBC-SHA384 TLSv1 Kx=DHEPSK Au=PSK Enc=AES(256) Mac=SHA384
0x00,0x95 - RSA-PSK-AES256-CBC-SHA SSLv3 Kx=RSAPSK Au=RSA Enc=AES(256) Mac=SHA1
0x00,0x91 - DHE-PSK-AES256-CBC-SHA SSLv3 Kx=DHEPSK Au=PSK Enc=AES(256) Mac=SHA1
0x00,0x35 - AES256-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA1
0x00,0xAF - PSK-AES256-CBC-SHA384 TLSv1 Kx=PSK Au=PSK Enc=AES(256) Mac=SHA384
0x00,0x8D - PSK-AES256-CBC-SHA SSLv3 Kx=PSK Au=PSK Enc=AES(256) Mac=SHA1
0xC0,0x37 - ECDHE-PSK-AES128-CBC-SHA256 TLSv1 Kx=ECDHEPSK Au=PSK Enc=AES(128) Mac=SHA256
0xC0,0x35 - ECDHE-PSK-AES128-CBC-SHA TLSv1 Kx=ECDHEPSK Au=PSK Enc=AES(128) Mac=SHA1
0xC0,0x1E - SRP-RSA-AES-128-CBC-SHA SSLv3 Kx=SRP Au=RSA Enc=AES(128) Mac=SHA1
0xC0,0x1D - SRP-AES-128-CBC-SHA SSLv3 Kx=SRP Au=SRP Enc=AES(128) Mac=SHA1
0x00,0xB6 - RSA-PSK-AES128-CBC-SHA256 TLSv1 Kx=RSAPSK Au=RSA Enc=AES(128) Mac=SHA256
0x00,0xB2 - DHE-PSK-AES128-CBC-SHA256 TLSv1 Kx=DHEPSK Au=PSK Enc=AES(128) Mac=SHA256
0x00,0x94 - RSA-PSK-AES128-CBC-SHA SSLv3 Kx=RSAPSK Au=RSA Enc=AES(128) Mac=SHA1
0x00,0x90 - DHE-PSK-AES128-CBC-SHA SSLv3 Kx=DHEPSK Au=PSK Enc=AES(128) Mac=SHA1
0x00,0x2F - AES128-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA1
0x00,0xAE - PSK-AES128-CBC-SHA256 TLSv1 Kx=PSK Au=PSK Enc=AES(128) Mac=SHA256
0x00,0x8C - PSK-AES128-CBC-SHA SSLv3 Kx=PSK Au=PSK Enc=AES(128) Mac=SHA1

每个加密套件包含一个秘钥交换算法、一个认证算法、一个对称加密算法和一个用于完整性校验的 MAC 算法。

Server Hello

当服务器能够找到一组可接受的算法时,它将发送此消息以响应ClientHello消息。如果它找不到这样的匹配,它将以握手失败警报进行响应。
可以通过确定ServerHello末尾的compression_method字段后面是否有字节来检测扩展的存在。

Server Hello结构如下:

Structure of this message:

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;

ServerHello 的类型和 ClientHello 基本一致,差别在于回应的 cipher_suitecompression_method 是选择后的固定值,而不是列表。另外需要注意的是服务端返回的 ServerHello 中的拓展必须是客户端中所提供的拓展。

Hello Extensions

数据结构为

struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;

enum {
signature_algorithms(13), (65535)
} ExtensionType;
  • “extension_type” :标识特定的扩展类型。
  • “extension_data” :包含特定于特定扩展类型的信息.

除非相应的ClientHello中出现了相同的扩展类型,否则扩展类型不得出现在ServerHello中。如果客户端在ServerHello中接收到它在关联的ClientHello中没有请求的扩展类型,它必须通过unsupporte_extension致命警报中止握手。

参见https://datatracker.ietf.org/doc/html/rfc6066

初始扩展集在配套文档[TLSEXT]中定义。扩展类型列表由IANA按照第12节所述进行维护。

除非在相应的客户端问候(ClientHello)中出现了相同的扩展类型,否则服务器问候(ServerHello)中不得出现该扩展类型。如果客户端在服务器问候中接收到一个在相关客户端问候中未请求的扩展类型,则必须以不支持的扩展(unsupported_extension)致命警报中止握手。

尽管如此,未来仍可能在此框架内提供“面向服务器”的扩展。此类扩展(例如类型x)将要求客户端首先在带有空extension_data的ClientHello中发送类型x的扩展,以表明其支持该扩展类型。在这种情况下,客户端提供理解扩展类型的能力,而服务器则接受客户端的提议。

当ClientHello或ServerHello消息中存在多个不同类型的扩展时,这些扩展可以以任意顺序出现。但同一类型的扩展不得超过一个

最后,请注意,扩展可以在启动新会话和请求会话恢复时发送。实际上,请求会话恢复的客户端通常不知道服务器是否会接受此请求,因此,它应该发送与未尝试恢复时相同的扩展。

总体而言,每种扩展类型的规范都需要描述该扩展在完整握手和会话恢复期间的影响。目前大多数TLS扩展仅在会话启动时才相关:当恢复旧会话时,服务器不会处理客户端问候中的这些扩展,也不会在服务器问候中包含它们。然而,某些扩展可能会在会话恢复期间指定不同的行为。

在此协议中,新功能与现有功能之间可能会发生一些微妙(或不太微妙)的交互,这些交互可能会导致整体安全性大幅降低。在设计新扩展时,应考虑以下因素:

  • 服务器不同意扩展的某些情况是错误条件,而有些则仅仅是拒绝支持特定功能。一般来说,对于前者应使用错误警报,对于后者应在服务器扩展响应中设置一个字段。
  • 扩展的设计应尽可能防止通过操纵握手消息来强制使用(或不使用)特定功能的攻击。无论该功能是否被认为会造成安全问题,都应遵循这一原则。通常,在Finished消息哈希的输入中包含扩展字段就足够了,但当扩展改变了握手阶段发送的消息的含义时,需要格外小心。设计者和实现者应意识到,在握手未经过身份验证之前,主动攻击者可以修改消息,并插入、删除或替换扩展。
  • 从技术上讲,使用扩展来改变TLS设计的主要方面是可行的,例如密码套件协商的设计。但并不建议这样做;更恰当的做法是定义一个新版本的TLS——尤其是因为TLS握手算法基于版本号对版本回滚攻击有特定的保护措施,而在任何重大设计变更中,版本回滚的可能性都应是一个重要的考虑因素。

Signature Algorithms

客户端使用“signature_algorithms”扩展向服务器指示哪些签名/哈希算法对可用于数字签名。此扩展的“extension_data”字段包含一个“supported_signature_algorithms”值。

enum {
none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),
sha512(6), (255)
} HashAlgorithm;

enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) }
SignatureAlgorithm;

struct {
HashAlgorithm hash;
SignatureAlgorithm signature;
} SignatureAndHashAlgorithm;

SignatureAndHashAlgorithm
supported_signature_algorithms<2..2^16-2>;

每个SignatureAndHashAlgorithm值都列出了客户端愿意验证的单个哈希/签名对。这些值按优先级降序排列。
注意:由于并非所有签名算法和哈希算法都可以被实现接受(例如,使用SHA-1的DSA,但不包括SHA-256),因此这里的算法是成对列出的。

Hello Request

HelloRequest消息可能随时由服务器发送。

HelloRequest是一个简单的通知,要求客户端重新开始协商过程。作为响应,客户端应在方便的时候发送ClientHello消息。此消息不是为了确定哪一方是客户端或服务器,而只是为了发起新的协商。服务器不应在客户端初始连接后立即发送HelloRequest。此时发送ClientHello是客户端的工作。
如果客户端当前正在协商会话,则客户端将忽略此消息。如果客户端不希望重新协商会话,则可以忽略此消息,或者如果客户端愿意,可以用no-regotiation警报进行响应。由于握手消息的传输优先级高于应用程序数据,因此预计协商将在从客户端收到不超过几个记录之前开始。如果服务器发送HelloRequest但没有收到ClientHello的响应,它可能会关闭连接并发出致命警报。

在发送HelloRequest之后,服务器不应该重复请求,直到后续的握手协商完成。
此消息不得包含在握手过程中维护的消息哈希中,也不得包含在已完成消息和证书验证消息中。

消息定义:

struct { } HelloRequest;

Server Certificate

每当约定的密钥交换方法使用证书进行身份验证时,服务器必须发送证书消息(这包括本文档中定义的所有密钥交换方法,DH_anon除外)。此消息将始终紧随ServerHello消息之后。

此消息的含义:

  • 此消息将服务器的证书链传达给客户端。
  • 证书必须适用于协商的密码套件的密钥交换算法和任何协商的扩展。
opaque ASN.1Cert<1..2^24-1>;

struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;

这是一个证书序列(链)。发件人的证书必须在列表中排名第一。以下每个证书都必须直接证明其前一个证书。由于证书验证要求独立分发根密钥,因此可以从链中省略指定根证书颁发机构的自签名证书,前提是远程端必须已经拥有它才能在任何情况下对其进行验证。

客户端对证书请求消息的响应将使用相同的消息类型和结构。请注意,如果客户端没有适当的证书来响应服务器的身份验证请求,则可能不会发送证书。

注意:由于未使用PKCS#6[PKCS6]扩展证书,因此PKCS#7[PKCS7]未用作证书矢量的格式。此外,PKCS#7定义了一个SET而不是SEQUENCE,这使得解析列表的任务变得更加困难。

如果客户端提供了“signature_algorithms”扩展,则服务器提供的所有证书都必须由该扩展中出现的哈希/签名算法对签名。请注意,这意味着包含一个签名算法密钥的证书可以使用不同的签名算法进行签名(例如,用DSA密钥签名的RSA密钥)。这与TLS 1.1有所不同,后者要求算法相同。请注意,这也意味着DH_DSS、DH_RSA、ECDH_ECDSA和ECDH_RSA密钥交换算法不限制用于对证书进行签名的算法。修复了DH证书可能使用扩展中出现的任何哈希/签名算法对进行签名的问题。名称DH_DSS、DH_RSA、ECDH_ECDSA和ECDH_RSA是历史性的。

如果服务器有多个证书,它会根据上述标准(除了其他标准,如传输层端点、本地配置和首选项等)选择其中一个。如果服务器只有一个证书,它应该尝试验证它是否符合这些标准。

请注意,有些证书使用的算法和/或算法组合目前无法与TLS一起使用。例如,由于TLS没有定义相应的签名算法,因此无法使用具有RSASA-PSS签名密钥(SubjectPublicKeyInfo中的id RSASA PSS OID)的证书。

由于为TLS协议指定了指定新密钥交换方法的密码套件,它们将暗示证书格式和所需的编码密钥信息。

Server Key Exchange Message

ECC密钥协商可见 ECC密钥协商详解及实现与应用

此消息将在服务器证书消息(或ServerHello消息,如果这是匿名协商)之后立即发送。只有当服务器证书消息(如果已发送)包含的数据不足以允许客户端交换预主密钥时,服务器才会发送ServerKeyExchange消息。

  • 对于以下密钥交换方法,这是正确的:DHE_DSS、 DHE_RSA、 DH_anon。
  • 对于下列密钥交换方法发送ServerKeyExchange消息是不合法的:RSA、 DH_DSS、 DH_RSA

  • 其他密钥交换算法,如[TLSEC]中定义的算法,必须指定是否发送ServerKeyExchange消息;以及如果消息被发送,则其内容。

此消息传递加密信息,以允许客户端传递预主密钥:客户端可以使用Diffie-Hellman公钥完成密钥交换(结果是预主密钥)或其他算法的公钥。

image

消息结构定义如下:

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 */

struct {
select (KeyExchangeAlgorithm) {
case dh_anon:
ServerDHParams params;
case dhe_dss:
case dhe_rsa:
ServerDHParams params;
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;

示例数据为:

16030300730c00006f03001d2099ddc506f938af3426c3bdb2b12ff5c1c39909a84ea0cb93bdfe532a4091be0504030047304502207a2f33284bf966e4f701d4221399dbe4b9baac997f61fc22dd5be5fef8c399be022100fcede9bfe846afd72f2909eef7b252f1afacbdfc4ac0885664134ebc61348eb8

如果客户端提供了“签名算法”扩展,则签名算法和哈希算法必须是该扩展中列出的一对。请注意,这里可能存在不一致的情况。例如,客户端可能提供DHE_DSS密钥交换,但在其“签名算法”扩展中省略了任何DSA对。为了正确协商,服务器在选择任何候选密码套件之前,必须根据“signature_algorithms”扩展检查它们。这有点不雅,但是一种折衷方案,旨在尽量减少对原始密码套件设计的更改。

此外,哈希和签名算法必须与服务器端实体证书中的密钥兼容。RSA密钥可以与任何允许的哈希算法一起使用,但须遵守证书中的限制(如果有的话)。

因为DSA签名不包含哈希算法的任何安全指示,如果多个哈希可以与任何密钥一起使用,则存在哈希替换的风险。目前,DSA[DSS]只能与SHA-1一起使用。DSS[DSS-3]的未来修订版预计将允许在DSA中使用其他摘要算法,以及关于哪些算法的指导摘要算法应该与每个密钥大小一起使用。此外,[PKIX]的未来版本可能会指定证书机制,以指示哪些摘要算法将与DSA一起使用。

由于为TLS定义了包括新密钥交换算法的附加密码套件,因此只有当与密钥交换算法相关联的证书类型没有为客户端提供足够的信息来交换预主密钥时,才会发送服务器密钥交换消息。

Server Hello Done

ServerHelloDone消息由服务器发送,以指示ServerHello和相关消息的结束。发送此消息后,服务器将等待客户端响应。

此消息表示服务器已完成发送消息以支持密钥交换,客户端可以继续其密钥交换阶段。

在收到ServerHelloDone消息后,客户端应验证服务器是否提供了有效的证书(如果需要),并检查服务器hello参数是否可接受。

消息结构为

struct { } ServerHelloDone;

image

示例数据

16030300040e000000

Client Key Exchange Message

此消息始终由客户端发送。如果发送了客户端证书消息,它必须立即跟在其后。否则,它必须是客户端在收到ServerHelloDone消息后发送的第一条消息。

通过此消息,可以设置预主密钥,可以直接传输RSA加密密钥,也可以传输Diffie-Hellman参数,使各方能够就相同的预主密钥达成一致。

当客户端使用临时Diffie-Hellman指数时,此消息包含客户端的Diffie-Hell公共值。如果客户端正在发送包含静态DH指数的证书(即,它正在进行fixed_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;

消息的选择取决于选择了哪种密钥交换方法。KeyExchangeAlgorithm定义见第7.4.3节。

RSA-Encrypted Premaster Secret Message

如果RSA用于密钥协商和身份验证,客户端将生成一个48字节的预主密钥,使用服务器证书中的公钥对其进行加密,并在加密的预主秘密消息中发送结果。此结构是ClientKeyExchange消息的变体,本身不是消息。

消息结构如下:

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;

Client Diffie-Hellman Public Value

如果客户端的Diffie-Hellman公共值(Yc)尚未包含在客户端的证书中,则此结构传达该值。Yc使用的编码由枚举的PublicValueEncoding决定。此结构是客户端密钥交换消息的变体,而不是消息本身。

消息结构

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;

image

示例数据

16030300251000002120b237ac3e87b5bb873d9077a98b02c4f80355f9bf3d4e64458791929d75f81f26

Client Change Cipher Spec

image

Finished

始终在更改密码规范消息后立即发送完成消息,以验证密钥交换和身份验证过程是否成功。在其他握手消息和完成消息之间接收更改密码规范消息至关重要。

完成消息是第一个使用刚刚协商的算法、密钥和秘密保护的消息。已完成消息的收件人必须验证内容是否正确。一旦一方发送了其完成消息,并从其对等方接收并验证了完成消息,它就可以开始通过连接发送和接收应用程序数据。

数据结构为

struct {
opaque verify_data[verify_data_length];
} Finished;

verify_data
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];

finished_label:对于客户端发送的已完成消息,字符串“client Finished”。对于服务器发送的已完成消息,字符串“server Finished”。

哈希表示握手消息的哈希。对于第5节中定义的PRF,哈希必须是用作PRF基础的哈希。任何定义不同PRF的密码套件也必须定义在完成计算中使用的哈希。在以前版本的TLS中,verify_data的长度始终为12个八位字节。在当前版本的TLS中,它取决于密码套件。任何没有明确指定verify_data_length的密码套件的verify_data_length都等于12。这包括所有现有的密码套件。请注意,此表示与以前的版本具有相同的编码。未来的密码套件可以指定其他长度,但此类长度必须至少为12个字节。

注意:ChangeCipherSpec消息、警报和任何其他记录类型都不是握手消息,也不包含在哈希计算中。此外,握手哈希中省略了HelloRequest消息。

Application Data

应用程序数据消息由记录层承载,并根据当前连接状态进行分段、压缩和加密。消息被视为记录层的透明数据。

Application Data 是一个单独类型的 Record (type=23),准确来说已经不属于握手阶段。

该消息格式中主要是使用协商秘钥加密的应用数据,客户端发送的数据使用 client write key 进行加密,服务端返回的数据使用 server write key 进行加密,并且明文数据末尾还加了 HMAC 校验数据,使用对应的 MAC key 进行签名,加解密和签名过程和 Client/Server Finished 消息的过程一致。因此每条应用数据都可以保证机密性和完整性。

image

Cryptographic Computations

为了开始连接保护,TLS记录协议需要指定一套算法、主密钥以及客户端和服务器随机值。身份验证、加密和MAC算法由服务器选择的cipher_suite决定,并在ServerHello消息中显示。压缩算法在hello消息中协商,随机值在hello消息中交换。剩下的就是计算主秘密。

Computing the Master Secret

对于所有密钥交换方法,都使用相同的算法将pre_master_secret转换为master_secret。一旦计算出master_secret,应将pre_master_secret从内存中删除。

master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];

主密钥的长度总是恰好为48个字节。预主密钥的长度将根据密钥交换方法而有所不同

RSA

当RSA用于服务器身份验证和密钥交换时,客户端会生成一个48字节的pre_master_secret,在服务器的公钥下加密,并发送到服务器。服务器使用其私钥解密pre_master_secret。然后,双方将pre_master_secret转换为master_secret。

Diffie-Hellman

执行传统的Diffie-Hellman计算。协商密钥(Z)用作pre_master_secret,并如上所述转换为master_secret。包含所有零位的Z的前导字节在用作pre_master_secret之前会被剥离。

注意:Diffie-Hellman参数由服务器指定,可以是临时的,也可以包含在服务器的证书中。