- 密码学
【免费下载链接】cryptography
cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
cryptography 是面向 Python 开发者的密码学工具包,在其 docs/exceptions.rst 中系统定义了 6 个核心异常类型。本文将逐一讲解这些异常的触发场景、正确的捕获与处理方式,并结合 src/cryptography/exceptions.py 的源码实现与仓库测试用例(如 test_cmac.py、test_argon2.py)深入剖析其底层原理,帮助你在实际项目中写出健壮的密码学错误处理代码。
异常模块总览:cryptography.exceptions 的定位
所有密码学相关异常统一收敛在cryptography.exceptions模块中,由文档docs/exceptions.rst正式定义。在源码层面,这些类定义于 src/cryptography/exceptions.py,全部直接或间接继承自 Python 内置的Exception:
| 异常类 | 继承关系 | 一句话语义 |
|---|---|---|
UnsupportedAlgorithm | Exception | 请求的算法(或算法组合)不受支持 |
AlreadyFinalized | Exception | 上下文(context)在 finalize 之后仍被继续使用 |
InvalidSignature | Exception | 签名/消息认证码校验失败 |
NotYetFinalized | Exception | 在 finalize 之前就访问了 AEAD 的 tag 属性 |
AlreadyUpdated | Exception | 已调用过 update 之后又向上下文追加附加数据 |
InvalidKey | Exception | KDF 验证方法计算出的密钥与期望密钥不匹配 |
除了文档列出的这 6 类,exceptions.py 还定义了InvalidTag(AEAD 解密时 tag 校验失败)与InternalError(携带 OpenSSL 错误码的底层错误)两个补充类型,它们在文档未列出的场景中同样会从密码学操作中抛出。
值得关注的是,UnsupportedAlgorithm与InternalError是仅有的两个携带额外属性的异常:
UnsupportedAlgorithm构造函数接受message与可选的reason(_Reasons | None),并在实例上保存self._reason;InternalError保存err_code属性,其中包含来自 Rust 侧 OpenSSL 绑定的错误列表list[rust_openssl.OpenSSLError]。
理解UnsupportedAlgorithm:算法与运行环境不匹配的信号
UnsupportedAlgorithm是所有异常中出现频率最高、覆盖范围最广的一类,它表示"当前请求的算法或算法组合不受支持"。需要强调的是,它不代表你的代码写错了(这类问题通常抛TypeError/ValueError),而是代表运行时环境(尤其是底层 OpenSSL 后端)无法满足算法需求。
从 Rust 侧看 reason 枚举
在 src/cryptography/exceptions.py 中,_Reasons直接复用自 Rust 侧实现:
_Reasons = rust_exceptions._Reasons该枚举定义在 src/rust/src/exceptions.rs,共包含 12 个成员,可据此精确诊断"哪种能力缺失":
| reason 值 | 含义 |
|---|---|
BACKEND_MISSING_INTERFACE | 后端缺少所需接口 |
UNSUPPORTED_HASH | 哈希算法不受支持 |
UNSUPPORTED_CIPHER | 分组密码算法不受支持 |
UNSUPPORTED_PADDING | 填充方案不受支持 |
UNSUPPORTED_MGF | MGF(掩码生成函数)不受支持 |
UNSUPPORTED_PUBLIC_KEY_ALGORITHM | 公钥算法不受支持 |
UNSUPPORTED_ELLIPTIC_CURVE | 椭圆曲线不受支持 |
UNSUPPORTED_SERIALIZATION | 序列化格式不受支持 |
UNSUPPORTED_X509 | X.509 功能不受支持 |
UNSUPPORTED_EXCHANGE_ALGORITHM | 密钥交换算法不受支持 |
UNSUPPORTED_DIFFIE_HELLMAN | Diffie-Hellman 参数不受支持 |
UNSUPPORTED_MAC | MAC 算法不受支持 |
从 src/rust/src/exceptions.rs 可以看到,Rust 绑定通过pyo3::import_exception!直接引用 Python 侧的异常类,确保底层 Rust 实现抛出的错误能原样映射回cryptography.exceptions,保持异常类型的统一。
典型触发路径:模式与算法类型不匹配
以分组密码模式校验为例,src/cryptography/hazmat/primitives/_modes.py 中的_check_nonce_length明确展示了触发逻辑:
def _check_nonce_length(nonce, name, algorithm): if not isinstance(algorithm, BlockCipherAlgorithm): raise UnsupportedAlgorithm( f"{name} requires a block cipher algorithm", _Reasons.UNSUPPORTED_CIPHER, ) if len(nonce) * 8 != algorithm.block_size: raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.")同理,modes.py 以及椭圆曲线、Ed448、ML-DSA、ML-KEM、X25519/X448 等模块(参见 ec.py、mldsa.py、mlkem.py)都会在算法不受后端支持时抛出UnsupportedAlgorithm。
实战捕获建议
由于该异常可能在任何初始化算法对象、或调用涉及后端能力的方法时抛出,官方推荐在代码中显式捕获并降级处理:
from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes try: digest = hashes.Hash(hashes.SHA3_512()) except UnsupportedAlgorithm: print("当前 OpenSSL 后端不支持 SHA3-512,请升级或更换后端。")测试中同样如此——test_cmac.py 通过raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER)精确断言了"使用非分组密码算法构造 CMAC"应抛出的异常与 reason。
掌握两个"状态机"异常:AlreadyFinalized 与 AlreadyUpdated
密码学上下文对象(如哈希、CMAC、HMAC、AEAD 加解密器)普遍遵循"可更新 → 终结 → 只读"的状态机。AlreadyFinalized与AlreadyUpdated正是状态机违反时的哨兵。
AlreadyFinalized:finalize 之后的任何使用都是非法操作
文档定义:当上下文在终结(finalized)之后仍被使用时抛出。例如对已经finalize()的对象再次调用update()、finalize()、verify()或copy()。
测试 test_cmac.py 完整展示了这一场景:
def test_raises_after_finalize(self): key = b"2b7e151628aed2a6abf7158809cf4f3c" cmac = CMAC(AES(key)) cmac.finalize() with pytest.raises(AlreadyFinalized): cmac.update(b"foo") with pytest.raises(AlreadyFinalized): cmac.copy() with pytest.raises(AlreadyFinalized): cmac.finalize() with pytest.raises(AlreadyFinalized): cmac.verify(b"")同样的断言也遍布 test_aes.py、test_chacha20.py、test_block.py 等测试文件,说明这是所有上下文型原语的统一约定。
在 Rust 侧,src/rust/src/exceptions.rs 提供了便捷的构造辅助:
pub(crate) fn already_finalized_error() -> CryptographyError { CryptographyError::from(AlreadyFinalized::new_err("Context was already finalized.")) }实战要点:捕获AlreadyFinalized通常意味着程序设计缺陷(对已终结对象重复操作)。合理做法是在业务层避免复用上下文对象,如确有需要,重新创建一个新对象。
AlreadyUpdated:update 之后的二次数据追加
文档定义:**在已调用过 update 之后又向上下文添加附加数据(additional data)**时抛出。它保护的是那些"只允许一次 update 之后不可再追加"的上下文类型。其触发场景相对集中在特定的原语实现中,属于较罕见的错误,通常意味着调用方对接口语义理解有误——应当将全部数据一次性传入,或改用支持流式多次 update 的接口。
签名与密钥验证的失败信号:InvalidSignature 与 InvalidKey
这两个异常分别守护"验证类"操作的两条链路:MAC/签名校验链路与 KDF 密钥校验链路。
InvalidSignature:HMAC 或非对称签名验证失败
文档定义:签名验证失败时抛出,可发生于 HMAC 或非对称密钥签名校验。它不携带任何附加信息(源码中为空的pass类,见 exceptions.py),仅作为布尔判定的异常化表达。
典型用法是CMAC.verify():
from cryptography.hazmat.primitives.cmac import CMAC from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.exceptions import InvalidSignature cmac = CMAC(AES(b"2b7e151628aed2a6abf7158809cf4f3c")) cmac.update(b"6bc1bee22e409f96e93d7e117393172a") try: cmac.verify(b"foobar") # 标签不匹配 except InvalidSignature: print("MAC 校验失败,数据可能被篡改。")test_cmac.py 中的test_invalid_verify正是用pytest.raises(InvalidSignature)断言这一行为。HMAC(HMAC.verify())与 RSA/ECDSA/Ed25519 等非对称签名(verify())在验证失败时同样抛出该异常。
实战要点:校验失败是"预期内可恢复"的错误(可能源于篡改或传输损坏),通常捕获后走重试、告警或拒绝流程;而TypeError/ValueError才代表调用方自身的参数错误。
InvalidKey:KDF 验证方法计算的密钥不匹配
文档定义:密钥派生函数(KDF)的 verify 方法计算出的密钥与期望密钥不匹配时抛出。其触发场景典型见于 Argon2 等密码哈希 KDF 的verify()调用。
test_argon2.py 展示了相关断言,且 test_argon2.py 中还存在match="did you mean to use"的提示性消息,说明InvalidKey在部分实现中会携带帮助用户修正 API 用法的友好提示(例如误把verify当hash使用时的引导信息)。
实战要点:密码验证场景中,InvalidKey表示"口令错误",应据此返回"用户名或密码错误"之类的统一响应,避免向攻击者泄露账户存在性。
容易被忽略的 NotYetFinalized:AEAD tag 的时序保护
文档定义:在上下文尚未终结(finalized)时就访问 AEAD 的 tag 属性时抛出。这是 AEAD(如 AES-GCM、ChaCha20-Poly1305)特有的时序约束——tag 只有在finalize()之后才存在,提前读取是非法操作。
这一设计的正确用法是先终结、再取 tag:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM aesgcm = AESGCM(key) ct = aesgcm.encrypt(nonce, data, None) # 直接返回密文+tag,一步完成 # 若使用分步 API(如 Cipher 上下文),则必须先 finalize 再读取 tag捕获NotYetFinalized通常意味着流程编排错误(忘记调用finalize()),属于编程缺陷而非外部攻击信号。
完整实践:一段涵盖多类异常的错误处理模板
综合上述语义,给出一个可落地的统一错误处理范式:
from cryptography.exceptions import ( AlreadyFinalized, AlreadyUpdated, InvalidKey, InvalidSignature, NotYetFinalized, UnsupportedAlgorithm, ) def safe_verify(data, tag, cmac): try: cmac.update(data) cmac.verify(tag) return True except InvalidSignature: return False # 校验失败:可恢复 except AlreadyFinalized: cmac = new_cmac() # 编程缺陷:重建上下文后重试 return safe_verify(data, tag, cmac) except AlreadyUpdated: raise ValueError("数据必须一次性传入") from None except UnsupportedAlgorithm as e: print(f"后端不支持该算法: {e._reason}") return None总结与最佳实践清单
- 按语义分类处理:
UnsupportedAlgorithm反映环境能力缺失(可升级 OpenSSL 或换后端);InvalidSignature/InvalidKey是验证失败(可恢复的业务结果);AlreadyFinalized/AlreadyUpdated/NotYetFinalized是状态机违规(编程缺陷,应修复调用逻辑)。 - 善用 reason 属性:捕获
UnsupportedAlgorithm后读取_reason(_Reasons枚举的 12 个成员,见 exceptions.rs),可向用户给出精确的缺失能力提示。 - 统一从
cryptography.exceptions导入:所有异常类型均在该模块中定义(exceptions.py),Rust 侧实现也通过pyo3::import_exception!回指该模块,保证类型一致。 - 用测试固化行为:仓库测试(如 test_cmac.py、test_argon2.py、test_aes.py)对每个异常都有精确断言,可作为自己代码中
pytest.raises断言的参照模板。
- 密码学
【免费下载链接】cryptography
cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
相关推荐
Scrapy 异常体系全解:CloseSpider、DropItem、IgnoreRequest 等核心异常的用法与源码级原理
Scrapy 异常体系全解:CloseSpider、DropItem、IgnoreRequest 等核心异常的用法与源码级原理 导读 本文以 docs/topi
网页爬虫后端Streamlink 异常体系全解:从 StreamlinkError 到 CDPError 的源码级剖析
Streamlink 异常体系全解:从 StreamlinkError 到 CDPError 的源码级剖析 Streamlink 的异常体系是整个项目错误处理的
音视频Salt 异常体系全解析:读懂 salt.exceptions 源码与异常处理实践
Salt 异常体系全解析:读懂 salt.exceptions 源码与异常处理实践 Salt(SaltStack)把全部自定义异常集中定义在 salt/exce
运维配置管理后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考