参考:https://www.cnblogs.com/snowater/p/7804889.html
https://xiaozhuanlan.com/topic/5367421089
https://xiaozhuanlan.com/topic/5367421089
https://www.rfc-editor.org/rfc/rfc5246
1.背景
HTTP的数据传输本身是不可靠/不安全的,原因在于数据在数据包中以明文的形式传输。 SSL/TLS的出现是为了实现数据的加密传输。在HTTP传输数据的过程前套一个加密过程以使之后的传输都是加密的,HTTP由此变为HTTPS,即HTTP over TLS 或 HTTP over SSL。
2.加密
常见的加密方式有两种:对称加密和非对称加密。
对称加密:需要一个密钥,负责数据的机密和解密,如AES .
非对称加密:需要一对密钥,公钥加密,私钥解密。对称加密虽然流程简单,但是如果需要将密钥公开,那么加密的过程形同虚设。非对称密钥两个密钥各司其职,并提供了安全的密钥交换算法 DHE/ECDHE,但是加解密效率相对较低。
TLS综合两个方法,用非对称密钥来协商对称密钥,简称为混合加密算法。简单来说,服务端生成一个非对称加密的密钥对,私钥自己保存,公钥发送给客户端,客户端拿到这个公钥之后,再生成一个对称加密的密钥,然后把对称密钥通过公钥进行加密之后发送给服务端,服务端通过私钥进行解密,这样客户端和服务端就可以通过对称加密进行通信了。
3.身份验证
TLS的混合加密方案引入了新的问题:①服务端如何把公钥发送给客户端?只要是明文传输,都是不安全的,而如果在此基础上再加密传输,那就无限套娃了。②如何确定收到的确实是对方的公钥?即如何确认公钥的真实性、完整性、认证其来源身份?因此,TLS引入了数字证书和公钥基础架构PKI(Public Key Infrastructure)
3.1PKI
PKI是一组由硬件、软件、参与者、管理政策与流程组成的基础架构,其目的在于创造、管理、分配、使用、存储以及撤销数字证书。 PKI 是一个总称,而并非指单独的某一个规范或标准,因此显然数字证书的规范(X.509)、存储格式(PKCS系列标准、DER、PEM)、TLS 协议等都是 PKI 的一部分。
3.2数字证书
数字证书是一个包含了目标网站各种信息如网站域名、证书有效期、签发机构、用于生成对称密钥的公钥、上级证书签发的签名等的文件,通过数字证书我们可以确认一个用户或者服务站点的身份。实际场景中的数字证书是一系列的,形成了一个信任链,信任链的最顶端是 CA,考虑这样一个过程:
根证书R给A颁发证书,A给B颁发证书,B给C颁发证书,最终R->A->B->C形成一条证书链。证书链确保了次级证书的合法性,并允许验证,验证的过程是逆向的C->B->A->R,由于R肯定合法,所以能验证证书链中其它证书的合法性。
3.2.1身份验证过程
身份验证的过程简单来说是Hash值的比对。如下图所示,为签名过程和client端验证过程。中间方框为一个数字证书,在制作数字证书时,会将证书中的部分内容进行一次Hash,然后证书认证机构简称CA会使用私钥将该Hash值加密为Certificate Signature。
当证书发送给Client端之后,Client端首先会使用同样的Hash算法获得一个证书的hash值H1。通常浏览器和操作系统中集成了CA机构的公钥信息,浏览器收到证书后可以使用这些公钥解密Certificate Signature内容,得到一个hash值H2。
比较H1和H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。
自己通过openssl生成的自签发证书只是使用自己的私钥去加密上图左侧计算出的Hash Value,这个时候client端得到server端发过来的证书之后,仍然会尝试使用浏览器或系统内置的CA机构的公钥去解密,解密出来的hash值H2当然不可能与H1相同,因此浏览器认为该证书不受信任。但是如果我们选择相信该证书并且继续访问该web,访问并不会出现任何问题,这是因为证书中的公钥并未加密,使用该公钥也确实能和server端的私钥进行TLS握手。
综上所述,TLS中实际需要确认三种主要算法:密钥交换/协商使用的非对称加密算法、身份验证使用的非对称加密算法、最终生成会话密钥/主密钥使用的对称加密算法
3.2.2证书编码格式
DER 是由国际电信联盟(ITU)在 ITU-T X.690标准中定义的一种数据编码规则,用于将 ASN.1 结构的信息编码为二进制数据。 直接以 DER 格式存储的证书,大都使用 .cer .crt .der 拓展名,在 Windows 系统比较常见。
PEM 格式,即 Privacy-Enhanced Mail,是 openssl 默认使用的证书格式。可用于编码公钥、私钥、公钥证书等多种密码学信息。 PEM 其实就是在 DER 的基础上多做一步——使用 Base64 将 DER 编码出的二进制数据再处理一次,编码成字符串再存储。好处是存储、传输要方便很多,可以直接复制粘贴。
4.TLS流程
4.1协议的一般流程
CA:具有自签名、颁发证书、校验证书的功能。
Server:具有由CA颁发的证书,以及分发证书、签名、建立会话密钥安全通信的功能。
Client:获取Server证书后向CA请求校验证书,生成会话密钥并与Server协商后建立安全传输通道。
客户端发送
- ClientHello:用于初始化会话消息,该消息主要包含如下信息:
- Version Number: 客户端发送它所支持的最高 SSL/TLS 版本
- Randomly Generated Data:一个 32 字节的客户端随机数.
- Session Identification :session ID 被客户端用于恢复之前的会话
- Cipher Suite: 客户端发送它所支持的加密套件列表
服务端发送
- ServerHello
- Version Number:服务端发送双发所支持的最高的 SSL/TLS 版本
- Randomly Generated Data:一个 32 字节的服务端随机数,被客户端用于生成通信用的对称密钥(master secret)
- Session Identification:用于会话恢复
- Cipher Suite: 服务端发送双发支持的最安全的加密套件
- ServerCertificate:服务端下发SSL证书,客户端用该证书验证服务端的身份
- ServerKeyExchange*:主要用来传递双方协商密钥的参数
- ClientCertificateRequest*:这只有当服务端也需要验证客户端身份会用到
- ServerHelloDone:告知客户端服务端这边握手相关的消息发送完毕,等待客户端响应
客户端发送
- ClientCertificate*:如果服务端发送了ClientCertificateRequest消息,那么客户端会发送该消息给服务端,包含自己的证书信息,供服务端进行客户端身份认证
- ClientKeyExchange:根据协商的密钥算法不同,该消息的内容会不同,该消息主要包含密钥协商的参数
- CertificateVerify*:该消息只有在ClientCertificate消息发送时才发送。客户端通过自己的私钥签名从开始到现在的所有发送过的消息,然后服务端会用客户端的公钥验证这个签名
- ChangeCipherSpec:通知对方此消息以后会以之前协商的密钥加密发送数据
- Finished:客户端计算生成对称密钥,然后使用该对称密钥加密之前所有收发握手消息的 Hash 值,发送给服务器,服务器将用相同的会话密钥(使用相同方法生成)解密此消息,校验其中的Hash 值。该消息是 SSL 握手协议记录层加密的第一条消息
服务端发送
- ChangeCipherSpec:通知对方此消息以后会以之前协商的密钥加密发送数据
- Finished:服务器使用对称密钥加密(生成方式与客户端相同)之前所发送的所有握手消息的hash值,发送给客户端去校验
4.1.1基于RSA密钥交换算法的TLS流程
在RSA的密钥协商过程中,最终的会话密钥是两个随机数和一个随机数预主密钥进行数据处理并加密的结果
4.1.2基于ECDHE密钥交换算法的TLS流程
在使用ECDHE进行密钥协商时,服务器需要通过签名的方式,向客户端证明发送的密钥确实是由服务器生成的,且对应服务器的身份,此处的签名和之前身份验证中对证书内容(有效期、签发者、版本、公钥、地区……)的签名存在区别。但ECDHE算法只保证了前向安全性,其本身并不能进行身份验证。我们单独展示对公钥Q1的验证过程如下图所示:
在ECDHE的密钥协商过程中,最终的会话密钥是两个个随机数和一个经过圆锥曲线计算后的预主密钥进行数据处理并加密的结果
4.2密码套件
密码套件(Cipher_suite)是 TLS 协议中一组用于实现安全通讯的密码学算法,类似于我们前面学习过的加密方案。 不同密码学算法的组合形成不同的密码套件,算法组合的差异使这些密码套件具有不同的性能与安全性,另外 TLS 协议的更新迭代也导致各密码套件拥有不同的兼容性。 通常越新推出的密码套件的安全性越高,但是兼容性就越差(旧设备不支持)。
密码套件的名称由它使用的各种密码学算法名称组成,而且有固定的格式,以上述图中的一个密码套件为例介绍:
- TLS: 定义了此套件适用的协议,通常固定为 TLS
- ECDHE: 密钥交换算法
- RSA: 数字证书认证算法
- AES_128_GCM: 使用的对称加密方案,这是一个基于 AES 与 GCM 模式的对称认证加密方案,使用 128 位密钥
- SHA256: 哈希函数,用于 HMAC 算法实现消息认证。TLS 固定使用 HMAC 算法进行消息认证
4.3密钥协商算法(DH、DHE、ECDHE)
密钥交换算法,常见的有RSA、DH、DHE、ECDHE等,目前支持前向加密的只有ECDHE和DHE算法。 完美前向保密(Perfect Forward Secrecy)是指TLS能够保护过去进行的通讯不受密钥在未来暴露的威胁。 即使攻击者破解出了一个「对称密钥」,也只能获取到一次事务中的数据,其他事务的数据安全性完全不受影响。
Diffie-Hellman系列的密钥协商算法运用了离散对数的原理:定义gcd(a,n)=1,必定存在唯一整数x(0≤x≤Φ(n)),有a≡gx(mod n),则称x是以g为底的a模n的指标或离散对数,简记x = logg(a)。该情况的前提条件是n=1,2,4,pe,2pe,g是原根,此时 Zn∗是循环群,满足Zn∗中的每一个元素都可以通过 gx mod n得到。
4.3.1DH
User1和User2需要先确定模数和底数作为算法的参数,这两个参数是公开的,记为P 和 G 。然后User1和User2各自生成一个随机整数a、b作为私钥。 现在User1和User2可以计算出公钥:
User1的公钥记作 A, A = Ga ( mod P )
User2的公钥记作 B, B = Gb ( mod P )
双方交换各自 DH 公钥后,User1手上共有 5 个数:P、G、a、A、B,User2手上也同样共有 5 个数:P、G、b、B、A。据此,User1和User2可以计算出会话密钥K。根据离散对数的原理,如果 P 是一个大数,在现有的计算机的计算能力是很难破解出私钥 a、b 的,破解不出私钥,也就无法计算出会话密钥,因此 DH 密钥交换是安全的。
DH 交换密钥时就只有客户端(User1)的公钥是变化的,而服务端(User2)公钥是不变的,那么随着时间延长,黑客就会截获海量的密钥协商过程的数据,因为密钥协商的过程有些数据是公开的,黑客就可以依据这些数据暴力破解出User2的私钥b,然后就可以计算出K,于是之前截获的加密数据会被破解。
4.3.2DHE
4.3.3ECDHE
ECDHE 算法是在 DHE 算法的基础上利用了 ECC 椭圆曲线特性,可以用更少的计算量计算出公钥,以及最终的会话密钥。
User1和User2使用 ECDHE 密钥交换算法的过程:
- 双方事先确定好使用哪种椭圆曲线,和曲线上的基点 G,这两个参数都是公开的;
- 双方各自随机生成一个随机数作为私钥d,并与基点 G相乘得到公钥Q(Q = dG),此时User1的公私钥为 Q1 和 d1,User2的公私钥为 Q2 和 d2;
- 双方交换各自的公钥,最后User1计算点(x1,y1) = d1Q2,User2计算点(x2,y2) = d2Q1,由于椭圆曲线上是可以满足乘法交换和结合律,所以 d1Q2 = d1d2G = d2d1G = d2Q1 ,因此双方的 x 坐标是一样的,即预主密钥
4.5TLS实现
TLS协议分为两层,一层是记录层,一层是握手层。
- TLS记录层负责从更高层接收数据并对其进行处理。具体来说,记录层接收要传输的消息,将数据分成可管理的块,可选地压缩数据,应用MAC(消息认证码),加密,然后传输结果。接收到的数据被解密、验证、解压缩、重新组装,然后传递给更高层的客户端。 另外,为了允许TLS协议的扩展,记录层可以支持额外的记录内容类型。
- 在TLST握手层,中有三个子协议使用到了记录: handshake protocol、alert protocol、changecipher spec protocol。
SSL握手协议:包括协商安全参数和密码套件、服务器身份认证(客户端身份认证可选)、密钥交换;
SSL握手密钥参数更换协议:一条消息表明握手协议已经完成;
SSL告警协议:对握手协议中一些异常的错误提醒,分为fatal和warning两个级别,fatal类型的错误会直接中断SSL链接,而warning级别的错误SSL链接仍可继续,只是会给出错误警告;
(1)记录层
记录层将接收到的数据分片成大小不超过2^14字节的TLSPlaintext记录。这个分片过程是为了使数据块更易于管理和传输。重要的一点是,客户端的消息边界在记录层中并不保留,这意味着:
多个相同类型的客户端消息可以合并成一个TLSPlaintext记录。
一个消息可以被分片成多个记录。
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;
(2)握手层
ChangeCipherSpec
更改密码规范协议(Change Cipher Spec Protocol)用于标志加密策略的转换。它通过一个单字节的消息来通知对方,后续的通信将使用新协商的加密算法和密钥。
struct { enum { change_cipher_spec(1), (255) } type; } ChangeCipherSpec;
Alert
警报消息传达消息的严重性(警告或致命)和警报的描述。严重性为致命的警报消息会立即终止连接。
enum { warning(1), fatal(2), (255) } AlertLevel; //warning:警告级别的警报,表示可能存在问题,但不需要立即终止连接。 //fatal:致命级别的警报,表示严重问题,必须立即终止连接。 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;
Handshake
TLS握手协议的任务是生成会话状态的加密参数,该协议在TLS记录层之上运行。当TLS客户端和服务器首次开始通信时,它们会协商协议版本,选择加密算法,可选地相互认证,并使用公钥加密技术生成共享的秘密。
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;
①HelloRequest
HelloRequest消息是由服务器发送给客户端的一个简单通知,指示客户端应该重新开始协商过程。
服务器可以在任何时间发送HelloRequest消息,但不应在客户端初次连接时立即发送。
HelloRequest消息结构非常简单,仅包含一个空的结构体。
struct { } HelloRequest;
②ClientHello
当客户端首次连接到服务器时,必须发送ClientHello作为其第一条消息。客户端还可以在收到HelloRequest后或出于自身需要重新协商现有连接的安全参数时发送ClientHello。
struct { uint32 gmt_unix_time; opaque random_bytes[28]; } Random; enum { null(0), (255) } CompressionMethod; uint8 CipherSuite[2]; /* 加密套件选择器 */ 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;
ClientHello消息包含以下主要部分:
- Random:一个包含当前时间和28字节随机数的结构,用于生成后续的加密参数。
- SessionID:一个可变长度的会话标识符,用于指示客户端希望重用的会话。
- CipherSuite:一个加密套件列表,按客户端的偏好顺序排列。
- CompressionMethod:一个压缩方法列表,按客户端的偏好顺序排列。
- Extensions:一个可选的扩展字段,用于请求服务器提供扩展功能。
除非相应的ClientHello中也出现了相同的扩展类型,否则扩展类型不得出现在ServerHello中。如果客户端在ServerHello中接收到其未在相关ClientHello中请求的扩展类型,则必须通过不支持的扩展致命警报(unsupported_extension fatal alert)中止握手。
struct { ExtensionType extension_type; //标识特定的扩展类型。 opaque extension_data<0..2^16-1>; //包含特定扩展类型的信息。 } Extension; enum { signature_algorithms(13), (65535) } ExtensionType;
③ClientKeyExchange
此消息总是由客户端发送。如果发送了客户端证书消息,它必须紧跟在客户端证书消息之后。否则,它必须是客户端在接收到ServerHelloDone消息后发送的第一条消息。
通过此消息,设置预主密钥,方法是直接传输RSA加密的秘密或传输Diffie-Hellman参数,使双方能够达成相同的预主密钥。
struct { select (KeyExchangeAlgorithm) { case rsa: EncryptedPreMasterSecret;// RSA加密的预主密钥 case dhe_dss: case dhe_rsa: case dh_dss: case dh_rsa: case dh_anon: ClientDiffieHellmanPublic;// 客户端的Diffie-Hellman公钥值 } exchange_keys; } ClientKeyExchange;
- 计算主密钥
对于所有密钥交换方法,使用相同的算法将pre_master_secret转换为master_secret。一旦计算出master_secret,pre_master_secret应从内存中删除。
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)//伪随机函数 [0..47];
- /RSA
当使用RSA进行服务器认证和密钥交换时,客户端生成一个48字节的pre_master_secret,使用服务器的证书中的公钥加密,并发送给服务器。服务器使用其私钥解密得到pre_master_secret。然后双方按照上述规定将pre_master_secret转换为master_secret。此结构是ClientKeyExchange消息的变体,不是独立消息。
PreMasterSecret中的版本号是客户端在ClientHello.client_version中提供的版本,而不是为连接协商的版本。此功能设计用于防止回滚攻击。
struct { ProtocolVersion client_version; opaque random[46]; } PreMasterSecret; struct { public-key-encrypted PreMasterSecret pre_master_secret;//由客户端生成的随机值,用于生成主密钥 } EncryptedPreMasterSecret;
- Diffle-Herman
如果客户端使用了Diffie-Hellman密钥交换方法,客户端需要向服务器传递其Diffie-Hellman公钥值(Yc)。根据情况不同,Yc的传递可以是显式的或隐式的。
enum { implicit, explicit } PublicValueEncoding; //implicit:客户端已经发送了包含Diffie-Hellman密钥的证书,因此不需要再次发送Yc。客户端密钥交换消息将被发送,但内容为空。 //explicit:需要发送Yc。 struct { select (PublicValueEncoding) { case implicit: struct { }; case explicit: opaque dh_Yc<1..2^16-1>; } dh_public;//客户端的Diffie-Hellman公钥值(Yc) } ClientDiffieHellmanPublic;
④CertificateVerify
此消息用于对客户端证书进行显式验证。此消息仅在客户端证书具有签名能力(即,除包含固定Diffie-Hellman参数的证书外的所有证书)后发送。发送时,它必须紧跟在客户端密钥交换消息之后。
struct { digitally-signed struct { opaque handshake_messages[handshake_messages_length]; } } CertificateVerify;
这里的handshake_messages指的是从客户端hello开始到(但不包括)此消息为止发送或接收的所有握手消息,包括握手消息的类型和长度字段。这是迄今为止交换的所有握手结构(如第7.4节所定义)的连接。注意,这要求双方要么缓存消息,要么为所有潜在的哈希算法计算运行哈希,直到CertificateVerify计算时。服务器可以通过在CertificateRequest消息中提供有限的摘要算法集合来最小化此计算成本。
⑤Finished
完成消息(Finished)是TLS握手过程中的最后一个握手消息,用于确认整个握手过程的完整性和正确性。它是第一个使用刚协商的加密算法、密钥和秘密进行保护的消息。
完成消息总是在更改密码规范消息(ChangeCipherSpec)之后立即发送。这个顺序是必须的,因为完成消息是用新协商的加密算法和密钥保护的。
struct { opaque verify_data[verify_data_length]; } Finished; //verify_data是一个通过伪随机函数(PRF)计算的验证数据 PRF(master_secret, finished_label, Hash(handshake_messages))[0..verify_data_length-1]; /* finished_label: 客户端发送的完成消息使用字符串:"client finished"。 服务器发送的完成消息使用字符串:"server finished"。 */ /* handshake_messages: 这是到目前为止交换的所有握手消息的哈希值,不包括HelloRequest消息和记录层头。 */
标签:TLS,加密,证书,消息,客户端,密钥 From: https://www.cnblogs.com/elegantcloud/p/18486971