為了保護客戶端與伺服器之間的通訊,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.01999最不安全的 TLS 版本,但仍比 SSL 3.0 安全
TLS 1.12006較好,但包含若干今日已知很弱的演算法
TLS 1.22008又更好,但複雜,只有在正確設定時才有高安全性(而正確設定並非易事)
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 協定,但記錄協定與握手是分不開的

握手的流程概觀:

  1. 客戶端送出一則稱為 ClientHello 的初始訊息,其中包含它想使用的密碼等參數。
  2. 伺服器檢查這則訊息與其參數,以一則稱為 ServerHello 的訊息回應。
  3. 雙方處理完彼此的訊息後,就準備好用握手協定所建立的工作階段金鑰來交換加密資料。

憑證與憑證機構#

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 BeforeNot 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_ivserver_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/ 的安全存取:

  1. 你把瀏覽器(客戶端)指向該站,瀏覽器送出含有它所支援密碼的 ClientHello
  2. 網站以 ServerHello 與一份包含 www.nostarch.com 網域相關公鑰的憑證回應。
  3. 客戶端用瀏覽器內嵌的某個憑證機構驗證憑證的有效性(收到的憑證應由受信任的 CA 簽署,該 CA 的憑證應包含在瀏覽器的憑證庫中才能通過驗證)。
  4. 所有檢查通過後,瀏覽器就向伺服器請求網站的首頁。

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 曲線,以及 Curve25519Curve448(兩者定義於 RFC 7748)。
  • 支援的整數群:RFC 7919 定義的五個群——2048、3072、4096、6144 與 8192 位元。

其他選項至少提供 128 位元安全性,而 2048 位元 Diffie–Hellman 被認為提供不到 100 位元的安全性。

支援 2048 位元的群,因此可以被視為與 TLS 1.3 其他設計選擇不一致