為了保護客戶端與伺服器之間的通訊,TLS 由數個協定的多個版本組成,統稱 TLS 協定套組。
儘管 TLS 的意思是「傳輸層安全」,它其實不是傳輸協定。
TLS 通常位於傳輸協定 TCP 與 HTTP、SMTP 這類應用層協定之間,用來保護透過 TCP 連線傳輸的資料。
TLS 也能跑在 UDP 之上——那是用於語音或視訊流量這類「無連線」傳輸的協定。但與 TCP 不同,UDP 不保證送達或封包順序正確,因此 TLS 的 UDP 版本略有不同,稱為 DTLS(Datagram Transport Layer Security)。
簡史:從 SSL 到 TLS 1.3#
TLS 的生命始於 1995 年,當時 Netscape 瀏覽器的開發者 Netscape 開發了 TLS 的祖先——SSL(Secure Socket Layer)協定。
SSL 遠稱不上完美,SSL 2.0 與 SSL 3.0 都有安全瑕疵。
結論:永遠不要使用 SSL,永遠使用 TLS。
更添混亂的是,TLS 經常被稱為「SSL」——連安全專家也這麼叫。
而且並非所有 TLS 版本都安全:
| 版本 | 年份 | 評價 |
|---|---|---|
| TLS 1.0 | 1999 | 最不安全的 TLS 版本,但仍比 SSL 3.0 安全 |
| TLS 1.1 | 2006 | 較好,但包含若干今日已知很弱的演算法 |
| TLS 1.2 | 2008 | 又更好,但複雜,只有在正確設定時才有高安全性(而正確設定並非易事) |
| TLS 1.3 | — | 成熟的 TLS |
TLS 1.2 的複雜度增加了實作出現 bug 與設定錯誤的風險。
例如它支援 CBC 模式的 AES——那經常易受填充預言機攻擊。
TLS 1.2 從更早的版本繼承了數十個功能與設計選擇,使它在安全性與效能上都不夠理想。
為了清理這團混亂,密碼工程師重新發明了 TLS——只保留好的部分並加入安全特性。
成果就是 TLS 1.3:一次徹底翻修,簡化了臃腫的設計,讓它更安全、更高效、更簡單。
TLS 的兩大協定#
TLS 有兩個主要協定:一個決定如何傳輸資料,另一個決定傳輸什麼資料。
- 記錄協定(record protocol):定義一個封包格式,把來自更高層協定的資料封裝起來送給對方。這是個簡單的協定,人們常常忘記它也是 TLS 的一部分。
- 握手協定(handshake protocol,或就叫 handshake):TLS 的金鑰協商協定。
握手協定常被誤認為「就是」TLS 協定,但記錄協定與握手是分不開的。
握手的流程概觀:
- 客戶端送出一則稱為 ClientHello 的初始訊息,其中包含它想使用的密碼等參數。
- 伺服器檢查這則訊息與其參數,以一則稱為 ServerHello 的訊息回應。
- 雙方處理完彼此的訊息後,就準備好用握手協定所建立的工作階段金鑰來交換加密資料。
憑證與憑證機構#
TLS 握手中最關鍵的一步、也是 TLS 安全性的核心,就是憑證驗證——伺服器用憑證向客戶端鑑別自己。
憑證(certificate)本質上是一把公鑰,加上該金鑰的簽章與相關資訊(包括網域名稱)。
例如連到 https://www.google.com/ 時,你的瀏覽器會從某個網路主機收到一份憑證,然後驗證該憑證的簽章——內容大意是「我是 google.com,我的公鑰是 [key]」。若簽章通過驗證,該憑證(與其公鑰)就被稱為受信任的,瀏覽器就能繼續建立連線。
憑證機構(CA)#
那瀏覽器怎麼知道驗證簽章所需的公鑰?這就是憑證機構(CA,certificate authority)概念的用武之地。
該公鑰對應的私鑰(也就是簽署能力)屬於一個受信任的組織,該組織確保它所簽發之憑證中的公鑰,確實屬於宣稱擁有它們的網站或實體。CA 扮演的是受信任第三方的角色。
沒有 CA,就沒有辦法驗證 google.com 提供的公鑰屬於 Google,而非某個執行中間人攻擊的竊聽者。
憑證鏈#
用 OpenSSL 命令列工具連到 www.google.com 的 443 埠(TLS 型 HTTP 連線所用的網路埠,也就是 HTTPS):
$ openssl s_client -connect www.google.com:443
CONNECTED(00000003)
--snip--
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
i:/C=US/O=Google Inc/CN=Google Internet Authority G2
1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIISCr6QCbz5rowDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
--snip--
-----END CERTIFICATE-----解讀方式:
- 以
s:開頭的行描述主體名稱(subject),以i:開頭的行描述簽章的簽發者(issuer)。 - 憑證 0 是 google.com 收到的那份。
- 憑證 1 屬於簽署憑證 0 的實體。
- 憑證 2 屬於簽署憑證 1 的實體。
簽發憑證 1 的組織(GeoTrust)授權 Google Internet Authority 為網域
www.google.com簽發憑證(憑證 0),把信任轉移給了 Google Internet Authority。
顯然這些 CA 組織必須值得信賴、只對值得信賴的實體簽發憑證,而且必須保護好自己的私鑰——以防攻擊者代表他們簽發憑證(例如為了冒充一台合法的 google.com 伺服器)。
憑證的內容#
$ openssl x509 -text -noout
-----BEGIN CERTIFICATE-----
--snip--
-----END CERTIFICATE-----
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 5200243873191028410 (0x482afa4026f3e6ba)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Google Inc, CN=Google Internet Authority G2
Validity
Not Before: Dec 15 14:07:56 2016 GMT
Not After : Mar 9 13:35:00 2017 GMT
Subject: C=US, ST=California, L=Mountain View, O=Google Inc,
CN=www.google.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:bc:bc:b2:f3:1a:16:3b:c6:f6:9d:28:e1:ef:8e:
--snip--
Exponent: 65537 (0x10001)
--snip--
Signature Algorithm: sha256WithRSAEncryption
94:cd:66:55:83:f1:16:7d:46:d8:66:21:06:ec:c6:9d:7c:1c:
--snip--一份憑證裡包含:序號與版本資訊、識別資訊、有效期(Not Before 與 Not After)、公鑰(此處是 RSA 模數與其公開指數),以及對前述所有資訊的簽章。
儘管安全專家與密碼學家常宣稱整個憑證系統在設計上就是壞的,它仍是我們手上最好的解法之一——與 SSH 採用的「首次使用即信任」(TOFU,trust-on-first-use)政策並列。
記錄協定#
TLS 1.3 通訊中交換的所有資料,都以 TLS 記錄(TLS records)序列的形式傳輸。
記錄協定先用來承載握手期間交換的資料;握手完成、雙方共享秘密金鑰之後,應用資料就被切成片段,作為 TLS 記錄的一部分傳輸。
記錄的結構#
一個 TLS 記錄是最大 16 KB 的資料塊,結構如下:
| 位元組 | 名稱 | 內容 |
|---|---|---|
| 第 1 個 | ContentType | 傳輸資料的型態:22 = 握手資料、23 = 加密資料、21 = 警示 |
| 第 2、3 個 | ProtocolVersion | 固定為 3 與 1(歷史原因,非 TLS 1.3 專屬) |
| 第 4、5 個 | — | 以 16 位元整數編碼待傳資料的長度,最大 2^14 位元組(16 KB) |
| 其餘 | payload | 待傳輸的資料 |
TLS 記錄的結構相對簡單——標頭只有三個欄位。
作為對照:一個 IPv4 封包在其 payload 之前有 14 個欄位,一個 TCP 區段有 13 個欄位。
當
ContentType為 23 時,payload 會用鑑別式密碼加密與鑑別,內容是密文後接鑑別標籤。那接收方怎麼知道要用哪個密碼與金鑰解密? 這就是 TLS 的魔法:若你收到一則加密的 TLS 記錄,你早就知道密碼與金鑰了——因為它們在執行 TLS 握手協定時就已建立。
Nonce#
與 IPsec 的 ESP 等許多其他協定不同,TLS 記錄不指定鑑別式密碼所要使用的 nonce。
加解密 TLS 記錄所用的 nonce 是從 64 位元序號導出的——序號由各方在本地維護,每個新記錄就遞增:
- 客戶端加密資料時,把序號與一個從共享秘密導出的值
client_write_iv做 XOR 來導出 nonce。 - 伺服器用類似方法,但使用另一個值
server_write_iv。
例如你傳輸三個 TLS 記錄,就分別從 0、1、2 導出 nonce;接著若你收到三個記錄,也依序使用 nonce 0、1、2。
「加密送出的資料」與「解密收到的資料」使用相同的序號值並不是弱點——因為它們被與不同的常數(
client_write_iv與server_write_iv)XOR,而且兩個方向使用不同的秘密金鑰。
零填充#
TLS 1.3 記錄支援一個很好的功能:零填充(zero padding),用來緩解流量分析攻擊。
流量分析是攻擊者利用時序、傳輸資料量等流量樣式來萃取資訊的方法。
例如由於密文大小與明文大致相同,即使使用了強加密,攻擊者仍能只憑觀察密文長度就判定你訊息的大致大小。
零填充在明文後加上零來膨脹密文的大小,藉此欺騙觀察者,讓他們以為一則加密訊息比實際上更長。
握手協定#
握手是 TLS 的關鍵協商協定——客戶端與伺服器藉此建立共享秘密金鑰以啟動安全通訊。
握手過程中雙方扮演不同角色:客戶端提出一些組態(TLS 版本與一組依偏好排序的密碼),伺服器選擇要使用的組態。伺服器應該遵從客戶端的偏好,但也可以不遵從。
TLS 1.3 規格也描述了資料該以什麼格式送出,以確保實作之間的互通性——保證任何實作 TLS 1.3 的伺服器,都能讀取任何實作 TLS 1.3 之客戶端送出的資料,即使它們使用不同的函式庫或程式語言。
完整流程#
客戶端 伺服器
產生金鑰對 (c, C = cQ)
ClientHello ──→
- 支援的密碼 產生金鑰對 (s, S = sQ)
- 公鑰 C 計算 secret = DH(s, C)
導出 keys = KDF(secret)
←── ServerHello
- 選定的密碼
- 公鑰 S
←── Certificate(憑證)
驗證憑證 ←── Signature(對 ClientHello、
驗證簽章 ServerHello、憑證的簽章)
計算 secret = DH(c, S)
導出 keys = KDF(secret) ←── MAC(對 ClientHello、ServerHello、
用 keys 驗證 MAC 憑證、簽章的 MAC)
圖 13-1:連線到 HTTPS 網站時的 TLS 1.3 握手流程
ClientHello 的內容相當於說:「我想與你建立 TLS 連線。這些是我支援的、用來加密 TLS 記錄的密碼,這是一把 Diffie–Hellman 公鑰。」
這把公鑰必須專為這次 TLS 工作階段產生,客戶端保留對應的私鑰。
這則訊息還包含一個 32 位元組的隨機值與選用資訊。
ServerHello 則載滿了資訊:
- 用來加密 TLS 記錄的密碼
- 一把 Diffie–Hellman 公鑰
- 一個 32 位元組隨機值(見「降級保護」)
- 一份憑證
- 對 ClientHello 與 ServerHello 中所有先前資訊的簽章(用憑證公鑰所對應的私鑰計算)
- 對上述同樣資訊加上該簽章的 MAC
MAC 是用一把從 Diffie–Hellman 共享秘密導出的對稱金鑰計算的——伺服器從自己的 DH 私鑰與客戶端的公鑰算出該共享秘密。
客戶端收到 ServerHello 後:驗證憑證的有效性、驗證簽章、計算共享的 DH 秘密並從中導出對稱金鑰、驗證伺服器送來的 MAC。一切驗證完畢,客戶端就準備好送出加密訊息給伺服器。
TLS 1.3 支援許多選項與擴充,因此行為可能與此處描述不同。
例如你可以設定 TLS 1.3 握手要求客戶端憑證,讓伺服器驗證客戶端的身分。TLS 1.3 也支援使用預共享金鑰的握手。
實際情境#
假設你部署 TLS 1.3 來提供 https://www.nostarch.com/ 的安全存取:
- 你把瀏覽器(客戶端)指向該站,瀏覽器送出含有它所支援密碼的 ClientHello。
- 網站以 ServerHello 與一份包含
www.nostarch.com網域相關公鑰的憑證回應。 - 客戶端用瀏覽器內嵌的某個憑證機構驗證憑證的有效性(收到的憑證應由受信任的 CA 簽署,該 CA 的憑證應包含在瀏覽器的憑證庫中才能通過驗證)。
- 所有檢查通過後,瀏覽器就向伺服器請求網站的首頁。
TLS 1.3 握手成功後,客戶端與伺服器之間的所有通訊都被加密與鑑別。
竊聽者能得知某個 IP 位址的客戶端正在與另一個 IP 位址的伺服器對話,也能觀察到交換的加密內容,但無法得知底層的明文,也無法修改加密訊息——若他們修改了,接收方會注意到通訊遭到竄改,因為訊息不只被加密,也被鑑別了。
TLS 1.3 使用的密碼演算法#
鑑別式密碼#
TLS 1.3 只支援三種演算法:
- AES-GCM
- AES-CCM(一個比 GCM 略沒效率的模式)
- ChaCha20 串流密碼結合 Poly1305 MAC(定義於 RFC 7539)
由於 TLS 1.3 不允許你使用 64 或 80 位元這類不安全的金鑰長度,秘密金鑰只能是 128 位元(AES-GCM 或 AES-CCM)或 256 位元(AES-GCM 或 ChaCha20-Poly1305)。
金鑰衍生#
KDF 基於 HKDF——一個建立在 HMAC 之上、定義於 RFC 5869 的建構,使用 SHA-256 或 SHA-384。
Diffie–Hellman#
執行 DH 運算(TLS 1.3 握手的核心)的選項僅限於橢圓曲線密碼學與質數模下的整數乘法群(如傳統 DH)。但你不能用任意曲線或群:
- 支援的曲線:三條 NIST 曲線,以及 Curve25519 與 Curve448(兩者定義於 RFC 7748)。
- 支援的整數群:RFC 7919 定義的五個群——2048、3072、4096、6144 與 8192 位元。
其他選項至少提供 128 位元安全性,而 2048 位元 Diffie–Hellman 被認為提供不到 100 位元的安全性。
支援 2048 位元的群,因此可以被視為與 TLS 1.3 其他設計選擇不一致。