请别再先对公钥进行哈希了(或者说:ML-DSA 的 µ 让我很烦)

Lobsters Hottest 新闻

摘要

作者批评 ML-DSA 和 Ed25519 在签名时先对公钥哈希的做法,认为这破坏了验证端先哈希消息再逐个试验签名密钥的高效流程,并建议把公钥哈希拼接在消息末尾以保留流式哈希的优势。

<p><a href="https://lobste.rs/s/xatiaj/please_stop_hashing_public_key_first_ml">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/14 00:03

# 请别先对公钥做哈希了(或者说:ML-DSA 的 μ 让我很烦) 来源:https://blog.erincandescent.net/2026/10/mu-annoys-me 设计签名算法时,人们可能希望实现的一个目标,是防止对自己未曾见过的消息进行签名\。 乍听之下,对未曾见过的消息进行签名似乎很难,但大多数 (https://www.rfc-editor.org/info/rfc8017/#section-8.1) 经典 (https://www.rfc-editor.org/info/rfc8017/#section-8.2) 签名 (https://en.wikipedia.org/wiki/Digital_Signature_Algorithm) 算法 (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) 在通常的用法中,签署的是消息的哈希值,而不是消息本身\。之所以必须这样安排,是因为底层算法只能对大小有限的消息进行签名\。你可能没见过消息,但可能见过它的哈希值,或者另一份编码了该哈希值的签名\。在许多协议中,这让你能够为一条从未见过的消息生成有效签名\。这在实践中很少成为问题,但可能出人意料,也会成为毫无防备的协议设计者踩中的陷阱\。 较新的算法会采取措施来防止这种情况: - Ed25519 签署的是`SHA\-512\(R \|\| PubK \|\| M\)`,其中 R 在功能上相当于一个随机曲线点,PubK 是公钥,M 是消息,`\|\|`是连接运算符 - ML\-DSA 签署的是`SHAKE\-256\(SHAKE\-256\(PubK, 64\) \|\| 0x00 \|\| len\(ctx\) \|\| ctx \|\| m, 64\)`\(其中 ctx 是一个长度为 0 到 255 字节的上下文字符串\) 这可能会让签名代码变得复杂,尤其是在使用 HSM 或智能卡时,但并不需要大幅改造 (https://keymaterial.net/2024/11/05/hashml-dsa-considered-harmful/) 你的签名流程\。遗憾的是,验证端的情况就没那么乐观了\。 ## 难题 一般而言,在验证签名之前,我们应当避免触碰已签名消息的正文\。同样,一般而言,有多个签名者的密码协议会将签名者的身份放在该正文中 \(X\.509、大多数 JWT 和 CWT 的用法、SAML,我说的就是你们,但也不止你们\)\。 这意味着验证流程最终会变成: 1. 小心翼翼地解析消息 2. 深入查看消息,直到找到足以识别签名密钥的信息 3. 真正验证消息 4. 真正使用消息 通常 \(又是 JWT、CWT 和 SAML\),已签名的消息中要么没有密钥标识符,要么允许多个签名密钥共用一个密钥标识符†\。这意味着,如果签发者有多个密钥,你可能必须尝试多次验证,才能确定消息是否确实有效\。 这些协议都有一个不错的特性 \(XML 数字签名除外,不过*没人*觉得 XML 数字签名是个好主意\):它们会在头部中携带签名方法\。因此,使用传统签名算法时,你可以选择: 1. 解析头部 2. 识别算法,从而确定哈希算法 3. 对消息做哈希 4. 小心翼翼地解析消息 5. 深入查看消息,直到找到足以识别签名密钥的信息 6. 真正验证消息 7. 真正使用消息 这样做有两个好处: 1. 完成第 3 步后,就可以丢弃原始消息,减少内存占用 2. 在第 6 步中,每尝试一个密钥,你只需承担验证签名的开销,而不用再承担重新对消息做哈希的开销\。大多数情况下,消息很小,这部分开销也很小,但能省一点是一点\。 遗憾的是,这个方法有个小缺陷:它对按现有定义实现的 Ed25519 和 ML\-DSA 都不适用\。 对于 ML\-DSA,本来很容易重新调整一下顺序\。如果我们改为计算`SHAKE\-256\(0x00 \|\| len\(ctx\) \|\| ctx \|\| m \|\| SHAKE\-256\(PubK, 64\), 64\)`\(即把公钥哈希放在*最后*\),就可以保留 SHAKE\-256 的上下文,并在每次尝试验证时复制一份\。这样最终需要保留的是略多于 200 字节的 SHAKE\-256 上下文 \(而不是 64 字节的输出\),但也不算太糟\。遗憾的是,当初没有选择这种方案,我们也只能接受了\。 Ed25519 则稍微棘手一些\。如果我们反过来,将哈希计算改为`SHA\-512\(M \|\| R \|\| PubK\)`,它就会受到长度扩展攻击 \(对于任意`SHA\-512\(X\)`,你都可以将它扩展成`SHA\-512\(X \|\| 一些可预测的垃圾数据 \|\| Y\)`,其中 Y 可以是任意内容\)\。 在 Ed25519 于 2011 年发布时,要解决这个问题会有些麻烦,不过,在不截断哈希值的前提下,最简单的选择就是签署`SHA\-512\(SHA\-512\("ed25519 domain separation" \|\| M \|\| R \|\| PubK\)\)`\。 既然我们没有时光机,这些讨论多少有点纸上谈兵\。到了 2026 年,你可以直接使用 SHA\-3、BLAKE2、截断的 SHA\-2 变体,或其他任何不易受到长度扩展攻击的现代哈希函数来避开这个问题\。 在此期间,我只能叹口气,盼着我们能减少来回传递的签名数量,毕竟它们真的正变得越来越大,开销也越来越高\。 --- † 你可能会想,如果密钥标识符不唯一,那岂不是有点违背设置它的初衷\。我并不反对\!

相似文章

你想要部署 FN-DSA

Lobsters Hottest

本文讨论了 FN-DSA 后量子签名标准的当前状态、标准化延迟以及部署中的重要注意事项,包括预哈希考虑。

在接近SNFS时间内伪造1024位RSA签名

Lobsters Hottest

本文提出了一种伪造1024位RSA签名的方法,其复杂度接近特殊数域筛算法(SNFS),突出了在广泛使用的密码学系统中潜在的安全风险。

后量子密码学现状

Lobsters Hottest

详细概述了向后量子密码学的过渡进程,涵盖NIST对ML-KEM和ML-DSA的标准化、“现在收集、以后解密”威胁、混合密钥交换的采用,以及PKI分裂为MTC和基于ML-DSA的X.509。

Passkeys 是一个陷阱,请勿使用

Hacker News Top

一篇评论文章批评 passkeys 是一种「陷阱」,认为它们削弱了用户对自身登录凭证的控制权,并指出 FIDO Alliance 由大科技公司、政府和银行等利益方主导,缺乏消费者权益代表。