4、Python 签名库实操:使用 cryptography 库实现签名与验证
好,咱们进入实操环节。
前面聊了那么多理论,什么非对称算法、哈希链、证书链…… 说实话,不亲手敲几行代码,那些概念始终是飘着的。我个人习惯是,学签名工具链,一定得先从 Python 入手。为什么?因为 Python 生态成熟,调试方便,你可以在十分钟内跑通一个完整的签名-验证流程。这在 C 或者 Rust 里,光搭环境就得半天。
这一章,我们就拿 Python 里最主流的 cryptography 库来动手。我会带着你从安装开始,一步步实现签名和验证,顺便聊聊我在实际项目中踩过的坑。
4.1 为什么选 cryptography 而不是 PyCrypto 或 M2Crypto?
这个问题我经常被问到。其实答案很简单:安全性和维护状态。
PyCrypto 已经停止维护很多年了,存在已知的安全漏洞。M2Crypto 依赖 OpenSSL 的 C 绑定,安装起来非常痛苦,尤其是在 Windows 上。我曾在某个 CI 环境里折腾 M2Crypto 编译,整整浪费了两天时间。
cryptography 库就不一样了。它底层虽然也调 OpenSSL,但封装得非常好,提供了 Pythonic 的 API。而且它背后有 PyCA 团队持续维护,CVE 响应速度很快。说白了,选它,就是选个省心。
4.2 环境准备与安装
先装库。我建议你在虚拟环境里操作,避免污染全局 Python。
# 创建虚拟环境(可选但推荐)
python -m venv fw_sign_env
source fw_sign_env/bin/activate # Linux/Mac
# fw_sign_env\Scripts\activate # Windows
# 安装 cryptography
pip install cryptography
装完之后,验证一下版本:
python -c "from cryptography.hazmat.primitives.asymmetric import rsa; print('OK')"
如果没报错,说明环境就绪了。
4.3 生成密钥对:RSA vs ECDSA
签名之前,你得先有一对密钥。这里我分别演示 RSA 和 ECDSA 的生成方式。你想想看,实际固件签名场景里,这两种算法都很常见。RSA 更通用,ECDSA 密钥更小、性能更好。
4.3.1 生成 RSA 密钥对
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# 生成私钥(2048位,这是固件签名的底线)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# 序列化私钥为 PEM 格式(带密码保护)
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b"my_secure_passphrase")
)
with open("fw_private.pem", "wb") as f:
f.write(pem_private)
# 提取公钥
public_key = private_key.public_key()
pem_public = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open("fw_public.pem", "wb") as f:
f.write(pem_public)
print("RSA 密钥对生成完毕。私钥已加密,公钥已导出。")
BestAvailableEncryption 会使用 AES-256-CBC 加密,密码强度至少 12 位以上。
4.3.2 生成 ECDSA 密钥对
from cryptography.hazmat.primitives.asymmetric import ec
# 使用 P-256 曲线(NIST 标准,固件签名常用)
private_key_ec = ec.generate_private_key(ec.SECP256R1())
# 序列化
pem_private_ec = private_key_ec.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b"ec_passphrase")
)
with open("fw_ec_private.pem", "wb") as f:
f.write(pem_private_ec)
public_key_ec = private_key_ec.public_key()
pem_public_ec = public_key_ec.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open("fw_ec_public.pem", "wb") as f:
f.write(pem_public_ec)
print("ECDSA P-256 密钥对生成完毕。")
我个人更倾向于 ECDSA P-256。为什么?同等安全强度下,它的签名长度只有 64 字节,而 RSA 2048 的签名是 256 字节。对于固件这种对存储空间敏感的场景,能省一点是一点。
4.4 签名流程:从固件文件到签名值
密钥有了,接下来就是签名。这里我画了一张流程图,帮你理清整个签名过程的逻辑。
流程其实不复杂:读固件 → 算哈希 → 用私钥签名哈希 → 输出签名值。但这里有个细节很多人会忽略——签名之前一定要先哈希。直接对几百 MB 的固件做 RSA 签名,性能会非常差,而且 RSA 本身有长度限制(密钥长度减去填充开销)。
好,上代码:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa, utils
def sign_firmware(private_key_path: str, firmware_path: str, signature_path: str, passphrase: bytes):
"""
对固件文件进行签名
"""
# 1. 加载私钥
with open(private_key_path, "rb") as f:
private_key = serialization.load_pem_private_key(
f.read(),
password=passphrase,
)
# 2. 读取固件
with open(firmware_path, "rb") as f:
firmware_data = f.read()
# 3. 计算哈希(SHA-256)
digest = hashes.Hash(hashes.SHA256())
digest.update(firmware_data)
firmware_hash = digest.finalize()
# 4. 使用 PSS 填充进行签名(推荐)
signature = private_key.sign(
firmware_hash,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(hashes.SHA256())
)
# 5. 保存签名
with open(signature_path, "wb") as f:
f.write(signature)
print(f"签名完成。签名长度: {len(signature)} 字节")
return signature
# 使用示例
sign_firmware("fw_private.pem", "firmware.bin", "firmware.sig", b"my_secure_passphrase")
4.5 验证流程:确保固件未被篡改
签名是给别人验证的。验证方只有公钥,没有私钥。验证流程就是签名的逆过程:读固件 → 算哈希 → 用公钥验证签名值是否匹配。
def verify_firmware(public_key_path: str, firmware_path: str, signature_path: str):
"""
验证固件签名
"""
# 1. 加载公钥
with open(public_key_path, "rb") as f:
public_key = serialization.load_pem_public_key(f.read())
# 2. 读取固件和签名
with open(firmware_path, "rb") as f:
firmware_data = f.read()
with open(signature_path, "rb") as f:
signature = f.read()
# 3. 计算哈希
digest = hashes.Hash(hashes.SHA256())
digest.update(firmware_data)
firmware_hash = digest.finalize()
# 4. 验证签名
try:
public_key.verify(
signature,
firmware_hash,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(hashes.SHA256())
)
print("✅ 签名验证通过!固件未被篡改。")
return True
except Exception as e:
print(f"❌ 签名验证失败: {e}")
return False
# 使用示例
verify_firmware("fw_public.pem", "firmware.bin", "firmware.sig")
这里有个坑,我踩过不止一次——验证时使用的填充参数必须和签名时完全一致。比如签名时用了 salt_length=MAX_LENGTH,验证时也必须用同样的值。否则即使密钥和固件都对,验证也会失败。
4.6 常见错误与避坑指南
实操过程中,你可能会遇到各种问题。我整理了一个表格,把最常见的错误和解决办法列出来。
| 错误现象 | 根本原因 | 解决办法 |
|---|---|---|
ValueError: Could not deserialize key data |
私钥文件损坏或密码错误 | 检查 PEM 文件格式,确认密码正确 |
InvalidSignature |
签名与固件不匹配,或填充参数不一致 | 确认签名/验证使用相同的 padding 和 hash 算法 |
TypeError: sign() missing 1 required positional argument |
API 调用方式错误(新旧版本差异) | 检查 cryptography 版本,推荐 ≥ 3.4 |
| 签名长度异常(如 RSA 签名只有 128 字节) | 使用了 1024 位密钥(已不安全) | 强制使用 2048 位或以上 |
4.7 完整示例:签名与验证一体化脚本
最后,我给你一个可以直接跑的通用的脚本。它把密钥生成、签名、验证三个步骤整合在一起,方便你快速测试。
#!/usr/bin/env python3
"""
固件签名工具 - 使用 cryptography 库
支持 RSA 和 ECDSA 两种算法
"""
import sys
import os
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import utils
def generate_keys(algorithm="rsa", key_size=2048, passphrase=b"default_pass"):
"""生成密钥对"""
if algorithm == "rsa":
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=key_size,
)
elif algorithm == "ecdsa":
private_key = ec.generate_private_key(ec.SECP256R1())
else:
raise ValueError("Unsupported algorithm")
# 保存私钥
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(passphrase)
)
with open(f"private_{algorithm}.pem", "wb") as f:
f.write(pem_private)
# 保存公钥
public_key = private_key.public_key()
pem_public = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open(f"public_{algorithm}.pem", "wb") as f:
f.write(pem_public)
print(f"[OK] {algorithm.upper()} 密钥对已生成")
return private_key, public_key
def sign_firmware(private_key, firmware_path, signature_path):
"""签名固件"""
with open(firmware_path, "rb") as f:
data = f.read()
digest = hashes.Hash(hashes.SHA256())
digest.update(data)
hash_value = digest.finalize()
signature = private_key.sign(
hash_value,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(hashes.SHA256())
)
with open(signature_path, "wb") as f:
f.write(signature)
print(f"[OK] 签名完成,长度: {len(signature)} 字节")
def verify_firmware(public_key, firmware_path, signature_path):
"""验证固件"""
with open(firmware_path, "rb") as f:
data = f.read()
with open(signature_path, "rb") as f:
signature = f.read()
digest = hashes.Hash(hashes.SHA256())
digest.update(data)
hash_value = digest.finalize()
try:
public_key.verify(
signature,
hash_value,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
utils.Prehashed(hashes.SHA256())
)
print("[OK] 签名验证通过 ✅")
return True
except Exception as e:
print(f"[FAIL] 验证失败: {e}")
return False
if __name__ == "__main__":
# 简单测试
algo = sys.argv[1] if len(sys.argv) > 1 else "rsa"
priv, pub = generate_keys(algo)
sign_firmware(priv, "test_firmware.bin", "test_firmware.sig")
verify_firmware(pub, "test_firmware.bin", "test_firmware.sig")
你可以创建一个测试固件文件 test_firmware.bin(随便写点内容进去),然后运行这个脚本。看到 [OK] 签名验证通过 ✅ 的输出,就说明整个流程走通了。