安全儲存之外(二):錢包密碼學的安全實現
金色財經
核心要點
即使攻擊者沒有讀到儲存中的私鑰,也可能將其恢復出來。六起事件揭示了弱隨機數、熵損失和簽名 nonce 缺陷如何危及受保護的密鑰。支付機構需要核驗實際實現是否保持了密鑰生成時的不可預測性,以及簽名時的保密性。
Beyond Secure Storage 第一篇討論了攻擊者如何濫用支付系統自身的簽名能力。第二篇轉向另一條獲得未經授權簽名的路徑:利用密碼學實現缺陷恢復私鑰,再自行簽名。這裡要回答的是:攻擊者不讀取受保護的密鑰副本,能否重建密鑰?
第 1 節先說明密鑰生成、儲存和簽名各自需要滿足的安全要求。第 2、3 節分別分析四起密鑰生成事件和兩起簽名事件,解釋實現錯誤及其後果。第 4 節據此總結設計與實現原則,再討論如何驗證相關安全屬性。
1. 密鑰生命周期中的安全屬性
一把密鑰從生成、儲存到用於簽名,各階段都有不同的安全要求,如下圖所示
在使用 BIP-39 助記詞和 BIP-32 密鑰派生的分層確定性錢包中,密碼學安全的隨機數據被編碼為助記詞,再轉換為種子,用於派生私鑰、公鑰和錢包地址 [1, 2]。
種子和私鑰生成後,需要通過加密或硬體隔離等方式保存。簽名時,系統使用私鑰生成可供公開驗證的簽名。根據簽名方案的要求,這一步還可能需要一個安全生成的隨機 nonce,或一個由秘密數據確定性派生的 nonce。
三個階段分別需要滿足以下要求:
生成時不可預測。 攻擊者不能預測密鑰,也不能以可行的成本枚舉出控制錢包的秘密資訊。
儲存時保密。 未經授權的人不能通過軟體、物理接口、備份、日誌或遠程服務讀到秘密資訊。
使用時不泄漏。 即使收集到大量公開簽名,攻擊者也無法從中獲得足以重建密鑰的資訊。
硬體錢包、安全元件(Secure Element,SE)和可信執行環境(TEE)能夠加強儲存保護,但不能僅憑使用了這些措施,就認定另外兩項要求也已滿足。
密鑰如果在生成時就可以被預測,存入安全硬體後也不會變得不可預測;簽名如果泄漏了可用於推算密鑰的資訊,將私鑰與主機隔離也無法收回這些資訊。因此,攻擊者可能繞過受保護的儲存副本,恢復密鑰並控制相應資產。
接下來兩節分析實現如何違背這些安全要求:密鑰生成缺陷可能讓私鑰變得可預測,簽名缺陷則可能讓攻擊者從公開簽名中恢復私鑰。這些案例將為第 4 節歸納實現原則與驗證目標提供依據。
2. 密鑰生成缺陷:可預測的私鑰
密鑰生成的每一步都要保留不可預測性
熵源需要適合密碼學用途,攻擊者不能以可行的成本枚舉其內部狀態;隨機字節轉換為種子或私鑰時,也不能損失應有的不確定性。如果目標是達到 128 位安全性,實現錯誤就不應把搜尋範圍縮小到攻擊者能夠窮舉的程度。
下面四個案例分別涉及依賴庫中的弱隨機數生成器、錯誤的隨機數生成器(RNG)綁定、丟失資訊的輸入轉換,以及初始狀態空間過小的生成器。表中影響數據來自不同報告,統計時間也不同,不能作為同一時點的損失總額比較。
2.1 Ill Bloom:錢包依賴庫中的弱隨機數在 Ill Bloom 事件中,一些軟體錢包使用有漏洞的 crypto-js 版
本生成 BIP-39 助記詞。
其 CryptoJS.lib.WordArray.random() 沒有從密碼學安全的隨機源獲取熵,而是使用由 Math.random() 初始化的自定義 Multiply-With-Carry(MWC)生成器。
錢包一旦依賴這個函數生成熵,後續即使正確執行了助記詞編碼和密鑰派生,也無法彌補隨機源的缺陷 [3]。
crypto-js v3.1.9-1 的相關實現 [4] 如下:
這裡的 r() 是自定義 MWC 生成器,Math.random() 參與初始化或影響其狀態。這個偽隨機數生成器(PRNG)雖然輸出看似隨機的字節,卻不具備密碼學所需的不可預測性。標稱 128 位和 256 位的輸出,實際候選空間分別僅約為 2³⁹ 和 2⁴⁷ [3]。
搜尋範圍縮小後,攻擊者就可以枚舉候選助記詞,派生錢包地址,再與公開鏈上活動比對。這樣,即使不從受害者設備中提取數據,也可能恢復秘密資訊。助記詞符合標準,無法彌補舊版依賴庫帶來的弱隨機性。
2.2 Coldcard:代碼沒有使用的硬體 RNG
Coldcard 的問題在於,設備配備了安全的隨機源,應用卻沒有調用到它
構建階段的集成錯誤使種子生成過程中的 rng_get() 實際調用了 MicroPython 的軟體 PRNG Yasmarang,而不是硬體隨機數生成器。
這個不適用於密碼學用途的 PRNG,以設備固定的唯一 ID 和有限的計時資訊初始化;其初始狀態較容易預測,導致錢包種子的有效熵大幅下降 [5]。
種子生成過程還混入了第二個 Yasmarang 生成器的輸出。較老的 Mk2/Mk3 設備用公開常量初始化第二個生成器,因此沒有增加熵。較新型號雖然引入安全元件提供的隨機數,但重新設置種子時只保留了 32 位。
因此,輸出雖然是 256 位錢包種子,實際搜尋空間卻小得多。按照 Coinkite 給定的攻擊假設,其初步估計為:Mk2/Mk3 約有 2⁴⁰ 個候選,Mk4/Q/Mk5 約有 2⁷² 個候選 [6, 7]。
攻擊者因而可以離線嘗試候選種子,計算對應地址,再與公開鏈上數據比對。搜尋成本取決於設備型號和攻擊假設,不能把 2⁷² 的搜尋難度等同於 2⁴⁰。一旦找到匹配的種子,就可以派生對應私鑰。
2.3 XRPH Wallet:輸入格式錯誤壓縮密鑰空間
隨機數生成之後,還需要按錢包 API 要求的格式傳入數據
XRPH Wallet 就在這一步丟失了大部分輸入。
其 Android 應用使用 JavaScript 的 Math.random() 生成若干組十進制數字,將其拼接為字符串,再傳給 Wallet.fromEntropy()。接收端把各字符轉換成數值較小的字節,並只保留前 16 個位置。
其中兩個位置是固定的空格分隔符,最終只有 14 個可變的十進制數字提供了熵 [8]。
這一步轉換後,錢包輸入只剩約 10¹⁴ ≈ 2⁴⁶ 種可能,遠少於均勻採樣 16 字節所應有的約 2¹²⁸ 種可能。這裡計算的是該轉換方式能夠產生多少種候選輸入,並不是在證明 Math.random() 的統計熵。
這個案例疊加了兩個問題:隨機源不適合密碼學用途,傳入 API 的數據表示和編碼又有錯誤。功能測試可能漏掉二者,因為錢包仍能生成看似正常的地址,也能生成有效簽名。公開調查說明密鑰存在被枚舉恢復的可能,但並未單憑這一點證明攻擊者實際通過哪種方式取得了密鑰。
2.4 Qubic Wallet:55 字符種子背後的弱隨機性
生成器的初始狀態也可能限制種子的取值範圍,無論輸出字符串有多長
Qubic 披露的錢包事件涉及使用 JavaScript 非密碼學函數 Math.random() 生成的種子,受影響環境包括 Apple 瀏覽器和部分基於 JavaScriptCore 的舊版 iOS 應用 [9, 10]。
以下代碼概括了原有問題和修複方法 [11]:
調用 Math.random() 55 次,並不會獲得 55 個獨立熵源。在兩次重新設置種子之間,PRNG 的每次調用都按確定性規則更新內部狀態。
固定調用位置後,如果初始種子是 S,生成的完整 55 字符錢包種子就是確定性函數 W=F(S)。知道 S 和調用位置,就能重現全部 55 個字符;多調用幾次並不會增加熵。
初始狀態的取值範圍取決於具體運行時的初始化方式。我們在 2026 年 10 月 29 日獲取並檢查的 JavaScriptCore 源碼快照中,PRNG 使用 32 位種子初始化,因此固定調用位置後,最多隻有 2³² 種輸出序列。
此次檢查的 V8 源碼使用 64 位種子,相應上限為 2⁶⁴ 種。這些上限只適用於所檢查的初始化路徑和固定調用位置,不能據此確定事件中受害者使用的具體瀏覽器構建版本,也不能據此斷定實際攻擊成本。
修復後的代碼改用 crypto.getRandomValues() 獲取密碼學安全隨機數,並通過拒絕採樣(byte
3. 簽名實現缺陷:從公開簽名恢復私鑰
密鑰安全生成後,還需要在簽名過程中保持秘密性
每次簽名都會產生可供外界觀察的數據;如果實現存在缺陷,這些公開數據就可能提供足以恢復私鑰的資訊。
ECDSA 和 Schnorr 為每次簽名引入一個秘密標量 nonce,用它掩蔽私鑰在公開簽名計算中的貢獻。
nonce 必須保密,同一密鑰不能為不同消息復用 nonce,其取值還應滿足相應方案的分布要求。確定性派生也可以滿足這些要求:僅知道公鑰和已簽消息的人,不應能夠預測 nonce。
以簡化的 Schnorr 關係為例:
s=k-rd (mod n),
其中 d 是私鑰,k 是 nonce,r 是根據公鑰、消息等公開資訊計算的挑戰值,s 是公開的簽名標量,n 是子群階。當 r 可逆時,已知 nonce 就能直接得到 d=(k-s)r⁻¹ (mod n)。
攻擊者並不一定需要知道完整的 nonce。多份簽名中 nonce 的取值約束或偏差,也可能提供足夠的資訊,使攻擊者能夠通過格方法恢復密鑰。是否可行,取決於暴露了多少資訊,以及能夠獲得哪些簽名。
下面兩個案例分別展示了這兩種情況:Zilliqa 的 nonce 取值受到部分限制,SecondFi 的 nonce 則可以完全由公開資訊計算出來。表中影響數據仍採用各報告自己的統計時間範圍。
3.1 Zilliqa Ledger Wallet:每個 Schnorr nonce 缺失 64 位Zilliqa Ledger Wallet 的問題出在處理舊版 Zilliqa
EC-Schnorr 交易的應用中 [15]。應用雖然生成了 nonce 所需的隨機數據,卻向最終簽名緩衝區複製了錯誤的片段:保留八字節零填充,丟棄八字節實際隨機數。這樣,每個受影響的 nonce 都滿足 0 ≤ k
簽名方程可以解釋,為什麼 nonce 的取值受限會帶來風險:
其中 G 為群的生成元。整理可得:
k=s+rd (mod n).
每份簽名都給出一個包含未知私鑰 d 的模方程,同時 nonce 的上界已知為 2¹⁹²。將多個獨立方程組合起來,可以構造隱藏數問題(Hidden Number Problem,HNP),再嘗試通過格約簡求解。
官方事後分析報告稱,利用四份或更多受影響簽名即可恢復私鑰。
這一過程不需要安全元件導出私鑰。nonce 的偏差讓已經公開在鏈上的簽名提供了恢復私鑰所需的資訊。
3.2 SecondFi:可由公開資訊算出的簽名 nonceSecondFi 的 Ed25519 nonce 派生錯誤更為直接:nonce
不只是存在部分資訊泄漏,而是可以完全由公開數據計算。攻擊者因此能夠從單份公開交易簽名恢復簽名密鑰 [16]。
在 Ed25519 類簽名中,正確的構造會從秘密密鑰數據派生秘密標量 a 和一個保密的 nonce 前綴。
以下公式中,A=aB 是公鑰,B 是 Ed25519 基點,M 是待簽消息,L 是子群階,H 表示 SHA-512:
有缺陷的實現卻令 r=H(M) mod L。這樣,只要知道簽名消息,就能計算 r。由於 R、A 和 S 也都公開,任何觀察者都可以繼續計算:
a=(S-r)h⁻¹ (mod L),
條件是 h 模 L 非零。
因此,與 Zilliqa 需要組合多份簽名的情況不同,SecondFi 的一份受影響簽名就足以恢復簽名標量。不過,這不等於同時恢復了原始助記詞或分層錢包中的所有同級密鑰。
問題位於密鑰派生與簽名之間的接口:所需秘密數據本來可用,卻沒有被傳給 nonce 派生函數。
4. 安全實現原則與驗證
綜合這些案例可以看到,審查隨機源和簽名算法時,還需要檢查連接它們的代碼
不可預測的隨機源應當產生不可預測的密鑰,而密鑰用於簽名時仍需保密。轉換函數或庫接口中的錯誤,都可能破壞這些要求。
第 4.1、4.2 節分別總結密鑰生成和簽名的設計與實現原則。第 4.3 節則說明,審查和測試需要提供哪些證據,才能評估實際部署的系統是否滿足這些要求。
4.1 保持密鑰生成的安全性
上述密鑰生成缺陷對應三項實現要求:
使用密碼學安全隨機源,出錯時停止生成。 種子和私鑰需要使用適合密碼學用途的安全隨機源,例如密碼學安全的偽隨機數生成器(CSPRNG)。Ill Bloom 和 Qubic 說明了為什麼一般用途的隨機數生成器不能承擔這一職責。如果預期隨機源不可用或運行失敗,應停止生成,不能悄悄改用更弱的替代方案。
在數據轉換中保留熵。 安全隨機源的輸出在編碼、採樣或跨接口傳遞時,都需要保留足夠的不確定性。XRPH Wallet 說明,僅看輸出是否正常並不夠。輸入類型、長度和編碼約束應防止意外截斷或有偏映射縮小實際密鑰空間。
確保部署代碼使用預期隨機源。 Coldcard 說明,系統配備了安全隨機源,不代表應用實際調用的就是它。從集成、編譯到部署,隨機源選擇都必須正確,包括出錯和回退處理。設計中對 RNG 的假設,需要在目標平台實際運行的程序中成立。
4.2 防止簽名過程泄漏秘密
上述要求約束了密鑰如何生成。
要在使用時繼續保持秘密性,還需要正確處理簽名 nonce。具體要求取決於簽名方案;下面除案例中的 Schnorr 和 Ed25519 外,也列出 ECDSA 的相關要求。
ECDSA:保持 nonce 的秘密性和不可預測性,同一密鑰不能為不同消息復用 nonce。遵循 RFC 6979 [17] 的確定性生成方法,可以避免每次簽名都依賴新的外部隨機數,但實現仍需防範側信道泄漏和故障攻擊。
EdDSA:保留 nonce 派生所需的全部秘密輸入。SecondFi 展示了遺漏秘密前綴、僅使用公開數據的後果。封裝代碼和密鑰適配層必須完整傳遞 RFC 8032 [18] 要求的秘密輸入,不能僅憑簽名能夠通過驗證就認定輸入正確。
Schnorr:保持方案要求的 nonce 分布和數據表示。Zilliqa 說明,即使 nonce 沒有重複、也無法被完全預測,只要丟失了部分隨機性,仍可能不安全。數據處理和標量轉換不能引入可被利用的取值約束或偏差。
經過充分審查的庫可以減少實現錯誤,但不能代替對集成代碼的檢查。調用方如果傳入了不完整或損壞的數據,即使底層簽名算法實現正確,系統仍可能不安全。
4.3 測試安全屬性
要判斷這些要求是否落實,需要沿實際運行的代碼檢查輸入如何被處理
集成錯誤也可能產生有效輸出,因此驗證還必須覆蓋正常功能測試容易遺漏的路徑。下表將密鑰生成和簽名要求歸納為具體的審查目標:
這些目標需要結合多種方法驗證。代碼審查追蹤秘密數據和隨機輸入如何到達實際部署的生成與簽名函數。異常路徑測試和故障注入用於檢查正常前提不成立時的行為;已知答案測試與獨立實現比對可以發現偏離方案規範的問題。
統計測試能夠發現明顯偏差,但地址生成成功、簽名驗證通過或統計測試通過,都不能單獨證明密碼學實現安全。應結合這些證據,核對隨機源、輸入處理和方案要求。
第三方安全團隊或專家可以通過代碼與集成審計補充內部驗證,尤其是隨機源、密鑰派生和簽名庫之間的接口。審計範圍應包括依賴版本、構建配置和實際執行路徑。發現問題時,應明確受影響的安全屬性,並提供可復現證據和回歸檢查。
實現或依賴發生變更後,仍需重新驗證。
5. 結論
六起事件揭示了實現錯誤危及受保護密鑰的兩種方式:生成時縮小密鑰的候選範圍,或簽名時泄漏 nonce。
時暴露已有密鑰的資訊。本文據此總結生成與簽名的實現要求,並給出支付基礎設施中部署代碼的審查和測試目標。
對支付機構而言,安全儲存保護的是儲存中的秘密資訊,無法彌補可預測的密鑰生成或簽名中的資訊泄漏。所需安全屬性必須在依賴庫、適配層和部署過程中保持不變。因此,審查和測試應覆蓋完整實現,不能僅憑地址和簽名有效就推斷系統安全。
這些實現要求與 Beyond Secure Storage 第一篇討論的流程控制相互補充。第一篇分析流程風險、預防檢查,以及檢查未能阻止未經授權操作時的損失控制;第二篇關注可能讓攻擊者獨立簽名的實現缺陷。
密碼學運算正確,不代表業務授權有效;業務審批通過,也不代表簽名密鑰無法被預測或恢復。支付與簽名系統需要同時設計和驗證業務授權流程與底層密碼學實現。
參考資料
[1] BIP-39, Mnemonic Code for Generating Deterministic Keys: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[2] BIP-32, Hierarchical Deterministic Wallets: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[3] CryptoJS Security Advisory, GHSA-rg76-677x-56q9: https://github.com/brix/crypto-js/security/advisories/GHSA-rg76-677x-56q9
[4] CryptoJS core.js source, version 3.1.9-1: https://github.com/brix/crypto-js/blob/3.1.9-1/core.js
[5] Coldcard firmware advisory and release notes: https://github.com/Coldcard/firmware/blob/master/releases/ChangeLog.md
[6] MicroPython, Coldcard Postmortem from MicroPython"s Perspective: https://github.com/orgs/micropython/discussions/19588
[7] Coinkite, Technical Deep Dive into the Entropy Issue: https://blog.coinkite.com/entropy-technical-backgrounder/
[8] XRPL.to, The XRPH Wallet Hack: 4,011 Wallets Emptied, and Every Key Was Guessable: https://xrpl.to/insights/xrph-wallet-hack-4011-wallets
[9] Qubic incident statement and affected-platform guidance: https://x.com/_Qubic_/status/2111796725677715474
[10] Qubic incident timeline and wallet summary: https://qubicmap.org/check
[11] Qubic Wallet, secure seed-generation fix 09dd288: https://github.com/qubic/wallet/commit/09dd288c338c7ccfade043e840c53b80e795a916
[12] WebKit, JSGlobalObject.cpp, m_weakRandom initialization: https://github.com/WebKit/WebKit/blob/fad04e2527d858db0920b3c14c62ae2b9b434a53/Source/JavaScriptCore/runtime/JSGlobalObject.cpp
[13] WebKit, WeakRandom.h, seed and PRNG implementation: https://github.com/WebKit/WebKit/blob/a37b18497f9a36e072449253c8580acfcb87bd34/Source/WTF/wtf/WeakRandom.h
[14] V8, math-random.cc, PRNG state initialization: https://github.com/v8/v8/blob/99f606174481f6e2f4809c4262122a30a25583af/src/numbers/math-random.cc
[15] Zilliqa, Post-mortem: Legacy Schnorr Nonce Bias: https://www.zilliqa.com/ledger-incident/post-mortem/
[16] SecondFi implementation analysis and reproduction: https://github.com/KarpelesLab/cardano_secondfi_analysis
[17] RFC 6979, Deterministic Usage of DSA and ECDSA: https://www.rfc-editor.org/rfc/rfc6979
[18] RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA): https://www.rfc-editor.org/rfc/rfc8032
來源:金色財經
發佈者對本文章的內容承擔全部責任
在投資加密貨幣前,請務必深入研究,理解相關風險,並謹慎評估自己的風險承受能力。不要因為短期高回報的誘惑而忽視潛在的重大損失。
暢行幣圈交易全攻略,專家駐群實戰交流
▌立即加入鉅亨買幣實戰交流 LINE 社群(點此入群)
不管是新手發問,還是老手交流,只要你想參與加密貨幣現貨交易、合約跟單、合約網格、量化交易、理財產品的投資,都歡迎入群討論學習!
- 讓加密貨幣幫你滾出年化30%現金流
- 掌握全球財經資訊點我下載APP
延伸閱讀
- 講座
- 公告
上一篇
下一篇