多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

一行代码缺失,千万美元蒸发: SecondFi 钱包 Ed25519 签名漏洞全解密

一行代码缺失,千万美元蒸发: SecondFi 钱包 Ed25519 签名漏洞全解密 SecondFi 钱包事件一份面向开发者的加密学事后剖析背景2026 年 6 月 21 日至 23 日SecondFi原 YoroiCardano 钱包中的一个致命漏洞导致约 1610 万 ADA当时价值约 240 万美元从 374 个用户钱包中被盗。根源仅仅是在 Ed25519 签名实现中少写了一行代码。作为开发者我们常常把加密库当作黑盒来用。这次事件残酷地提醒我们如何使用加密原语和原语本身同样重要。让我们一步步拆解到底出了什么问题、为什么会发生以及如何避免重蹈覆辙。技术拆解Ed25519 签名本该怎么做Ed25519 是 RFC 8032 定义的一种确定性签名方案。与 ECDSA其随机数重用已是众所周知的灾难不同Ed25519 的设计初衷就是彻底消除随机数失败的风险, 它从私钥材料和消息中确定性地派生出临时随机数nonce。对于 Cardano 的扩展 Ed25519 变体钱包存储一个 64 字节的扩展私钥分为两部分kL第 0–31 字节签名标量kR第 32–63 字节秘密随机数后缀每个钱包独有永不公开正确的签名流程如下r SHA-512(kR || M) mod L # 随机数由秘密 消息派生 R r * B # R 是公开的 k SHA-512(R || A || M) mod L # 挑战标量 S (r k * kL) mod L # 签名分量最终签名是(R, S)对。这个设计安全的原因确定性同一消息 同一密钥 同一签名无需依赖 RNG秘密参与随机数r依赖kR而kR永远不会离开设备可验证任何人都可通过[S]B R [k]A验证无需知道私钥SecondFi 实际做了什么SecondFi Android 10.0.3 版本在从 BIP32-Ed25519 派生层向签名适配器层传递数据时遗漏了关键的kR秘密前缀。只有签名标量被传给了底层签名器。有缺陷的实现计算随机数的方式是r SHA-512(M) mod L仅此而已。没有任何秘密成分只有交易体哈希, 而这是链上公开可见的。为什么这是灾难性的当随机数可以公开计算时攻击者只需一条链上签名就能恢复私钥。给定任何交易中的公开值M被签名的消息交易体R公开随机数点A公钥S签名分量攻击者计算r SHA-512(M) mod L # 和漏洞签名器一样 k SHA-512(R || A || M) mod L # 和验证器一样 s (S - r) * inverse(k) mod L # 恢复私钥一条签名一笔交易你的私钥就归别人了。安全研究员 Charles Guillemet 现场演示了这一点他从主网上拉取签名仅凭链上信息就重建了私钥。时间线日期事件6 月 8 日SecondFi Android 10.0.3 发布包含有缺陷的签名器6 月 12 日早期用户交易已经开始泄露密钥6 月 21–23 日两个独立攻击者团伙系统性清空 374 个钱包6 月 22–23 日SecondFi 承认问题进入维护模式6 月 22 日SecondFi 紧急将约 1.29 亿 ADA 转移至第三方托管机构6 月 24 日补丁发布7 月 22 日SecondFi 宣布永久关闭根本原因未经审计的 SDK漏洞是在 6 月 8 日引入的当时一个名为trantor的未经审计的第三方实验性 SDK由一名独立开发者发布在 npm 上替换了 EMURGO 此前审计过的签名模块。这是一个关键的教训绝不能在生产环境中用未经审计的代码替换经过审计的加密代码, 尤其不能跳过完整的安全审查。Tibane Labs 的取证报告证实有漏洞的签名器正是trantorSDK它替换了经过验证的 EMURGO 构建版本。问题不在于 SDK 本身的意图而在于它如何与 Cardano 扩展 Ed25519 签名流程集成, 秘密前缀根本没有通过适配器层传递。代码级分析有漏洞的实现简化版importhashlib# 漏洞完全省略了秘密前缀 kRdefflawed_sign(private_key_scalar,message):# 缺失kR秘密随机数前缀# 本应是r SHA512(kR || message) mod L# 有漏洞随机数只由消息派生rSHA512(message)%L# 可公开计算Rr*B kSHA512(R||public_key||message)%L S(rk*private_key_scalar)%Lreturn(R,S)正确的实现RFC 8032importhashlibdefcorrect_sign(extended_secret_key,message):# extended_secret_key kL (32 字节) kR (32 字节)kLextended_secret_key[0:32]kRextended_secret_key[32:64]# 正确随机数由秘密前缀 消息派生rSHA512(kR||message)%L# 秘密且不可预测Rr*B kSHA512(R||public_key||message)%L S(rk*kL)%Lreturn(R,S)概念验证玩具示例下面这个简化的 PoC 展示了漏洞原理importhashlib L2**127-1# 不是真正的 Ed25519 阶仅为演示defH(*parts):hhashlib.sha512()forpinparts:h.update(pifisinstance(p,bytes)elsestr(p).encode())returnint.from_bytes(h.digest(),little)%Ldefinv(x):returnpow(x,-1,L)# 受害者的私钥玩具示例secret_s98765432123456789public_Asecret_s# 真实 Ed25519 中 A s * BMbcardano tx body hash, toy example# 漏洞签名器前缀被丢弃随机数仅由消息决定rH(M)Rr# 真实 Ed25519 中 R r * BkH(R,public_A,M)S(rk*secret_s)%Lprint([受害者签名])print(A ,public_A)print(R ,R)print(S ,S)# 攻击者从公开数据恢复私钥r_publicH(M)recovered_s((S-r_public)*inv(k))%Lprint(\n[攻击者])print(恢复出的私钥:,recovered_s)print(匹配:,recovered_ssecret_s)修复方案应该怎么做1. 使用经过审计的库永远不要自己造加密轮子。使用成熟、审计过的库Rusted25519-dalek标准且经过审计的实现JavaScript/TypeScriptnoble/ed25519或tweetnaclPythonpynacllibsodium 绑定对于 Cardano请使用Cardano Serialization Libraryemurgo/cardano-serialization-lib它经过审计并遵循正确的扩展 Ed25519 规范。2. 在所有层级保留秘密前缀在通过适配器层传递密钥时确保完整的扩展私钥kL和kR两者都保留// 正确传递完整的 64 字节扩展私钥fnsign_transaction(extended_secret:[u8;64],message:[u8])-Signature{letkLextended_secret[0..32];letkRextended_secret[32..64];// kR 必须用于随机数派生letrsha512(kR,message);// ...}3. 绝不用未经审计的代码替换已审计代码trantorSDK 在 6 月 8 日替换了 EMURGO 的审计实现。这种变更本应触发完整的安全审查加密正确性验证测试分阶段发布并伴随监控4. 实施加密正确性的回归测试测试随机数派生是否正确#[test]fntest_nonce_derivation_includes_secret_prefix(){letsecretgenerate_test_secret();letmessagebtest message;letsig1sign(secret,message);letsig2sign(secret,message);// 确定性同一密钥 同一消息 同一签名assert_eq!(sig1,sig2);// 不同消息应产生不同随机数letsig3sign(secret,bdifferent message);assert_ne!(sig1,sig3);// 关键验证随机数不是简单的 SHA512(message)// 这需要访问内部状态或已知答案测试}5. 审计所有第三方依赖trantorSDK 由一名独立开发者发布从未被审计。在集成任何加密依赖之前审查源代码检查已知漏洞验证维护者的信誉考虑进行全面安全审计开发者的核心要点Ed25519 设计上是确定性的——随机数由SHA-512(secret_prefix || message)派生。如果省略秘密前缀随机数就变成了公开可计算的。当随机数可预测时一条签名就足以恢复你的私钥。这比 ECDSA 随机数重用更糟糕, 后者通常需要两条签名。漏洞是通过用未经审计的代码替换已审计代码引入的。加密代码不是“即插即用”的, 集成方式至关重要。一旦密钥在链上被泄露它就永远泄露了。区块链交易是不可逆的。EMURGO 警告用户将受影响的助记词导入其他钱包并不能降低风险, 被泄露的地址必须彻底放弃。攻击窗口只有短短两周6 月 8 日至 6 月 21 日但已有 374 个钱包被清空。当漏洞如此容易利用时攻击者可以行动得极快。最后的思考这不是一次复杂的攻击。没有零日漏洞没有钓鱼活动没有智能合约漏洞也没有助记词被偷。攻击者只是读取了区块链然后做了一些算术。正如 MyCrypto 创始人 Taylor Monahan 所指出的这比 2011 年早期的比特币钱包漏洞还要严重。这是一次流程上的失败, 在未审查的情况下替换审计过的加密代码未能测试集成然后将其推送给数百万用户。SecondFi 将永久关闭。374 名用户失去了他们的资金。一位支持 Cardano 九年的用户损失了为退休而积攒的 998,000 ADA。所有这一切只是因为少写了一行代码。
返回列表