- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
本文以 jose 仓库中 docs/types/interfaces/JWK_AKP_Public.md 文档为主体,系统讲解 jose 对 AKP 类型 JSON Web Key(JWK)的类型建模:JWK_AKP_Public接口的每个属性含义、与 ML-DSA 后量子签名算法的绑定关系,以及密钥生成、导出、导入、指纹计算等完整实战流程。读完本文,你将能正确构造、校验和运用 AKP 公钥 JWK,在支持 WebCrypto 的运行时上完成 ML-DSA 签名的 JWS 场景落地。
一、AKP 是什么:后量子签名密钥在 JWK 中的表示
JWK(RFC 7517)通过kty(Key Type)成员区分密钥类型。jose 支持'EC' | 'RSA' | 'OKP' | 'AKP' | 'oct'五种类型(见 src/types.d.ts)。其中AKP是面向ML-DSA(Module-Lattice-Based Digital Signature Algorithm,NIST FIPS 204 定义的后量子数字签名算法)密钥新增的类型,用于承载 ML-DSA-44、ML-DSA-65、ML-DSA-87 三种参数集的公钥与私钥。
从源码结构看,AKP 与 ML-DSA 的绑定非常直接:在 src/lib/jws_algorithms.ts 中,mldsa(bits)函数将'ML-DSA-44'、'ML-DSA-65'、'ML-DSA-87'三个 JWS 算法标识统一映射到kty: ['AKP'],并以算法标识本身作为 WebCrypto 算法名(源码注释明确写道:“ML-DSA names its WebCrypto algorithm and its Node key type after the JWA identifier”)。也就是说,一个kty为AKP的 JWK,其alg只会是这三个 ML-DSA 标识之一。
JWK_AKP_Public就是 jose 为这种公钥 JWK 提供的 TypeScript 便利接口,其定义位于 src/types.d.ts:
/** Convenience interface for Public AKP JSON Web Keys */ export interface JWK_AKP_Public extends JWKParameters { /** JWK "alg" (Algorithm) Parameter */ alg: string /** AKP JWK "pub" (The Public key) Parameter */ pub: string }二、JWK_AKP_Public 接口属性总览
JWK_AKP_Public继承JWKParameters(通用 JWK 参数集,见 src/types.d.ts),自身新增两个必选成员。原文档所列全部属性如下:
| 属性 | 类型 | 必选 | 说明 |
|---|---|---|---|
alg | string | 是 | JWK "alg"(Algorithm)参数,AKP 键必须显式声明算法标识 |
pub | string | 是 | AKP JWK "pub"(The Public key)参数,Base64URL 编码的公钥 |
ext? | boolean | 否 | JWK "ext"(Extractable)参数,标记密钥是否可导出 |
key_ops? | string[] | 否 | JWK "key_ops"(Key Operations)参数,如['verify'] |
kid? | string | 否 | JWK "kid"(Key ID)参数,用于密钥标识 |
kty? | string | 否 | JWK "kty"(Key Type)参数,AKP 键建议显式填写'AKP' |
use? | string | 否 | JWK "use"(Public Key Use)参数,如'sig' |
x5c? | string[] | 否 | JWK "x5c"(X.509 Certificate Chain)参数 |
x5t? | string | 否 | JWK "x5t"(X.509 Certificate SHA-1 Thumbprint)参数 |
x5t#S256? | string | 否 | JWK "x5t#S256"(X.509 Certificate SHA-256 Thumbprint)参数 |
x5u? | string | 否 | JWK "x5u"(X.509 URL)参数 |
一个典型的 AKP 公钥 JWK 对象形如:
{ "kty": "AKP", "alg": "ML-DSA-65", "pub": "AhGV...(Base64URL 编码的公钥)", "kid": "ml-dsa-65-key-01", "use": "sig", "key_ops": ["verify"] }三、必选成员:alg 与 pub
与 RSA/EC/OKP 通过n/e、crv/x/y等成员承载密钥材料不同,AKP 键只有两个必选成员,语义差异值得注意。
alg(Algorithm):AKP 键必须携带alg。这一点在导入环节被强制校验:在 src/key/import.ts 的importJWK中,当kty === 'AKP'时,若 JWK 上缺失或为空字符串alg,会直接抛出TypeError('missing "alg" (Algorithm) Parameter value')。这与 RSA/EC/OKP 不同——后者允许在调用importJWK时通过第二个参数补传算法标识,而 AKP 分支不仅要求alg存在,还要求调用方传入的alg参数必须与 JWK 上的alg完全一致,否则抛出TypeError('JWK alg and alg option value mismatch')。原因在于 ML-DSA 的 WebCrypto 算法名就是 JWA 标识本身(ML-DSA-44/65/87),JWK 中的alg直接决定了底层subtle.importKey使用的算法,因此不允许被外部覆盖。
pub(The Public key):承载 Base64URL 编码的 ML-DSA 公钥本体。在 src/lib/jwk_to_key.ts 的jwkToKey中,AKP 键的alg会被保留在传入crypto.subtle.importKey('jwk', ...)的keyData里(if (keyData.kty !== 'AKP') { delete keyData.alg }),其余类型的alg一律删除,只有 AKP 例外——因为 WebCrypto 需要借助该alg成员确定 ML-DSA 的算法与参数集。
四、继承自 JWKParameters 的可选通用参数
除alg/pub外,其余属性均继承自JWKParameters(见 src/types.d.ts),与 EC/OKP/RSA 公钥 JWK 共用同一套通用成员语义:
kid:Key ID,用于在多密钥(如 JWKS)场景中标识具体密钥,配合 JWS 头部的kid完成密钥匹配。use:建议取值'sig'或'enc'。ML-DSA 属于签名算法,AKP 公钥一般标记为'sig'。在导入为 WebCrypto 密钥时该成员会被删除(见 src/lib/jwk_to_key.ts),不参与算法选择。key_ops:密钥允许的操作列表。JWS 验签场景下公钥为['verify']。jwkToKey会将其作为importKey的keyUsages(若未提供则回退到算法描述符的默认 usages)。ext:Extractable 布尔值,决定密钥是否可被exportKey导出。jwkToKey中默认值为!isPrivate(公钥默认可导出、私钥默认不可导出)。x5c/x5t/x5t#S256/x5u:X.509 证书链、SHA-1/SHA-256 指纹与证书 URL,用于把密钥绑定到 PKI 证书体系。AKP 键在 jose 中没有独立的 X.509 导入路径,这些成员仅在 JWK 层面透传,实践中 ML-DSA 密钥通常不携带它们。
五、kty 参数与 AnyJWK 判别联合
值得强调的是,JWK_AKP_Public中的kty是可选的(类型为string),接口本身并不把kty锁死为'AKP'。这是所有JWK_*_Public/Private便利接口的共同设计:它们只描述“某个类型的键需要哪些成员”,便于复用JWKParameters的通用成员。
若需要强制kty并借助 TypeScript 判别联合收窄类型,应使用AnyJWK:
export type AnyJWK = | (JWK_EC_Private & { kty: 'EC' }) | (JWK_EC_Public & { kty: 'EC' }) | (JWK_RSA_Private & { kty: 'RSA' }) | (JWK_RSA_Public & { kty: 'RSA' }) | (JWK_OKP_Private & { kty: 'OKP' }) | (JWK_OKP_Public & { kty: 'OKP' }) | (JWK_AKP_Private & { kty: 'AKP' }) | (JWK_AKP_Public & { kty: 'AKP' }) | (JWK_oct & { kty: 'oct' })以上定义见 src/types.d.ts。在AnyJWK中,JWK_AKP_Public与{ kty: 'AKP' }相交,从而可以在业务代码中通过if (jwk.kty === 'AKP')收窄到 AKP 形状,安全地读取pub、alg。对应文档见 docs/types/type-aliases/AnyJWK.md。
六、JWK_AKP_Private:从私钥到公钥的完整形状
AKP 私钥由JWK_AKP_Private表示,它在公钥接口基础上新增一个必选成员priv(见 src/types.d.ts):
export interface JWK_AKP_Private extends JWK_AKP_Public { /** AKP JWK "priv" (The Private Key) Parameter */ priv: string }即私钥 JWK 至少包含kty: 'AKP'、alg、pub、priv四个成员,其中priv为 Base64URL 编码的私钥种子。在 src/lib/jwk_to_key.ts 中,jose 通过isPrivate = !!(jwk.d || jwk.priv)判定键是否为私钥,AKP 正是通过priv参与判定。对应接口文档见 docs/types/interfaces/JWK_AKP_Private.md。
七、实战:AKP 密钥对的生成、导出与导入
jose 对 AKP 键的完整支持链路贯穿密钥生命周期,均可在 Node.js、浏览器、Cloudflare Workers、Deno、Bun 等支持 WebCrypto 的运行时上使用(ML-DSA 的具体可用性以运行时为准)。
1. 生成 ML-DSA 密钥对
generateKeyPair支持'ML-DSA-44' | 'ML-DSA-65' | 'ML-DSA-87'(见 src/key/generate_key_pair.ts)。由于 ML-DSA 没有曲线概念,crv选项对它无效——测试注释也明确写道:“RSA and ML-DSA have no curve, so nothing is being substituted and the option stays inert”(见 test/jwk/generate_key_pair.test.ts):
import { generateKeyPair, exportJWK } from 'jose' // 私钥默认不可导出;如需导出 JWK 需显式打开 extractable const { publicKey, privateKey } = await generateKeyPair('ML-DSA-65', { extractable: true, }) console.log(publicKey) // CryptoKey { type: 'public', algorithm: { name: 'ML-DSA-65' }, extractable: true, usages: ['verify'] }2. 导出为 AKP 公钥 JWK
const publicJwk = await exportJWK(publicKey) console.log(publicJwk) // { kty: 'AKP', alg: 'ML-DSA-65', pub: 'AhGV...' }导出过程有一个值得注意的细节:WebCrypto 的subtle.exportKey('jwk', ...)本身不会返回alg,而 jose 在 src/key/export.ts 中会先剔除ext、key_ops、alg、use,再对 AKP 键把alg重新附加回去(if (jwk.kty === 'AKP') { jwk.alg = alg })。这是因为 AKP 键的alg是算法标识本身,缺失会破坏 JWK 的自描述性,也与导入时的强校验形成闭环。注意导出要求密钥extractable === true,否则抛出TypeError('non-extractable CryptoKey cannot be exported as a JWK')。
3. 导入 AKP JWK
import { importJWK } from 'jose' const publicKey = await importJWK(publicJwk) // 等价写法:算法取自 JWK.alg // const publicKey = await importJWK(publicJwk, 'ML-DSA-65') // 错误用法:与 JWK.alg 不一致会抛错 // const wrong = await importJWK(publicJwk, 'ML-DSA-44') // TypeError: JWK alg and alg option value mismatch如第三节所述,AKP 分支要求alg必须存在于 JWK 且与调用参数一致(见 src/key/import.ts),底层的jwkToKey再把保留alg的 JWK 交给crypto.subtle.importKey('jwk', ...)(见 src/lib/jwk_to_key.ts)。
4. 用于 JWS 验签
导入后的公钥可直接参与compactVerify、flattenedVerify、generalVerify等 JWS 验签流程,例如配合jwtVerify校验携带 ML-DSA 签名的 JWT(见 docs/jws/compact/verify/functions/compactVerify.md、docs/jwt/verify/functions/jwtVerify.md):
import { jwtVerify } from 'jose' const { payload, protectedHeader } = await jwtVerify(jwt, publicKey) // protectedHeader.alg === 'ML-DSA-65'八、AKP JWK 指纹计算与本地 JWK 集匹配
指纹(Thumbprint):calculateJwkThumbprint对 AKP 键使用固定的成员子集{ alg, kty, pub }计算 RFC 7638 指纹——即alg和pub都是参与哈希的必选成员(见 src/jwk/thumbprint.ts):
import { calculateJwkThumbprint, calculateJwkThumbprintUri } from 'jose' const thumbprint = await calculateJwkThumbprint({ kty: 'AKP', alg: 'ML-DSA-44', pub: '...', }) const uri = await calculateJwkThumbprintUri({ kty: 'AKP', alg: 'ML-DSA-44', pub: '...', })测试 test/jwk/thumbprint.test.ts 验证了该行为:缺alg抛ERR_JWK_INVALID("alg" (Algorithm) Parameter missing or invalid),缺pub抛ERR_JWK_INVALID("pub" (Public key) Parameter missing or invalid)。指纹算法同样支持'sha256' | 'sha384' | 'sha512',默认sha256。对应文档见 docs/jwk/thumbprint/functions/calculateJwkThumbprint.md。
本地 JWK 集(createLocalJWKSet):在 src/jwks/local.ts 的键匹配逻辑中有一个针对 AKP 的特殊规则——(jwkAlg === undefined ? kty !== 'AKP' : alg === jwkAlg):当 JWKS 中的某个键没有声明alg时,AKP 键会被排除在匹配候选之外。这再次印证了“AKP 键必须携带alg才能参与运算”的约束。对应文档见 docs/jwks/local/functions/createLocalJWKSet.md。
九、使用注意事项与运行时前提
综合源码实现,使用 AKP 类型 JWK 时有以下关键约束:
alg不可缺失、不可覆盖:构造 AKP JWK 时alg与pub都是必填;导入时外部传入的算法标识必须与jwk.alg一致(src/key/import.ts)。alg只能取 ML-DSA 标识:当前 jose 将 AKP 键的算法限定为ML-DSA-44、ML-DSA-65、ML-DSA-87(src/lib/jws_algorithms.ts),JWS 算法与 WebCrypto 算法同名。- 运行时支持是前提:类型注释明确说明 JWS 算法标识的可用性“additionally depends on the runtime”(见 src/types.d.ts)。在旧版本运行时上使用 ML-DSA 可能抛出
JOSENotSupported,生产环境应先在目标运行时验证crypto.subtle是否支持对应算法。 - 私钥默认不可导出:
generateKeyPair生成的私钥extractable默认为false,需要导出私钥 JWK 时必须显式传入{ extractable: true }(见 docs/key/generate_key_pair/functions/generateKeyPair.md)。 - 导出结果自动回填
alg:exportJWK产出的 AKP JWK 会重新附加alg成员,因此从 jose 导出的 AKP JWK 一定自包含算法标识(src/key/export.ts)。
十、小结
JWK_AKP_Public是 jose 为后量子签名密钥提供的第一公民支持:它定义了 AKP 公钥 JWK 的完整 TypeScript 形状,必选成员alg(ML-DSA 算法标识)与pub(Base64URL 公钥)贯穿生成、导出、导入、指纹计算与 JWKS 匹配的全链路。对开发者而言,只要记住“AKP 键必须自带alg、算法不可覆盖、可用性取决于运行时”这三点,就能把 ML-DSA 后量子签名平滑接入现有的 JWS/JWT 体系。相关文档与实现入口:接口定义见 docs/types/interfaces/JWK_AKP_Public.md 与 src/types.d.ts,算法绑定见 src/lib/jws_algorithms.ts,密钥生命周期见 src/key/import.ts、src/key/export.ts、src/key/generate_key_pair.ts。
- 网络安全
- 认证鉴权
- 后端
【免费下载链接】jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
相关推荐
OpenSSL 4.x ML-DSA 后量子签名实现详解:FIPS 204 参数表、密钥内存布局、签名 API 与常量时间设计
OpenSSL 4.x ML DSA 后量子签名实现详解:FIPS 204 参数表、密钥内存布局、签名 API 与常量时间设计 本文基于 OpenSSL 仓库的
密码学网络安全通信cryptography 库 ML-DSA 抗量子签名实战指南:FIPS 204 密钥生成、签名与外部 mu 模式
cryptography 库 ML DSA 抗量子签名实战指南:FIPS 204 密钥生成、签名与外部 mu 模式 本篇指南围绕 cryptography ht
密码学@atproto/jwk-jose:基于 jose 库的 AT Protocol JWK 密钥实现解析
@atproto/jwk jose:基于 jose 库的 AT Protocol JWK 密钥实现解析 导读 @atproto/jwk jose 是 Blues
后端社交
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考