HTTPS的’S’

几个问题

关闭浏览器tab页面能不能断开tcp连接,tcp连接的断开时机?

为什么一个http(s)请求会建立两条tcp连接?

多次http(s)请求会复用同一条tcp连接吗?

为什么服务器发起断开连接后,客户端不发送FIN?

https建立连接需要几次握手?

tls的加密算法/对称加密秘钥是由客户端还是服务端确定的?

tls是怎么防止中间人攻击的?

tcp断开连接一定会4次挥手吗 ?

为什么keepalive无效?

回顾TCP握手

client -> server:SYN seq=0
server -> client:SYN ACK ack=1 seq=0
client -> server:ACK ack=1 seq=1

HTTPS的’S’

S = Secure

对称加密与非对称加密

远古以前用的是对称加密,这种加密和解密的密钥是同一个,因此极为不安全,一旦其中一个密钥泄露,那么就会导致加密文件被破解

后来发明了非对称加密,这种加密方式的逻辑就是公钥用来加密,私钥用来解密,一方使用另一方的公钥进行加密,传输密文给另一方,另一方再用私钥进行解密,这样没有拿到私钥就无法解密密文。但这种加密解密效率较低,后来就衍生出了混合加密,这种加密方式综合了非对称加密和对称加密。

混合加密首先使用非对称加密生成对称密钥,然后再使用对称密钥进行数据安全传输,这样加密解密的效率要高得多了。

几个算法

RSA

欧拉函数

p、q互质

n=p*q
$$
\varphi(n)=(p-1)(q-1)
$$

欧拉定理

image-20221121221720654

DH

Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度。简言之,可以如下定义离散对数:首先定义一个素数p的原根,为其各次幂产生从1 到p-1的所有整数根,也就是说,如果a是素数p的一个原根,那么数值

              a mod p, a2 mod p,…,ap-1 mod p

是各不相同的整数,并且以某种排列方式组成了从1到p-1的所有整数。

对于一个整数b和素数p的一个原根a,可以找到惟一的指数i,使得

              b=a^i mod p, 0<=i<=p-1

指数i称为b的以a为基数的模p的离散对数或者指数。该值被记为inda ,p(b)。

基于此背景知识,可以定义Diffie-Hellman密钥交换算法。该算法描述如下:

1、有两个全局公开的参数,一个素数q和一个整数a,a是q的一个原根。

2、假设用户A和B希望交换一个密钥,用户A选择一个作为私有密钥的随机数XA<q,并计算公开密钥YA=a^XA mod q。A对XA的值保密存放而使YA能被B公开获得。类似地,用户B选择一个私有的随机数XB<q,并计算公开密钥YB=a^XB mod q。B对XB的值保密存放而使YB能被A公开获得。

3、用户A产生共享秘密密钥的计算方式是K = (YB)^XA mod q。同样,用户B产生共享秘密密钥的计算是K = (YA)^XB mod q。

ECC

在线椭圆曲线

https://www.desmos.com/calculator?lang=zh-CN

y^2 = x^3 + ax + b

image-20221121222255826

定义加法的阿贝尔群

连续的不能用,需要使用离散的点,质数阶(P)的有限域上的点用来做密钥共享

ECC推荐参数:256k1

p=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
a=0000000000000000000000000000000000000000000000000000000000000000
b=0000000000000000000000000000000000000000000000000000000000000007
G=(79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
n=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

ECC推荐参数:256r1

p=FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
a=FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
b=5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
G=(6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5)
n=FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
secp256r1使用的参数如下:

使用的素数p:

p=FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
=2224(232−1)+2192+296−1

椭圆曲线E:y^2=x^3+ax+b的参数定义如下:

a=FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
b=5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B

基点G的非压缩格式:

G=046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5

有限域的阶n:
n=FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551

辅助因子h:
h=01

假设要加密的数据为m,把这个点当作x坐标得到在曲线上的一个点M,取定一个随机数r,计算点C1 = rG,C2 = M + rK,把这两个点便是加密后的数据,发给对方,对方收到后使用私钥k进行解密,过程如下:

M = C2 – rK = C2 – rkG = C2 – rkG = C2 – kC1

关于迪菲 - 赫尔曼密钥交换算法,这里简单提一下,这种算法有几个特点:

  1. 密钥可以合成但不能分解
  2. 合成后的密钥可以继续再合成
  3. 合成后的密钥结果与合成顺序无关
  4. 本质上是一种生成密钥算法

这个算法的加密流程是这样的:

  • A 和 B 想要交换并生成对称密钥
  • A 将 P 发送给 B
  • A 使用 P 和自己的私钥 SA 合成 PSA
  • B 使用 P 和自己的私钥 SB 合成 PSB
  • 双方交换 PSA 和 PSB
  • A 使用私钥 SA 与 PSB 合成 PSBSA
  • B 使用私钥 SB 与 PSA 合成 PSASB

这样对称密钥 PSASB 和 PSBSA 就合成完毕了,双方就可以使用这个对称密钥对传输的数据进行加密 🔑

⚠️ 问题是 A 将 P 发送给 B,B 如何确认 P 就是真的来自 A 的呢,而不是受到中间人攻击篡改了 P,这就需要使用 CA 认证了,只要有 CA 认证后的数字签名并且通过 CA 的公钥能够验证那么就可以确认 P 就是来自 A 的。

数字证书

CA原理:

一个网站 A 想要获取数字证书,那么要走如下流程:

  • A 将网站信息和公钥 PA 发送给 CA 认证机构
  • CA 认证机构验证网站信息并用自己的私钥 SCA 加密生成数字签名,并回传给 A
  • A 收到的数字签名中就包含了 A 的公钥 PA,那么其他人只需要通过 CA 认证机构的公钥即可认证解密数字签名,获取数字签名中的公钥 PA 了
  • 那么其他人就可以确定 PA 确实是来自 A 的

这样 A 的公钥 PA 就可以被认为确实是来自 A 的,因为有 CA 签发的数字证书认证和背书。 🥂

⚠️ 还有一个问题是 CA 的公钥谁来认证呢?要是 CA 的公钥也被恶意替换了怎么办?那么就需要 CA 的 CA 来认证了,那么 CA 的 CA 的公钥谁来认证?根据这个逻辑,最后有一个 root CA,这个 CA 用来做最后的权威鉴定。

身份验证

RSA签名

密码学常见算法

  1. 块加密算法 block cipher: AES, Serpent, 等
  2. 流加密算法 stream cipher: RC4,ChaCha20 等
  3. Hash函数 hash funtion:MD5,sha1,sha256,sha512 , ripemd 160,poly1305 等
  4. 消息验证码函数 message authentication code: HMAC-sha256,AEAD 等
  5. 密钥交换 key exchange: DH,ECDH,RSA,PFS方式的(DHE,ECDHE)等
  6. 公钥加密 public-key encryption: RSA,rabin-williams 等
  7. 数字签名算法 signature algorithm:RSA,DSA,ECDSA (secp256r1 , ed25519) 等
  8. 密码衍生函数 key derivation function: TLS-12-PRF(SHA-256) , bcrypto,scrypto,pbkdf2 等
  9. 随机数生成器 random number generators: /dev/urandom 等

TLS CipherSuite

  • 1.对称加密传输组件,例如aes-128-gcm(这几个例子都是当前2015年最主流的选择);
  • 2.认证密钥协商组件,例如rsa-ecdhe;
  • 3.密钥扩展组件,例如TLS-PRF-sha256

协议分层

TLS是用来做加密数据传输的,因此它的主体当然是一个对称加密传输组件。为了给这个组件生成双方共享的密钥,因此就需要先搞一个认证密钥协商组件,故,TLS协议自然分为:

  1. 做对称加密传输的record协议 ,the record protocol
  2. 做认证密钥协商的handshake协议,the handshake protocol

还有3个很简单的辅助协议:

  1. changecipher spec 协议,the changecipher spec protocol, 用来通知对端从handshake切换到record协议(有点冗余,在TLS1.3里面已经被删掉了)

  2. alert协议,the alert protocol, 用来通知各种返回码,

  3. application data协议, The application data protocol,就是把http,smtp等的数据流传入record层做处理并传输。

这种 认证密钥协商 + 对称加密传输 的结构,是绝大多数加密通信协议的通用结构,在后文的更多协议案例中,我们可以看到该结构一再出现。

这5个协议中: record协议在tcp流上提供分包, 图片来自网络:

image-20221122105012544

record 协议

record协议做应用数据的对称加密传输,占据一个TLS连接的绝大多数流量,因此,先看看record协议 图片来自网络:

img

Record 协议 – 从应用层接受数据,并且做:

  1. 分片,逆向是重组
  2. 生成序列号,为每个数据块生成唯一编号,防止被重放或被重排序
  3. 压缩,可选步骤,使用握手协议协商出的压缩算法做压缩
  4. 加密,使用握手协议协商出来的key做加密/解密
  5. 算HMAC,对数据计算HMAC,并且验证收到的数据包的HMAC正确性
  6. 发给tcp/ip,把数据发送给 TCP/IP 做传输(或其它ipc机制)。

SecurityParameters

record层的上述处理,完全依据下面这个SecurityParameters里面的参数进行:

   struct {
          ConnectionEnd          entity;
          PRFAlgorithm           prf_algorithm;
          BulkCipherAlgorithm    bulk_cipher_algorithm;
          CipherType             cipher_type;
          uint8                  enc_key_length;
          uint8                  block_length;
          uint8                  fixed_iv_length;
          uint8                  record_iv_length;
          MACAlgorithm           mac_algorithm;
          uint8                  mac_length;
          uint8                  mac_key_length;
          CompressionMethod      compression_algorithm;
          opaque                 master_secret[48];
          opaque                 client_random[32];
          opaque                 server_random[32];
      } SecurityParameters;

TLS加密流程

client -> server:client hello 发送支持的(cipher suite)加密套件以及一个随机数
server -> client:server hello 发送server端的cipher suite,随机数
server -> client: CA认证的数字证书 \n server key exchange \n server hello done:表示第一阶段客户端与服务端握手协商结束
client -> server:client key exchange \n change cipher spec \n client finish 生成 pre-master Key 随机数,用服务端公钥加密
server -> client: new session ticket \n change cipher spec \n server finish

服务端选中的加密套装叫TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,这一串的意思是:

(1)密钥交换使用ECDHE

(2)证书签名算法RSA

(3)数据加密使用AES 128 GCM

(4)签名校验使用SHA256

Client Hello

在 TLS 握手中,总是以客户端的 ClientHello 为起始,就像 TCP 握手总是以 SYN 为起始一样,告诉服务器我们想建立一个 TLS 链接。在 ClientHello 请求的结构如下:

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;·

Server Hello

服务器在收到 ClientHello 后必须回应 ServerHello 进行下一步握手协商,否则就需要回复 Alert 类型的 Record 告诉客户端中断握手并关闭连接。

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

Server Certificate

随后服务端接着返回自身的证书信息,X509 格式,使用 ASN.1 DER 编码。通常服务器会返回多个证书,因为当前域名往往不是由根证书直接签名的,而是使用由于根证书所签名的次级证书去签发具体域名的证书。

如果使用了多级证书,那么返回的证书列表中第一个必须是对应域名的证书,而后每个证书都是前一个证书的 issuer,且最后一个证书是由系统中某个根证书签发的,注意根证书本身并不会一起返回。

Server Key Exchange

服务器会在 server Certificate 消息之后发送 Server Key Exchange 消息,提供用于 ECDH 秘钥交换的信息。

关于 ECDH 的原理可以阅读文章开头的参考文章,简单来说,ECDH 可以在通信媒介不可信的情况下安全地完成秘钥交换,假设 A、B 双方的公私钥分别是 PA、SA,PB、SB,那么有:PA * SB == PB * SA

双方只需要知道对方的公钥,可以在不暴露私钥的情况下实现信息的交换,防止中间人攻击,所交换的信息就是后续使用的对称加密秘钥。

更进一步,为了避免未来私钥泄露导致以前的通信被解密,通常交换时并不直接使用原始公私钥,而是一个随机生成的新公私钥对,只需要用原始私钥进行认证。这种交换方式也称为 ECDHE,其中 E 表示 Ephemeral,而这种做法所带来的称为 Forward Security,即前向安全。

消息中包含椭圆曲线参数以及 ClientRandom+ServerRandom+参数 的签名信息。

Server Hello Done

服务端表示自己这一边的握手已经完成,接下来就等待客户端计算相关握手信息了。

Client Key Exchange

与Server Key Exchange一样。注意此处与 Server Key Exchange 不同,并没有对客户端的公钥进行签名,也就是说可以被中间人进行替换。不过协议设计的时候已经考虑到了这一点,因为此时双方已经有足够的信息去协商秘钥并且进行验证了,通过后文的计算过程也可以确认这一点。

Client Change Cipher Spec

该数据包告诉服务器客户端已经计算好了共享秘钥,并且后续客户端发送给服务器的数据都将使用共享秘钥进行加密。在下一个版本的 TLS 中该数据包类型将会被移除,因为加密数据是可以通过数据类型推断的。

  • client_random
  • server_random
  • server-ephemeral-public.key
  • client-ephemeral-private.key

通过对方的公钥和自己的私钥,可以计算出一个共同秘钥,称之为 PMS(Pre-Master-Secret)

$ ./curve25519-mult client-ephemeral-private.key \
                    server-ephemeral-public.key | hexdump

服务器计算出来的PMS是一样的

该共享秘钥计算过程只涉及自身私钥和对方的公钥,为了进一步将共享秘钥关联当当前会话中,需要为其加入双方的随机数,当然不能直接相加,需要增加随机性,因此使用到了一个伪随机函数,称为 PRF(pseudorandom function)。其计算方式如下:

seed = "master secret" + client_random + server_random
a0 = seed
a1 = HMAC-SHA256(key=PreMasterSecret, data=a0)
a2 = HMAC-SHA256(key=PreMasterSecret, data=a1)
p1 = HMAC-SHA256(key=PreMasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=PreMasterSecret, data=a2 + seed)
MasterSecret = p1[all 32 bytes] + p2[first 16 bytes]

最后,我们需要将该主密钥进行拓展(至任意长度),并将结果的不同部分分别用作不同秘钥,如下所示:

seed = "key expansion" + server_random + client_random
a0 = seed
a1 = HMAC-SHA256(key=MasterSecret, data=a0)
a2 = HMAC-SHA256(key=MasterSecret, data=a1)
a3 = HMAC-SHA256(key=MasterSecret, data=a2)
a4 = ...
p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
p2 = HMAC-SHA256(key=MasterSecret, data=a2 + seed)
p3 = HMAC-SHA256(key=MasterSecret, data=a3 + seed)
p4 = ...
p = p1 + p2 + p3 + p4 ...
client write mac key = [first 20 bytes of p]
server write mac key = [next 20 bytes of p]
client write key = [next 16 bytes of p]
server write key = [next 16 bytes of p]
client write IV = [next 16 bytes of p]
server write IV = [next 16 bytes of p]

最终秘钥分成了 6 个部分,分别是客户端和服务端的 MAC 秘钥、数据加密秘钥和初始向量。

Client Handshake Finished

该数据包告诉服务器,客户端的握手流程也已经完成。同时,还携带了一部分加密数据,所加密的内容称为 Verify Data,用以验证握手成功且没有被中间人修改过。

Verify Data 的内容是该消息之前的所有握手包的 HASH 经过 HMAC 计算出来的一个 12 字节数据,使用 client_write_key 进行加密,服务端收到后使用对应的秘钥进行解密,所使用加解密算法由之前协商的加密套件决定

Server Change Cipher Spec

为了进行验证,服务端使用相同的方式计算出共享秘钥 Pre Master Secret,由 ECDH 的特性以及前文的计算可以看到,服务端和客户端计算出的 PMS 是相同的,因衍生出来的对称加密秘钥、IV、MAC 秘钥也是相同的。

故,服务端收到加密数据后,可以使用协商出来的 client_write_key 对其进行解密。末尾还有 32 字节的数据,是使用 client mac key 计算的签名,用于确保所接收数据的完整性。

Server Handshake Finished

此时,服务端已经完成了握手的所有流程,并且也确认这个握手流程没有被中间人篡改,但是客户端还不知道啊!因此,类似于 Client Handshake Finished,服务端也要发送一个加密并验签的数据给客户端,让客户端进行验证并确认整个握手流程的正确性。

发送的数据格式和 Client Finished 几乎一样,只有几点小差异。比如数据使用 server_write_key 进行加密(而不是 client_write_key),HMAC key 也是类似。另外计算 verify_data 与前者相比还多了一个 Client Finished 消息,毕竟协议中说的是用于验证 “当前消息前的所有握手消息”。

客户端收到 Server Finished 后,同样进行解密并校验 HMAC,如果确认无误就可以开始发送应用数据了。

Application Data

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

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

实战

对https://httpbin.org/json抓包

image-20221119135405134

参考资料:TLS协议分析 与 现代加密通信协议设计

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
下一篇