4 認證加密:順序陷阱、MAC 與 AEAD
本章往密碼學裡多鑽一層:簽章與加密合用時的攻擊、MAC、Encrypt-then-MAC、AEAD。 它解釋的攻擊史很精彩,而且「把身分綁進被保護的內容」這個教訓會在第二部反覆回響—— 但第一次讀跳過它、直接去第 5 章,不影響主線。
前兩章把「驗身分」(簽章)建好了。但很多場景同時需要「保密」——訊息既要別人讀不到, 又要收的人能確認是誰發的。直覺很簡單:簽章和加密都做就好了嘛。
先預測:兩個都做,有兩種順序——先簽再加密,或先加密再簽。你覺得哪個對?
正確答案是本章的第一個關鍵洞見:這兩種天真做法,都有漏洞。
4.1 Part 1:簽章+加密的順序,怎麼選都出事
4.1.1 ① 先簽再加密(Sign-then-Encrypt)→「偷渡轉寄」攻擊
Alice 簽了「msg」→ 加密寄給 Bob
Bob 解密,拿到 msg + Alice 的簽章
Bob 把「msg + Alice 簽章」重新加密,轉寄給 Charlie
→ Charlie 看到:Alice 簽名的訊息,像是 Alice 寄給他的
問題出在:Alice 的簽章只證明「這是 Alice 寫的」,沒說「寫給誰」。 Bob 就能把 Alice 的情書轉給 Charlie、假裝是 Alice 寄給 Charlie 的。 這叫 surreptitious forwarding(偷渡轉寄)。注意 Bob 沒破解任何密碼學—— 每一步的簽章和加密都是真的,被利用的是「簽章的語意涵蓋範圍」。
4.1.2 ② 先加密再簽(Encrypt-then-Sign)→「換簽名」攻擊
Alice 加密出密文 C,對 C 簽名寄出
攻擊者攔到,把 Alice 的簽章撕掉、換上自己的簽章
→ 看起來像「攻擊者加密並寄出了這包密文」
問題出在:簽章綁的是密文,跟「誰知道內容」脫鉤。外層簽名像貼紙一樣可以撕掉重貼, 攻擊者能宣稱一包他根本不知道內容的密文「是他做的」。
4.1.3 真正的修法(本章最重要的一句)
光選順序沒用,要把「身分」綁進被保護的內容裡。Alice 簽的不該只是 msg, 而是 From: Alice, To: Bob, msg。這樣 Bob 想轉寄給 Charlie,內容裡寫著「To: Bob」 對不上收件人,攻擊直接破功。
這是 Don Davis 那篇經典論文《Defective Sign & Encrypt》(Davis 2001年)的結論—— S/MIME、PKCS#7、PGP、XML 簽章,早年全部踩過這個雷。教訓值得放大:
密碼學原語只保護你「餵給它」的 bytes。你沒寫進去的語意(給誰、什麼情境、什麼用途), 它一個 bit 都不保護。
記住這句。第二部談成品簽章時它會回來:光簽「artifact 的 hash」不夠, 要把「哪條 pipeline、哪個 commit、什麼時候 build 的」這些情境(provenance)一起綁進被簽的內容—— 跟 To: Bob 是同一個道理。
4.2 Part 2:對稱世界的認證加密
場景換到對稱加密(AES 這類,收發雙方共享同一把金鑰)。加密給了保密, 但收方怎麼知道密文沒被半路改過?這裡登場的是簽章的表親。
4.2.1 MAC:簽章的「共享金鑰版表親」
MAC(Message Authentication Code,訊息鑑別碼):
- 簽章:私鑰簽、公鑰驗——兩把不同的鑰匙(第 2 章)
- MAC:收發雙方共用同一把秘密金鑰,發方用它對訊息算出一個標籤(tag), 收方用同一把重算一次來比對
MAC 一樣給你完整性+真實性,但有個關鍵差異,先預測:共享金鑰的世界裡, 簽章三保證(真實性、完整性、不可否認)哪一個蒸發了?
金鑰是共享的——你能算的 tag,我也能算。事後你賴帳說「這 tag 是你自己偽造的」, 我無法向第三方反駁。簽章能證明「全世界只有你做得出來」,MAC 只能證明 「是我們兩個之一做的」。最常見的實作:HMAC-SHA256。
4.2.2 三種「加密+MAC」的組合順序,為什麼 EtM 贏
歷史上三大協定各選了一種,剛好湊齊全部選項:
① Encrypt-and-MAC C=Enc(msg), T=MAC(msg) (SSH)
② MAC-then-Encrypt T=MAC(msg), C=Enc(msg‖T) (舊 TLS)
③ Encrypt-then-MAC C=Enc(msg), T=MAC(C) (IPsec)✅
差別全部集中在一件事:能不能「先驗證、再決定要不要解密」。
- ①② 的 tag 都是對明文算的——收方得先解密才拿得到/驗得了 tag。 等於讓攻擊者控制的髒資料,先跑進你的解密程序才被檢查。
- ③ 的 tag 是對密文算的——收方可以先驗 tag,失敗就整包丟掉,根本不碰解密。
這條「驗證先於解密(verify-before-decrypt)」原則就是 EtM 的全部價值。 它把一整類側通道攻擊(padding oracle、Lucky13 這種靠「觀察解密過程的反應」洩密的手法) 從源頭掐掉——偽造的密文連被解密的機會都沒有。舊 TLS 選了 MAC-then-Encrypt, 後來就是被 padding oracle 系列打到棄守。
理論也背書:Bellare–Namprempre(2000)(Bellare 和 Namprempre 2000年)證明三者中只有 EtM 能同時拿到最強的安全性組合(IND-CCA+密文完整性 INT-CTXT)。工程直覺(先驗再解) 和理論證明(EtM 通吃)在這裡指向同一個答案——遇到這種收斂,通常表示你站在對的地方。
4.2.3 幾個真的會害你出事的坑
就算選對 EtM,手刻仍有雷:
- MAC 要蓋住全部:IV/nonce+密文+標頭都要進 MAC。只 MAC 密文本體、漏掉 IV → 攻擊者翻轉 IV 的 bit 就能翻轉解密後明文的第一個 block。
- 比對 tag 要常數時間:Java 用
MessageDigest.isEqual;普通的equals逐 byte 短路比對,比對時間洩漏「前幾個 byte 對了幾個」,可以被逐位猜出來。 - 加密和 MAC 用不同金鑰(或經 KDF 分流),別共用同一把。
4.2.4 現代結論:別手刻,用 AEAD
上面每個坑都有屍體。所以現代密碼學的答案是把「EtM 做對的全部細節」封裝成一個原語: AEAD(Authenticated Encryption with Associated Data,帶關聯資料的認證加密)。
- 代表:AES-GCM、ChaCha20-Poly1305——內部就是「EtM 做對」的一體成型封裝。
- AD(Associated Data,關聯資料)=「要驗證但不加密」的欄位。例如封包標頭—— 或者 Part 1 那個
To: Bob:綁進認證範圍、竄改必被抓,但保持明文可讀。 AEAD 直接把「把情境綁進保護範圍」做成了 API 的一級公民。
一句話收尾:EtM 的靈魂是「先驗證、後解密」;而 2020 年代的你, 直接用 AES-GCM 就等於免費拿到它。
本章帶走的東西:簽章+加密的兩種天真順序都有洞,修法不是選順序、是把身分與情境 綁進被保護的內容;MAC=簽章的共享金鑰表親,但沒有不可否認;EtM 贏在 「驗證先於解密」;實務直接用 AEAD,並把該綁的情境放進 AD。
往下一章的橋:到這裡,三個原語全部到齊——hash、簽章、MAC,加上把它們組裝起來的 PKI 與 AEAD。但工具箱擺滿不等於會用:每個原語各自「給什麼、不給什麼」? 序章那六個攻擊面,哪些是原語能堵的、哪些原語根本無能為力?下一章把整個第一部 收攏成一張能力邊界表——它就是第二部走進真實供應鏈時,你手上的地圖。