背景与价值JWE以会话密钥加密负载并用公钥封装CEK,配合kid与轮换策略提升机密性与可控性。统一规范算法组合:`enc=A256GCM` 与 `alg=RSA-OAEP-256`。kid标识:公钥以 `kid` 标识,便于轮换与追踪。轮换策略:新旧公钥在窗口内共存,旧kid逐步下线。核心实现加密与封装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<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'') } function enc(s: string): Uint8Array { return new TextEncoder().encode(s) } async function genCek(): Promise<CryptoKey> { return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt','decrypt']) } async function importRsaSpki(spki: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']) } async function importRsaPkcs8(pkcs8: ArrayBuffer): Promise<CryptoKey> { 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<string> { 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<CryptoKey> { 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<Jwe> { 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 } } 解封与解密async function aeadDecrypt(cek: CryptoKey, ivB64: string, ctB64: string): Promise<string> { 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<string> { 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 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
1.899355s