7.28 密碼學原語選型:金鑰位置決定威脅模型
本章的標題把金鑰位置與威脅模型綁在一起,而威脅模型這個框架本身(資產、邊界、攻擊者能力假設三者並排列舉)由那張卡承載,本章只處理金鑰位置這一條軸。
選錯原語的代價落在一個沒有訊號的位置:機制照常運作、測試照常通過,而它防的根本不是那件事。這個狀態要等資料真的外流才會浮現,所以選型階段要把加密、訊息驗證與可逆編碼各自對應到明確的威脅與失效條件。
本章涵蓋與不涵蓋
本章涵蓋四類原語,而判讀主軸「金鑰位置」只適用於依賴金鑰的前三類;第四類(不可逆的單向轉換)的判讀軸是計算成本,本章給分界與路由、深度在 7.30 使用者密碼儲存。選定機制之後的對接收斂(進入計算的素材怎麼定義、重放窗口怎麼收)是另一條軸、另一批讀者,見 7.35 簽章對接的驗證收斂。金鑰交換與隨機數產生的判讀軸同樣不是金鑰位置(前者看協商過程能否被中間人介入、後者看熵來源),各自的最小判準就地收在下方 threat scope 段之後。金鑰的產生、保存與輪替節奏屬於治理層,見 7.6 秘密管理與機器憑證治理。案例在問題觸發時作為證據參考。
本章 threat scope
In-scope:原語選錯導致保護落空 / 金鑰隨程式發佈 / 簽章金鑰與一般 secret 同層保存。
Out-of-scope(路由到他章):
- 秘密與機器憑證的生命週期治理 → 7.6
- 傳輸憑證與信任鏈 → 7.5
- 靜態資料的遮罩與分級 → 7.4
- 呼叫方身分的分層設計 → 7.29
- 系統之間要用哪一種機器憑證機制(共享密鑰、API key、共享密鑰簽章、mTLS)→ 7.34
- 選定共享密鑰簽章之後的素材對齊與重放收斂 → 7.35
- 金鑰託管平台選型(KMS、Secrets Manager、Vault)→ vendors
out-of-scope 的議題直接跳到對應章節。
金鑰交換在絕大多數服務裡不是獨立的選型決定,它包在傳輸層協定裡:用 TLS 就等於用了該版本協商出來的交換方式。判讀點因此落在協定版本本身——TLS 1.3 只留下具備前向保密的交換方式(每次連線各自產生一次性的金鑰,日後就算長期私鑰外洩,先前錄下的流量仍然解不開),1.2 仍可能協商到不具這個性質的組合,而那由伺服器的套件設定決定。版本與憑證信任鏈的判讀見 7.5 傳輸信任與憑證生命週期。需要自己設計交換協議的情境(點對點加密、離線裝置配對)本站尚未寫。最小判準是先確認這件事真的躲不掉——多數看似需要自訂交換的需求,實際上可以改成「用既有的傳輸層建立通道,金鑰在通道內傳一次」;躲不掉時(雙方永遠不會同時在線、載體是實體物件)選公開且有實作可用的方案,不自組。
隨機數產生的判讀只有一條:用作業系統或語言標準庫提供的密碼學安全來源,不用一般用途的亂數產生器,也不自己組合。這一條沒有取捨空間,因此不需要獨立章節。判別方式是查該語言的文件有沒有明寫這個函式是密碼學安全的——名稱線索只能當輔助訊號,它在不少語言上失效:PHP 的安全版是 random_bytes() 而不安全的是 mt_rand()(名稱反而更像專用)、Rust 的 thread_rng() 本身就是密碼學安全的、C# 的 RandomNumberGenerator 與 System.Random 從名稱也分不出來。
從本章到實作
本章是 routing layer,沿兩條 chain 進入 implementation:
兩條 chain 完成判準與模組級 chain 規格見 從章節到實作的 chain。
原語選型模型
選型的核心責任是先確認要防的是誰,再選對應的原語。以下四類解的問題彼此獨立,疊加使用時各自承擔一部分;前三類依賴金鑰,第四類不需要。
- 機密性:讓內容對持有密文的人不可讀。對應 At-Rest Encryption 與 TLS / mTLS。
- 來源與完整性:證明內容出自持有密鑰的一方且未被竄改。這一類有兩種形態,選型判準是「驗證方需不需要具備產生的能力」。
- 共享密鑰:雙方持同一把密鑰,對應 Message Authentication。選型上要注意它的撤銷粒度與能力上限都由「雙方共同持有」這件事決定,機制細節在卡片。
- 非對稱簽章:私鑰簽發、公鑰驗證,驗證方不具備產生能力。它能對不特定多方發佈可驗證的憑證,私鑰即 信任根,失守的後果是所有下游驗證同時失去意義;判準見 Non-repudiation。
- 可讀性遮蔽:讓內容在載體上不以明文出現,抵擋不特定旁觀者。對應可逆編碼。
- 不可逆的單向轉換:讓原值無法從結果還原,防的是取得儲存內容的攻擊者。使用者密碼的儲存(bcrypt、argon2 這類刻意放慢的雜湊)與內容校驗(checksum)都屬於這一類。它不依賴金鑰,判讀軸因此落在計算成本上:密碼儲存要選能調整 work factor 的演算法,讓攻擊者逐一嘗試的成本隨參數上升;校驗只要求碰撞難以構造,速度反而是優點。本章對這一類只給分界與路由:演算法怎麼選、參數怎麼量、既有參數怎麼升級(含它隨硬體變快而自己失效這個性質)見 7.30 使用者密碼儲存。
判斷自己需要哪一類,看的是資料會在哪裡、被誰看到:
- 資料會離開自己控制的範圍時要機密性——備份送到第三方儲存、跨區複製、筆記型電腦上的離線副本,以及法規明文要求加密的個資與醫療紀錄。
- 系統之間互相呼叫、或要接收外部推送時要來源與完整性——webhook 訂閱、B2B API 整合、對外發佈的授權憑證都屬於這一類。
- 內容要印在會被看到的載體上時要可讀性遮蔽——實體標籤、QR Code、放進網址的參數。
- 存下來的東西本身就不該被還原時要單向轉換——使用者密碼、檔案指紋。
一個系統通常同時需要好幾類,而它們各自獨立成立:使用者密碼用單向轉換存、傳輸過程用機密性保護、後端之間的呼叫用來源驗證,三者不能互相替代。
需要對多方發佈憑證、或事後要追究是哪一方產生某個值時,選的是非對稱簽章;雙方點對點互相呼叫且互相信任時,共享密鑰的成本更低。
前三類都依賴金鑰,差別在金鑰放在哪裡,而金鑰位置直接決定這個機制能對抗誰。以下的判讀主軸適用於這三類。
金鑰位置決定對抗對象
金鑰位置是選型判讀最有效的單一問題。它把抽象的「這樣安全嗎」轉成可回答的「誰拿得到金鑰」。五種落點與各自對抗的對象先列在這裡,下方逐一展開:
| 金鑰位置 | 能對抗誰 | 對誰失效 |
|---|---|---|
| 只在服務端 | 持有客戶端與網路流量的攻擊者 | 取得主機權限的人(可導出時) |
| 只在使用者手上 | 服務端被入侵、或被要求交出資料 | 使用者自己遺失金鑰 |
| 在雙方服務端 | 網路上的第三方 | 任一端的密鑰外洩 |
| 客戶端的受保護儲存區 | 取得產出物的人 | 能提權操作該裝置的人 |
| 隨客戶端發佈 | 看到載體或流量的旁觀者 | 能取得程式的人 |
主軸是持有者集合——金鑰同時存在於哪幾方手上;每一格內部還有一條共通的第二軸,就是持有方被攻破時金鑰跟不跟著走(可導出 vs 可用不可導出),下方在服務端那一格展開,客戶端側則是把它切成兩格分列。一個機制同時佔兩格是正常的(資料金鑰在應用記憶體可導出、包住它的主金鑰在專用模組不可導出),這時要各自判讀而非挑一格。金鑰由多方分持、任一方單獨都拿不到完整金鑰的形態不在這條主軸上(持有者不是單一方,「誰拿得到金鑰」這一題沒有單一答案),本站尚未寫。多方分持的成本落在每一次使用都要湊齊參與方,而它常常可以被「金鑰不可導出」加上「操作要兩人核可」取代——先確認這兩者達不到自己要的效果,再往下走。
金鑰只在服務端:機制能對抗持有客戶端與網路流量的攻擊者。伺服器解密、驗證、簽發,客戶端只收到結果。一般 Web 服務的 session 簽發、後端對後端的憑證核發都落在這一格,它是正式憑證的預設形態;非對稱簽章的私鑰也屬於這裡 —— 公鑰可以隨意發佈,能產生合法值的能力仍然只留在服務端。
這一格內部還有一條分界,決定應用伺服器被打下來之後金鑰跟不跟著走:金鑰可導出(存在設定、環境變數或本機檔案,取得主機權限就取得金鑰)與可用不可導出(金鑰留在專用模組內,應用只能請它代為運算,拿不到金鑰本體)。後者把「金鑰外洩」壓成「在入侵期間可以請它簽東西」,範圍與時間都變成有限。簽章金鑰特別值得走這一格,因為它失守的後果涵蓋所有下游驗證——本章風險邊界那條「簽發金鑰與應用 secret 走同一套保存流程」說的就是這條分界沒有被畫出來。
金鑰只在使用者手上:服務端也沒有,機制能對抗服務端本身被入侵或被要求交出資料。端對端加密的通訊與筆記產品落在這一格,代價是使用者遺失金鑰等於資料永久不可讀,因此產品要另外設計金鑰備援,而備援本身會把金鑰位置往回移。備援的三種形態各自移到不同的一格:使用者自己保管復原碼留在原格(代價是遺失率由使用者的保管習慣決定)、服務端存一份用使用者另一個秘密包起來的副本移到「服務端持有密文、能不能解由那個秘密的猜測成本決定」、交給第三方或多方分持移到「那些方的集合」。備援設計的完整處理本站尚未寫;在那之前的最小判準是先回答「使用者遺失時服務端要不要有能力救回」——答案是要的話,這個產品實際上不在「金鑰只在使用者手上」這一格,威脅模型的討論要用它實際落到的那一格進行,而非用宣稱的那一格。
金鑰在雙方服務端:機制能對抗網路上的第三方,但雙方互相信任。跨組織的 API 整合與 webhook 訂閱多半停在這一格——雙方各自把密鑰存在自己的服務端,共享密鑰的訊息驗證屬於這一類。任一端的密鑰外洩,攻擊者就能偽造另一端看來合法的請求,所以這一格的風險範圍涵蓋合作對象的安全水準。
金鑰在客戶端的受保護儲存區:機制能對抗取得產出物的人,對能提權操作該裝置的人失效。金鑰由裝置端產生後存進作業系統的受保護儲存區(iOS Keychain、Android Keystore、TPM),不進產出物也不上傳,因此反編譯這條路徑不成立。離線可用又不想讓服務端持有金鑰的功能落在這一格。
金鑰隨客戶端發佈:機制能對抗看到載體或流量的旁觀者,對能取得程式的人失效。行動應用與桌面應用的產出物可被反編譯或字串掃描,硬編碼的金鑰在其中以明文存在。這一格與上一格的差別只在金鑰有沒有進產出物,而這個差別決定了反編譯是否構成完整的攻擊路徑。
金鑰隨客戶端發佈這一種值得明確命名為 混淆、而非稱它加密。命名精確之後,設計討論會自動導向正確的問題:這段內容洩漏給「能取得程式的人」的後果可以接受嗎。
USAHERDS 2021 硬編碼憑證 展示這條路徑的實際後果:攻擊者從系統中檢索出可重用憑證後,沿固定認證路徑取得入口存取並長期維持。
以下條件的推導來自可逆編碼在客戶端的實作情境:混淆要成為合理選擇,需要憑證權限受限、服務端另有授權把關、產生入口限制在受控環境、正式憑證由服務端核發而非客戶端產生。這四條是必要條件而非充分條件 —— 全部成立仍可能被三種情形否決:合規對憑證儲存有明文規範、載體會流通到組織外、或同一把金鑰的載體大量產出——手上有夠多份用同一把金鑰編過的內容時,比對它們之間的規律就能反推金鑰,不必先取得產生入口,於是「產生入口受控」這個假設失效。
把客戶端的編碼稱為 縱深防禦 的外層要多一道手續:外層的正當性建立在內層獨立有效上,而內層有效與否要實測而非宣稱。四條的推導、各自的檢查方式與否決條件見 XOR 可逆編碼的適用邊界。
判讀流程
- 先分流:要存的東西本身就不該被還原時(使用者密碼、檔案指紋)走的是不依賴金鑰的第四類,判讀軸是計算成本而非金鑰位置,直接進 7.30 使用者密碼儲存,以下各步不適用。
- 其餘各類先確認要防的對象:旁觀者、網路第三方、還是能取得程式的攻擊者。
- 再確認金鑰位置,對照「金鑰位置決定對抗對象」那一節列出的落點。
- 接著把後果範圍換算成金鑰位置:後果涵蓋正式環境憑證或跨租戶資料時,往金鑰更難取得的一格移 —— 服務端持有是一條路,客戶端功能必須離線可用時,把金鑰移進裝置的受保護儲存區是另一條,兩者都讓反編譯拿不到金鑰。後果限於單一受限帳號、且服務端另有授權把關時,混淆可以留在外層,前提是「金鑰位置決定對抗對象」節末的條件成立。
- 接著確認簽發用的金鑰與一般 secret 走的是不是同一套保存流程——這一項不由金鑰位置決定,收斂條件見下方「跨章議題交叉引用」與 7.6。選定的是共享密鑰簽章時,對接階段還有三個收斂條件(素材定義、重放窗口、比對方式)要走 7.35。
- 最後把上面各步的答案寫下來——防的是誰、金鑰落在哪一格、簽發金鑰有沒有分層——再把金鑰的保存與輪替路由到治理層與部署面。這幾個答案是日後有人問「這裡為什麼用這個機制」時唯一該讀的東西,沒有寫下來的話下一個人只能重做一次判斷。
問題節點(案例觸發式)
| 問題節點 | 判讀訊號 | 風險後果 | 前置控制面 | 交接路由 |
|---|---|---|---|---|
| 金鑰隨程式發佈 | 密鑰以常數形式存在於產出物 | 取得程式即可還原內容或偽造請求 | secret-management、credential | 05 + 06 |
| 原語與威脅不匹配 | 用訊息驗證處理機密性需求,或反之 | 保護落空但團隊認為已受保護 | message-authentication、at-rest-encryption | 05 |
| 簽章金鑰無隔離 | 簽發用金鑰與一般 secret 同層保存 | 失守後可偽造所有下游可驗證的憑證 | key-management、hsm | 06 + 08 |
跨章議題交叉引用
本章「簽章金鑰無隔離」是 7.6 簽章金鑰跟長期信任根 在選型層的入口。該議題的完整處理在 7.6,本條只補「選型階段就該把簽發金鑰與一般 secret 分層」這個前置訊號。
問題節點出現在什麼樣的系統
選型發生在機制實作之前,而上表的判讀訊號要等實作完才量得到。設計階段能對照的是系統形態,以下逐一列出。
金鑰隨程式發佈出現在想省掉後端中繼的架構:行動應用直接呼叫第三方 API、桌面應用內建授權、IoT 裝置出廠即帶憑證。動機通常是離線也要能運作、或少一跳延遲。識別特徵最明確——正式版本的產出物裡有一把對方系統認得的常數,拿一份打包後的檔案做字串掃描就看得到,行動應用要先反編譯。
原語與威脅不匹配出現在需求用抽象詞交辦的系統:「這段資料要加密」「這個介面要安全」。抽象詞在需求階段讀起來完整,落到實作時由工程師自行補完它防的是誰,而補完的依據多半是手邊現成的函式庫。選型的依據是這個機制擋得住誰,而識別特徵正是這一題沒有答案:團隊列得出用了哪個演算法、指不出它防的是誰。
簽章金鑰無隔離出現在秘密管理做得統一的組織。所有 secret 走同一套申請、保存與輪替流程,這在治理上是進步,代價是簽發用的金鑰也跟著走進那一套——而它失守的後果涵蓋所有下游驗證,與資料庫密碼不是同一個量級。識別特徵是簽發金鑰與應用程式的一般 secret 存在同一個位置的同一層。
原語與威脅不匹配是這張表裡唯一不產生訊號的一格:保護落空,而團隊認為已受保護。它的失敗長這樣:需求寫「這個欄位要加密後儲存」,實作的人打開函式庫,在一排名字裡挑了看起來對的那個。挑的那一刻沒有任何東西是錯的——函式名裡有 hash、輸出是一段誰都看不懂的字串、寫進資料庫之後肉眼看不出原值。它算的是驗證值,證明的是內容沒被改過,而內容本身仍然由原值決定、也還原得回去。
這個誤選之後不再有第二次機會被抓到,因為往後每一道檢查繼承的都是那一刻的用詞。程式碼裡有那個函式的呼叫、工單寫著已加密、文件照抄工單、稽核回覆照抄文件——四處互相佐證,而它們佐證的是同一個字眼。要戳破它只能問一個不同形狀的問題:拿一筆存下來的值,看得懂嗎。那個問題不在任何一張檢查表上,因為檢查表是用「有沒有加密」寫的。某次資料庫備份檔外流之後才有人問了它。
止血要補的是加密而非調整既有機制,而成本落在既存紀錄要全部重寫一輪——那一輪由資料量與可停機窗口決定,與改程式碼的工作量無關。真正的修法在需求階段:把「要加密」換成「要讓誰看不到」,抽象詞一旦換成對象,選型就不會走錯。
常見風險邊界
- 客戶端持有的金鑰能解出正式環境憑證時,代表混淆承擔了超出能力的責任。
- 團隊用「已加密」描述只做了訊息驗證的路徑時,代表機密性需求沒有被實際滿足,修法是補上加密而非調整既有機制——訊息驗證與加密解的是不同問題,把驗證值加長、演算法換強都不會讓內容變成不可讀。補加密要接著決定三件事:加在哪一層(傳輸中用 TLS、落地後用 at-rest encryption,兩者防的對象不同、常常都要)、金鑰放哪一格(回到「金鑰位置決定對抗對象」那一節,機密性的保護範圍同樣由這一格決定)、以及既有資料怎麼補加密(既存紀錄要重寫一輪,這一輪的成本由資料量與可停機窗口決定,而改程式碼的成本與這兩者無關)。同一句話用在金鑰位於客戶端的可逆編碼上則是另一種誤稱,那裡要移動的是金鑰位置。
- 簽發金鑰與應用程式 secret 走同一套保存與輪替流程時,代表信任根的層級被壓平。
案例觸發參考
- 硬編碼憑證與固定認證路徑: USAHERDS 2021
- 簽章金鑰失守與下游偽造: Microsoft Storm-0558 2023
- 憑證集中與輪替壓力: CircleCI 2023
下一步路由
- 登入方式本身的選型(自建 / 委派 / passkey,在本章的上游):7.31 認證方式選型
- 從需求面回頭確認範圍:0.8 資安與資料保護需求 的「密鑰與秘密」議題
- 金鑰與秘密的生命週期治理:7.6 秘密管理與機器憑證治理
- 呼叫方身分的分層:7.29 API 認證的信任邊界分層
- 系統之間要用哪一種機器憑證機制:7.34 機器憑證的機制選型
- 選定共享密鑰簽章之後的對接收斂:7.35 簽章對接的驗證收斂
- 可逆編碼在客戶端的適用邊界與配套:XOR 可逆編碼的適用邊界
- 金鑰託管平台的選型與能力對照:07 vendors
- 金鑰在部署流程中的配發與注入:5.x 流量、配置與控制面邊界 的 Secret Boundary 段
- 金鑰失守後的止血與回復:8.x 止血與回復策略
- 回退演練的通用形態:6.x DR 與 rollback 演練;金鑰輪替本身的演練設計 06 可靠性 尚未有對應章節