一起加密貨幣冷錢包事件,表面上跟醫療資訊系統毫無關係。一個是管比特幣私鑰,一個是管病歷與檢驗數據,產業、法規、使用者完全不同。但把事件拆到根因那一層,會看到一組讓人不安的共通結構:一個組態設定在很久以前被寫錯,系統長期正常運作、沒有任何錯誤訊息,直到某天被用某種方式驗證出來,才發現所有建立在它之上的保護,從一開始就不成立。這篇要做的事,是把這個結構搬到醫療資訊系統裡,找出三個同樣可能正在靜默失效、卻沒有人在檢查的地方。
前情提要:一個被關掉的亂數產生器
一個組態檢查邏輯的疏漏,讓硬體亂數產生器長期形同虛設,系統卻在每一次開機、每一次交易裡都維持「正常運作」的外觀,直到有人驗證熵值才發現問題已存在超過五年。
2026年7月30日,加密貨幣冷錢包 Coldcard 被揭露這項根因問題,受影響裝置一度在41分鐘內被轉出逾千枚比特幣,後續擴大到數千個地址、金額達數千萬美元等級。完整的根因拆解、時間線與受影響版本,見姊妹作〈如何避免靜默降級:Coldcard冷錢包事件的3個檢查點〉,本文不重述,只取一件事往下走。

為什麼醫療資訊躲不掉這題
**兩者共享同一種失效結構:一次做錯、長期無感、爆發時無法回溯。**只要系統裡有任何一個環節依賴亂數產生器的品質,而這個環節本身不會因為亂數變差而報錯,就存在同樣的風險。
一次做錯、長期無感、爆發時無法回溯
這句話拆開來看,是三個獨立的條件,缺一不可才會釀成事故。
第一,一次做錯。組態寫錯、依賴關係接錯、驗證邏輯漏掉一個分支,這種錯誤往往發生在系統建置或整合的某個時間點,是一次性的工程決策,不是持續性的操作疏失。
第二,長期無感。錯誤之所以能存活五年,不是因為沒人維護系統,而是因為系統的其他部分完全不知道要去檢查這件事。亂數產生器輸出的每一個位元組看起來都合法、都能通過現有的格式檢查,程式碼繼續往下跑,交易繼續核發,種子繼續生成。沒有任何一層會報錯,因為「格式正確」跟「品質足夠」是兩件不同的事,而系統只檢查了前者。
第三,爆發時無法回溯。等到問題被發現,已經生成的資料(種子、金鑰、識別碼)本身就是問題所在,而不是某個可以打補丁修好的邏輯錯誤。Coinkite 在官方公告裡講得很直接:更新韌體不會改變或修復一個已經生成的種子(updating the firmware does not change or repair an existing seed)。修好程式碼,救不回已經產生的東西。
醫療資訊系統裡,任何依賴亂數產生器的環節,只要同時滿足這三個條件,就是同一種事故的另一個版本。差別只在於,加密貨幣的資產是可以被追蹤轉出的比特幣,醫療系統的資產是病人的身分與資料。
「韌體更新不能修復已產生的種子」的醫療對應版
把這句話直接翻譯到醫療資訊系統的語境,會變成:事後補上的檢核邏輯,救不回已經發出去的識別碼。
假設一套去識別化(de-identification,把可直接識別病人身分的欄位移除或置換,改用代號處理個人資料的技術)流程,用一個品質不足的亂數產生器產生病人的假名或研究用識別碼,已經跑了三年、產生了數萬筆資料。某天工程團隊發現這個產生器的熵不足、輸出可被預測,於是立刻修好程式碼、換上更好的產生器。這個修正能保護的,只有未來新產生的識別碼。已經發出去、已經流通在研究資料集裡、已經被合作單位存取過的那數萬筆識別碼,不會因為程式碼修好而變得不可預測。它們已經是既定事實,風險已經發生,只是還沒有人去驗證、去利用它。
這就是為什麼這題躲不掉:醫療資訊系統裡有太多地方依賴亂數產生器的品質,而且大多數地方,一旦品質不足,系統不會用任何方式提醒你。
檢查點一:去識別化的假名與研究 subject ID
如果產生假名或研究識別碼的亂數產生器可被預測,去識別化實質上形同虛設,但整條資料交付流程會完全正常跑完,不會有任何一層報錯。
去識別化的核心邏輯,是把「這個人是誰」跟「這筆資料屬於誰」之間的直接連結切斷,改用一個看似無意義的代號(假名、subject ID)去標記資料。這個代號要能發揮保護作用,前提是它必須跟原始身分之間沒有可被推導的關係,也就是說,代號本身不能被猜出來。
問題在於,「代號怎麼產生」這件事,在多數流程文件裡幾乎不被當成安全設計的一部分。它常常只是系統裡一段不起眼的程式碼,呼叫某個亂數函式、加上流水號、格式化成固定長度的字串。審查的重點會放在「這個代號有沒有被正確套用到每一筆資料」「對應表有沒有被適當隔離存放」,卻很少有人去問:產生這個代號的亂數,品質夠不夠。
如果這個亂數產生器的熵不足,例如受到系統開機時間、行程序號、或某個弱種子影響,使得輸出的代號在理論上可以被縮小猜測範圍,去識別化就會出現一個沒有被寫進任何流程文件的破口:代號本身變得可預測或可被逆推,資料被重新識別(re-identification,把已去識別化的資料重新對應回真實個人身分)的風險就此存在,而且這個風險不會因為流程「照規定跑完」而消失。
更關鍵的是,這個破口不會在任何一個環節被攔下來。資料照樣被去識別化、照樣通過品質檢查、照樣打包交付給研究單位或合作夥伴。所有人都會認為這批資料是安全的,因為系統的每一步都「正常運作」。唯一能發現問題的方式,是有人主動去檢驗這個亂數產生器本身的統計特性,而這件事通常不在任何人的待辦清單上,直到出事。
這正是「一次做錯、長期無感、爆發時無法回溯」在醫療資訊系統裡的第一個具體樣貌:去識別化流程本身沒有錯,錯的是流程裡一個被視為理所當然的元件。
檢查點二:SMART on FHIR 的 state 與 PKCE code_verifier
PKCE 機制的全部安全性,建立在 code_verifier 這串亂數字串不可被預測這一個前提上;亂數產生器出問題,授權流程外觀依然完整,依然核發看似正常的存取權杖。
FHIR(Fast Healthcare Interoperability Resources,一套由 HL7 International 制定的醫療資訊交換標準,讓不同醫療系統之間的資料能夠互通)是目前醫療系統資料交換最主流的標準之一。SMART on FHIR(Substitutable Medical Applications, Reusable Technologies,建構在 FHIR 之上的應用整合框架,讓第三方 App 能夠安全嵌入醫療系統、存取病患資料)則負責處理第三方應用程式該如何取得授權、存取哪些範圍的病患資料。這套授權流程,建立在 OAuth 2.0 之上,而 SMART App Launch 規範明確要求所有 SMART 應用程式都必須支援 PKCE(Proof Key for Code Exchange,一種防止授權碼被攔截或竄改的擴充機制),而且伺服器端只能接受 S256 這種雜湊過的驗證方式,不得接受未加密的 plain 方法。
PKCE 的運作邏輯很簡單,但每一步都仰賴同一個前提。應用程式先產生一個高熵的隨機字串,稱為 code_verifier,再對它做一次 SHA-256 雜湊並轉成 URL 安全格式,產生 code_challenge。應用程式在發起授權請求時,把 code_challenge 交給授權伺服器;等到要用授權碼換取存取權杖時,才把原始的 code_verifier 一併送出,伺服器驗證這兩者是否對得上,對得上才核發權杖。
定義 PKCE 機制的 IETF 標準文件 RFC 7636 明確指出,這整個機制的安全模型,仰賴 code_verifier 不會被攻擊者學到或猜到這件事,遵守此原則至關重要(the security model relies on the fact that the code verifier is not learned or guessed by the attacker)。任何違背這個前提的做法,都會讓機制形同虛設。

問題就出在這裡:code_verifier 的產生,同樣仰賴系統裡的亂數產生器。如果這個產生器的熵不足,理論上應該有 256 位元強度的隨機字串,可能實際上只落在一個小得多、能被窮舉或縮小猜測範圍的空間裡。一旦攻擊者能夠猜出或推導出 code_verifier,PKCE 原本要防禦的授權碼攔截攻擊,防線就等於不存在。
而這裡最不容易被察覺的地方,跟去識別化那個檢查點一模一樣:**授權流程本身不會因為亂數變差而失敗。**伺服器收到授權請求、核發授權碼、驗證 code_verifier 與 code_challenge 是否吻合、核發存取權杖,每一步的邏輯判斷依然成立,回應依然是「授權成功」。從系統日誌、從監控儀表板、從使用者的操作體驗來看,一切正常。真正被破壞的,是這個「正常」背後原本應該存在、卻已經名存實亡的不可預測性。這跟 Coldcard 事件裡種子看起來完整、交易看起來合法,是同一種失效樣貌。
檢查點三:你怎麼知道亂數是好的
沒有任何一個執行期的判斷式能單獨回答「這串亂數夠不夠亂」,因為亂數的品質是統計特性,不是單筆輸出能反映的邏輯正確性,所以需要在開機、建置、審查三個不同時間點各自建立檢查機制。
這是三個檢查點裡最抽象、也最容易被忽略的一個,原因很直接:亂數品質問題無法用一般的程式除錯手法找出來。一般的邏輯錯誤,程式跑出不符預期的結果,馬上就能定位;亂數品質不足,程式跑出的每一個結果都「看起來合法」,格式正確、範圍正確、型別正確,只有把大量輸出放在一起做統計分析,才可能看出可預測的模式。Coldcard 事件的根因,正是一個原本設計良好的檢查邏輯,因為一個邊界條件的疏漏而失效超過五年,期間沒有任何一次呼叫回報錯誤。
- 亂數品質不足不會讓程式報錯,只會讓「看起來正確」的輸出失去原本該有的不可預測性
- 去識別化的假名、SMART on FHIR 的 code_verifier,兩者的安全性都完全建立在亂數不可預測這個前提上
- 三個不同時間點的檢查各自負責不同的失效模式:開機自測抓硬體異常、熵源健康檢查抓長期退化、建置組態審查抓一次性錯誤配置
那麼,實務上該怎麼做?工程上的回應通常分成三個層次,各自對應不同時間點會發生的失效模式。
- 開機自測(power-on self-test):系統啟動時,對亂數產生器的輸出做基本統計檢驗(如連續性、分佈均勻性的初步檢查),異常就阻擋系統進入正常運作狀態,而不是讓它帶著問題默默啟動。
- 熵源健康檢查:針對硬體熵源(TRNG,true random number generator,利用實體物理程序產生亂數的硬體元件)建立持續性的健康監控,定期抽樣驗證輸出的統計特性,而不是只在裝置出廠時測試一次就視為終身有效。
- 建置組態納入安全審查範圍:把「亂數產生器的啟用旗標、後備機制、失敗時的行為」明確寫進系統的安全審查清單,而不是預設它只是一個底層基礎設施細節、交給編譯器與函式庫自行處理。Coldcard 事件的根因,正是一個組態旗標的檢查邏輯從未被納入過任何一輪安全審查。
Coinkite 在官方安全公告中明確指出:更新韌體不會改變或修復一個已經生成的種子(updating the firmware does not change or repair an existing seed)。這句話同樣適用於任何依賴亂數產生器的醫療資訊系統元件:修好程式碼能保護的,只有未來新產生的資料,不能追溯保護已經流通出去的部分。
這三層檢查機制的共通邏輯,是承認一件事:光靠「程式碼審查」跟「功能測試」抓不到亂數品質問題,必須額外建立針對統計特性的驗證機制,而且要在系統的生命週期裡重複執行,不是做一次就算數。
最好:無法回溯的東西要在源頭多花成本
對於一旦發生就無法補救的環節,多花一點成本做源頭的縱深防禦,通常比事後檢測更划算,因為事後檢測發現的往往已經是既成事實。
回到「問題、原因、方法、最好」這個分析順序。問題已經定義清楚:亂數品質不足會讓依賴它的機制形同虛設,而且系統不會主動提醒。原因也已經追到底:多半是一次性的組態錯誤或設計疏漏,長期沒有被任何機制檢查到。方法在上一節已經列出三層:開機自測、熵源健康檢查、建置組態審查。剩下要問的是「最好」的問題,不是哪一種方法功能最多,而是哪一種配置能從根本降低未來重複發生的機率。
| 對照維度 | Coldcard 冷錢包事件 | 醫療資訊系統的對應版本 |
|---|---|---|
| 失效元件 | 硬體亂數產生器啟用旗標的檢查邏輯 | 假名/研究識別碼產生器、code_verifier 產生器 |
| 錯誤特徵 | 值為零的巨集通過了「是否存在」的檢查 | 弱亂數輸出通過了「格式是否正確」的檢查 |
| 系統表現 | 種子照常生成、交易照常核發,無任何錯誤訊息 | 資料照常去識別化、授權照常核發權杖,無任何錯誤訊息 |
| 可否回溯 | 已生成的種子無法靠韌體更新修復 | 已發出的識別碼、已核發的權杖無法靠程式修復追溯保護 |
| 縱深防禦手段 | 骰子熵、獨立密碼短語(passphrase) | 開機自測、熵源健康檢查、建置組態安全審查 |
Coldcard 事件裡真正發揮作用、擋下部分風險的,不是任何一次事後的程式碼修正,而是使用者原本就採用的縱深防禦:額外加入至少五十次獨立骰子擲出貢獻的熵,以及一組強且唯一的密碼短語。這兩層防護的共同特性,是它們不依賴單一元件的品質,即使系統內建的亂數產生器出了問題,額外的熵來源與獨立防線依然能撐住結果。
醫療資訊系統裡對應的做法,同樣不是追求某一個亂數產生器做到完美無缺,而是在無法回溯的環節上,刻意疊加不完全依賴單一機制的防線:去識別化流程可以搭配定期的重新識別風險評估,而不是只信任產生器本身;SMART on FHIR 的實作可以搭配權杖有效期限縮短、異常存取模式監控,讓即使 code_verifier 品質出問題,攻擊者能利用的時間窗口也有限。這些做法的用意,是承認任何單一元件都可能在某個時間點失效,系統設計上要有能吸收這種失效的餘裕,而不是把所有信任押在一個元件品質完美無缺的假設上。
真正決定一套醫療資訊系統經不經得起這種考驗的,往往不是模型或功能夠不夠先進,而是在資料交付、身分保護、授權流程這些看起來「已經正常運作」的環節裡,有沒有人願意回頭問一句:如果這裡依賴的亂數不夠亂,系統會不會告訴我們?正在建置或維運去識別化流程與 SMART on FHIR 授權的讀者,可以直接用本文的三個檢查點,對照自己系統目前的設計。完整的 Coldcard 事件根因拆解與時間線,見姊妹作〈如何避免靜默降級:Coldcard冷錢包事件的3個檢查點〉。
常見問題
Q1: 這篇文章跟加密貨幣冷錢包事件是什麼關係?
本文是姊妹作,延伸 Coldcard 冷錢包亂數產生器事件揭露的根因結構,把「一次做錯、長期無感、爆發時無法回溯」這個失效模式,對應到醫療資訊系統裡去識別化與 SMART on FHIR 授權的實作細節,不重述加密貨幣事件本身的時間線與金額。
Q2: 什麼是去識別化?它跟亂數產生器有什麼關係?
去識別化是把病人的直接身分資訊移除或置換,改用假名或研究識別碼標記資料的技術。這個代號要能保護身分,前提是它不可被預測或推導,而代號通常由亂數產生器產生,所以產生器品質直接決定了去識別化實際能提供多少保護。
Q3: SMART on FHIR 是什麼?為什麼會用到 PKCE?
SMART on FHIR 是建構在 FHIR(醫療資訊交換標準)之上的授權框架,讓第三方應用程式能安全存取病患資料。授權過程建立在 OAuth 2.0 之上,規範要求所有應用程式支援 PKCE 機制,防止授權碼在傳輸過程中被攔截或重放,而 PKCE 的安全性完全仰賴 code_verifier 這串亂數字串不可被猜測。
Q4: 為什麼修好程式碼之後,問題還是沒有解決?
因為亂數品質不足留下的風險,附著在「已經產生的資料」上,不是留在程式邏輯裡。修正程式碼能防止未來繼續產生有問題的識別碼或字串,但無法讓已經發出去、已經被使用過的那些資料重新變得不可預測,這也是官方公告特別強調韌體更新無法修復既有種子的原因。
Q5: 一般的維運團隊該怎麼開始檢查這個問題?
可以從三個層次著手:系統啟動時是否對亂數輸出做過基本統計檢驗、是否有機制持續監控熵源的健康狀態、以及亂數產生器的組態設定是否曾經被納入過任何一輪安全審查。這三個問題如果都沒有明確答案,通常代表這個環節目前完全沒有被檢查過。
參考來源
- Coinkite (2026). Coldcard Security Advisory
- Coinkite. Entropy: A Technical Backgrounder
- Sakimura N, Bradley J, Agarwal N (2015). RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients
- HL7 International. SMART App Launch: App Launch (v2.2.0)
- 張饒輝 Lightman Chang (2026). 如何避免靜默降級:Coldcard冷錢包事件的3個檢查點. appi.news