背景与价值 JWE以会话密钥加密负载并用公钥封装CEK,配合kid与轮换策略提升机密性与可控性。 统一规范 - 算法组合:`enc=A256GCM` 与 `alg=RSA-OAEP-256`。 - kid标识:公钥以 `kid` 标识,便于轮换与追踪。 - 轮换策略:新旧公钥在窗口内共存,旧kid逐步下线。 核心实现 加密与封装 ```ts type Jwe = { header: { alg: 'RSA-OAEP-256'; enc: 'A256GCM'; kid: string }; iv: string; ciphertext: string; tag: string; cek: string } function b64(b: ArrayBuffer): string { const u = new Uint8Array(b); let s=''; for (let i=0;i { return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt','decrypt']) } async function importRsaSpki(spki: ArrayBuffer): Promise { return crypto.subtle.importKey('spki', spki, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']) } async function importRsaPkcs8(pkcs8: ArrayBuffer): Promise { return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['decrypt']) } async function aeadEncrypt(cek: CryptoKey, plain: string): Promise<{ iv: string; ct: string; tag: string }> { const iv = crypto.getRandomValues(new Uint8Array(12)) const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, cek, enc(plain)) return { iv: b64(iv.buffer), ct: b64(ct), tag: '' } } async function wrapCek(pub: CryptoKey, cek: CryptoKey): Promise { const raw = await crypto.subtle.exportKey('raw', cek); const out = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, pub, raw); return b64(out) } async function unwrapCek(priv: CryptoKey, cekB64: string): Promise { const raw = Uint8Array.from(atob(cekB64.replace(/-/g,'+').replace(/_/g,'/')), c => c.charCodeAt(0)).buffer; return crypto.subtle.importKey('raw', raw, { name: 'AES-GCM', length: 256 }, false, ['encrypt','decrypt']) } async function jweEncrypt(plain: string, spki: ArrayBuffer, kid: string): Promise { const cek = await genCek() const pub = await importRsaSpki(spki) const a = await aeadEncrypt(cek, plain) const cekw = await wrapCek(pub, cek) return { header: { alg: 'RSA-OAEP-256', enc: 'A256GCM', kid }, iv: a.iv, ciphertext: a.ct, tag: a.tag, cek: cekw } } ``` 解封与解密 ```ts async function aeadDecrypt(cek: CryptoKey, ivB64: string, ctB64: string): Promise { const iv = Uint8Array.from(atob(ivB64.replace(/-/g,'+').replace(/_/g,'/')), c => c.charCodeAt(0)) const ct = Uint8Array.from(atob(ctB64.replace(/-/g,'+').replace(/_/g,'/')), c => c.charCodeAt(0)).buffer const out = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, cek, ct) return new TextDecoder().decode(out) } async function jweDecrypt(jwe: Jwe, pkcs8: ArrayBuffer): Promise { const priv = await importRsaPkcs8(pkcs8) const cek = await unwrapCek(priv, jwe.cek) return aeadDecrypt(cek, jwe.iv, jwe.ciphertext) } ``` 落地建议 - 采用 `RSA-OAEP-256 + A256GCM`,以 `kid` 标识公钥并在窗口内轮换。 - 存储仅保存JWE对象,避免明文日志与持久化。 验证清单 - 加密与解密是否成功;`kid` 是否可追溯到当前有效公钥。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部