對於許多 Junior 工程師來說,在開發功能時,我們習慣於直接調用庫(Library)提供的 API。例如,當需要生成一個隨機數或隨機字串時,我們可能會直接呼叫 random() 函數。然而,本次由安全公司 Coinspect 揭露的事件提醒我們:並非所有的「隨機」在密碼學上都是安全的。
這起被稱為「Ill Bloom」的事件導致至少 570 萬美元的加密資產被盜,其核心問題在於一個廣泛使用的 JavaScript 密碼學庫 —— CryptoJS 的弱隨機數生成器(Weak Random Number Generator, RNG)。
事件背景:什麼是 Ill Bloom 漏洞?
在加密貨幣錢包中,最關鍵的安全基礎是「恢復短語」(Recovery Phrase,通常是 12 或 24 個單字)。這些短語實際上是由一串高熵(High Entropy)的隨機二進制數轉換而來的(遵循 BIP39 標準)。如果這串隨機數足夠隨機,攻擊者幾乎不可能在有生之年猜中它。
然而,Coinspect 發現部分錢包應用程式在生成這些恢復短語時,使用了 CryptoJS.lib.WordArray.random() 這個函數。由於該函數在某些版本中提供的「熵」不足,導致生成的私鑰變得「可預測」,攻擊者因此能透過暴力枚舉(Enumeration)的方式找回這些私鑰並盜取資金。
核心技術分析:熵(Entropy)的崩潰
隨機數與搜尋空間 在理想的密碼學實作中,如果你使用 128 位元(bit)或 256 位元的熵,攻擊者面臨的搜尋空間(Search Space)分別是 $2^$。這是一個天文數字,目前的運算能力無法在合理時間內完成破解。
但在受影響的 CryptoJS 版本中,該隨機數生成器將搜尋空間大幅縮小: 128-bit 熵 $\rightarrow$ 縮減至約 $2^$
對於現代電腦或普通硬體來說,$2^$ 的運算量是非常小的。攻擊者只需要將所有可能的輸出枚舉出來,轉換成 BIP39 短語,推導出對應的錢包地址,然後比對區塊鏈上的公開數據,只要發現地址內有錢,就可以立即轉走。
漏洞的演進時間線(Regression) 這是一個非常典型的「漏洞回歸」案例,開發者在更新版本時不小心將舊的弱代碼重新引入:
2014 年 6 月:Multiply-With-Carry 生成器引入,其種子(Seed)來自於 Math.random()。注意,Math.random() 在 JavaScript 中絕對不能用於密碼學用途,因為它是偽隨機且可預測的。 版本 3.2.0 與 3.2.1:開發團隊意識到問題,將其切換為原生密碼學隨機數(Native Cryptographic Randomness)。 版本 3.3.0:為了維持向後兼容性(避免 Breaking Change),開發團隊將弱代碼恢復了。這意味著如果開發者將專案從 3.2.x 升級到 3.3.x,反而會將原本安全的系統變為脆弱。 2020 年 2 月(版本 4.0.0):正式永久恢復使用原生密碼學隨機數。
實際影響與受害範圍
受影響的錢包 Coinspect 確認了五款使用了該弱隨機數生成器的應用程式: NanChat:已在 v1.3.0 修復。 Bitcoin Libre:已在 2024 年 7 月發布的 v4 修復。 Bexo Wallet:宣稱在 v20.1.0 修復(但 Coinpect 指出更新版本可能尚未完全上架商店)。 RRWallet 與 Milo:已停止服務且未修復。
此外,一個名為 ferrumnet/bip39 的 React Native 分叉(Fork)版本也被發現將原生的密碼學隨機數替換成了 CryptoJS,這為許多錢包軟體引入了該漏洞。
資產損失統計 攻擊分兩波進行: 第一波(5 月 27 日):從 431 個帳戶盜走約 314 萬美元。 第二波(5 月 30 日至 7 月 13 日):從 522 個種子對應的地址盜走 255 萬美元(其中包含單一 Tron 帳戶的 218 萬 USDT)。
總計已確認的損失下限為 5,690,922 美元,涉及 Bitcoin, Ethereum, Tron, Rootstock 和 Polygon 等多條鏈。
給工程師的關鍵教訓
為什麼更新 App 無法修復現有錢包? 這是本案中最令使用者絕望的一點:一旦恢復短語(Seed Phrase)被生成,它就永遠被標記了。
即便開發者將 CryptoJS 更新到 v4.0.0,或者將 App 更新到最新版本,之前生成的那個「弱隨機」短語依然是弱的。後續的雜湊(Hashing)或 PBKDF2 處理無法增加原本缺失的熵。 結論:受影響的使用者必須創建一個全新的錢包,並將資產遷移過去。
實務開發建議 不要使用 Math.random() 處理安全相關邏輯:在 JavaScript 中,請務必使用 crypto.getRandomValues() (Web Crypto API) 或 Node.js 的 crypto.randomBytes()。 審查依賴庫的隨機數實現:當你使用第三方庫生成金鑰、Token 或密碼時,檢查它底層是用什麼生成隨機數的。 小心「不破壞性更新」的陷阱:如 CryptoJS 3.3.0 所示,為了兼容性而回滾安全修復是非常危險的。
工程判斷與總結
這起事件是一個典型的密碼學實作錯誤(Cryptographic Implementation Error)。漏洞並非出在加密算法(如 AES 或 SHA)本身,而是在於「輸入源」的品質。
對於開發者而言,最重要的一點是:密碼學的安全性建立在「不可預測性」之上。 當搜尋空間從 $2^$ 時,這不再是「困難」的問題,而變成了「時間」的問題。在處理涉及金錢或身份認證的系統時,請永遠優先選擇平台原生的、經過審計的密碼學 API,而非第三方庫中便捷但未經證實的隨機函數。