跳到主要内容
ecdsa.com
Python

JWCrypto

Red Hat 的 JWCrypto,FreeIPA 和众多企业级 Python 服务使用的 JOSE 实现:完整覆盖 JWS/JWE/JWK(包括 ML-DSA),默认容差为 60 秒,alg: none 只有显式要求时才会启用。
许可证
LGPL-3.0-or-later
检查的版本
1.6.1 ·
源代码
github.com/latchset/jwcrypto
文档
jwcrypto.readthedocs.io/en/latest/

兼容性概览

JOSE 与 JWT

JWS 算法
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSAEd25519Ed448ML-DSA-44ML-DSA-65ML-DSA-87

JWCrypto 基于 pyca/cryptography 实现了 JOSE 家族:JWK 和 JWK 集、JWS、JWE 以及 JWT。一切都以密钥对象为基础——即由 PEM、JSON 或 generate() 构建的 jwk.JWK——因此每次调用时密钥类型都是明确的。它采用 LGPL-3.0-or-later 许可证,如果你要再分发修改过的副本,这一点很重要。

它的算法覆盖面是 Python JOSE 库中最广的:HS、RS、PS 和 ES 系列,ES256K、EdDSA 以及完全指定的 Ed25519 和 Ed448;1.6.1 还支持 ML-DSA-44、ML-DSA-65 和 ML-DSA-87,使用后量子 JOSE 草案中的 AKP 密钥——它们在检查中全部签名与验证成功,而且全都在默认允许列表中。问题恰恰出在这个默认列表上:除非传入 algs,否则只要密钥匹配,其中任何算法都会被接受,因此应像示例那样,把列表缩小到颁发者实际使用的算法。

除非列出 none,否则未签名的令牌会被拒绝,报错 Algorithm not allowed;用 EC 公钥 JWK 验证 HS256 令牌会失败,因为这种密钥类型无法执行 HMAC。ES256 会拒绝 P-384 密钥,报错 Curve requested is "P-256", but key curve is "P-384"。ES256 签名是 64 字节的原始 r‖s 格式;DER 签名会报错 Verification failed for all signatures——可以在 JWT 调试器中检查编码。

有两个默认行为与其他库不同。过期检查容许 60 秒的时钟偏差:57 秒前过期的令牌仍然验证通过,63 秒前过期的则不通过——比完全不留容差的 PyJWT 或 jose 更宽松。另外,exp 是可选的:除非在 check_claims 中列出它,否则不含它的令牌也能验证通过。

可以用 JWKS 生成器构建密钥集,用 JWE 查看器查看加密令牌;对于 ML-DSA 的密钥和签名,后量子签名工具会显示涉及的大小。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/jwcrypto/run.sh 于2026年10月20日在容器镜像 python:3.14.8-slim-trixie 中运行了 JWCrypto 1.6.1(运行时为 cryptography 50.0.2);本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • JWS 算法

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSAEd25519Ed448ML-DSA-44ML-DSA-65ML-DSA-87

    256/384/512 位的 HS、RS、PS 和 ES,以及 ES256K、EdDSA、Ed25519、Ed448 和 ML-DSA-44/65/87(使用 AKP 密钥)的签名与验证均成功;它们全部在默认允许列表中。

    检查记录
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA, Ed25519, Ed448, ML-DSA-44, ML-DSA-65, ML-DSA-87
    evidence jws-algorithms: ML-DSA-44: JWK kty AKP
    evidence jws-algorithms: ML-DSA-65: JWK kty AKP
    evidence jws-algorithms: ML-DSA-87: JWK kty AKP
    evidence jws-algorithms: default allowed list: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA, ES256K, Ed25519, Ed448, ML-DSA-44, ML-DSA-65, ML-DSA-87
  • ES256 签名编码

    raw

    ES256 签名是 64 字节的原始 r‖s。

    检查记录
    fact jws-ecdsa-encoding = raw
  • JWS 中的 DER 签名

    rejected

    DER 编码的 ES256 签名会报错 InvalidJWSSignature: Verification failed for all signatures。

    检查记录
    fact jws-der-signature = rejected
    evidence jws-der-signature: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
  • 算法允许列表

    from-key

    不传 algs 时,使用默认列表,由密钥类型决定:ES256 令牌用 EC 密钥验证通过,而用同一密钥验证 RS256 令牌则失败。

    检查记录
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: no algs argument: verified; RS256 token with the EC key: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
  • 未签名的令牌(alg none)

    opt-in

    默认情况下,未签名的令牌会报错 Algorithm not allowed,只有设置 algs=["none"] 时才能验证通过。

    检查记录
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: with the EC key: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSOperation('Algorithm not allowed')]"]
    evidence jwt-alg-none: algs ['none'] with an empty oct key: verified
  • 把公钥用作 HMAC 密钥

    rejected

    用 EC 公钥 JWK 检查 HS256 令牌失败;只有特意构造、包含 PEM 文本的 oct JWK 才让它验证通过。

    检查记录
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: HS256 token checked with the EC public JWK: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
    evidence jwt-hmac-public-key: only an explicit oct JWK made from the PEM text verifies it: True
  • 按 alg 检查曲线

    enforced

    用 P-384 密钥签名 ES256 时抛出 Curve requested is "P-256", but key curve is "P-384"。

    检查记录
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidJWKValue: Curve requested is "P-256", but key curve is "P-384"
  • 默认时钟偏差

    60s

    过期检查默认容许 60 秒的时钟偏差:57 秒前过期的令牌验证通过,63 秒前过期的则未通过。

    检查记录
    fact jwt-clock-skew = 60s
  • 是否要求过期时间声明

    no

    不含 exp 的令牌验证通过;在 check_claims 中列出 exp 即可将其设为必需。

    检查记录
    fact jwt-exp-required = no
    evidence jwt-exp-required: verified

用法

签发带密钥 ID 的 ES256 令牌,并在缩小算法列表、要求 iss 和 exp 的条件下验证它。
import json
import time

from jwcrypto import jwk, jwt

key = jwk.JWK.generate(kty="EC", crv="P-256", kid="2026-10")
public = jwk.JWK.from_json(key.export_public())

token = jwt.JWT(header={"alg": "ES256", "kid": "2026-10"}, claims={"sub": "user-42", "iss": "https://auth.example.com", "exp": int(time.time()) + 600})
token.make_signed_token(key)

# algs narrows the default list; check_claims makes iss and exp mandatory.
checked = jwt.JWT(
    jwt=token.serialize(),
    key=public,
    algs=["ES256"],
    check_claims={"iss": "https://auth.example.com", "exp": None},
)
print("sub:", json.loads(checked.claims)["sub"])

代码片段测试工具会在容器镜像 python:3.14.8-slim-trixie 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
pip install --quiet --root-user-action=ignore jwcrypto==1.6.1 cryptography==50.0.2
命令
python usage.py
输出必须匹配(正则表达式)
sub: user-42

库