从Python cryptography.io迁移至PyNaCl:预计算数据摘要签名方法及安全性问询
刚好之前做过从cryptography迁移到PyNaCl的工作,碰到过几乎一样的问题,来给你仔细梳理下:
首先得明确:PyNaCl默认的Ed25519签名接口(SigningKey.sign/sign_detached)是不支持直接传入预计算摘要的——这不是PyNaCl的限制,而是Ed25519标准签名算法的设计本身需要完整消息参与计算。标准Ed25519的签名流程里,除了对消息做SHA-512哈希,还会在中间步骤把原始消息和临时公钥、公钥一起哈希,所以光有消息哈希是不够的。
那超大文件没法读入内存的情况怎么办?别急,libsodium(PyNaCl的底层依赖)提供了专门的Ed25519ph(Ed25519 pre-hashed)算法,专门用来处理预哈希场景,PyNaCl可以通过底层的bindings模块调用这个接口。这个方法完全不需要读入整个文件,你可以流式计算SHA-512哈希,再用哈希值签名,亲测有效。
给你个示例代码:
import hashlib import nacl.bindings from nacl.signing import SigningKey # 生成密钥对(和普通Ed25519密钥兼容) signing_key = SigningKey.generate() verify_key = signing_key.verify_key # 流式计算大文件的SHA-512哈希(不用读入全量数据) large_file_path = "your_big_file.bin" hasher = hashlib.sha512() with open(large_file_path, "rb") as f: while chunk := f.read(4096): # 按4KB块读取 hasher.update(chunk) prehashed_data = hasher.digest() # 用Ed25519ph签名预哈希值 signature = nacl.bindings.crypto_sign_ed25519ph_detached( prehashed_data, signing_key.encode() # 把PyNaCl的SigningKey转成原始字节 ) # 验证签名 try: nacl.bindings.crypto_sign_ed25519ph_verify_detached( signature, prehashed_data, verify_key.encode() ) print("签名验证通过!") except nacl.exceptions.CryptoError: print("签名无效!")
这个方法既符合你的现有架构,又解决了大文件内存问题,而且是标准的预哈希签名方案。
这里得分两种情况说:
不推荐的做法:直接把摘要当作消息传给普通Ed25519签名
如果你直接调用sign_detached(your_digest),本质是在签名“这个摘要值”,而不是“原始数据”。从安全角度:
- 只要你用的哈希算法是抗碰撞的(比如SHA-256/SHA-512),理论上不会有伪造风险,但这属于“非标准用法”,会有互操作性问题——比如其他语言的Ed25519库可能不理解你在签名哈希值,而且如果后续换库,很容易踩坑。
- 如果你用了弱哈希(比如MD5、SHA-1),那肯定会有碰撞风险,直接GG。
推荐的做法:用标准化的预哈希算法(如Ed25519ph)
Ed25519ph是RFC 8032规定的官方变种,专门为预哈希场景设计,安全有保障。它的要求很明确:
- 必须用SHA-512作为哈希算法:Ed25519ph的流程是基于SHA-512设计的,不能用其他哈希(比如SHA-256),否则不符合标准,也会失去算法本身的安全特性。
- 哈希长度必须是64字节(512位):这是SHA-512的输出长度,也是Ed25519ph要求的输入长度。
这种做法的安全性和标准Ed25519完全一致,同时完美适配你的“先算哈希再签名”的架构,还能处理大文件场景。
最后补充一点:如果你之前用的是cryptography的ECDSA之类的算法,ECDSA本身支持直接签哈希,但Ed25519的设计思路不同,所以一定要用专门的预哈希接口,别硬套ECDSA的用法。
内容的提问来源于stack exchange,提问作者Brad

