我在解密 JWE 的 cipherText 部分时看到以下错误。 CipherText(alg:aes-256-gcm)是使用节点 Node Jose 创建的,我正在尝试使用 Node crypto 来解密
Error: Unsupported state or unable to authenticate data
at Decipheriv.final (node:internal/crypto/cipher:193:29)
at Object.<anonymous> (/index.js:100:24)
at Module._compile (node:internal/modules/cjs/loader:1103:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
Node jose 加密解密:工作正常
const jose = require('node-jose');
(async () => {
let keypair = {
"p": "wP8U4hF_Dyj-Z-YY_89gmT1ngnKER_0e_WTxOS5SWQp-giImmsCEVpP9mqKrwHBrkPJenFwa8kIxNgmJtGSuXkNMd8NoHdqzwtq50-mMCV1oDAIjXl0UUX8_CdVyy9TFe3-P5lJuQoOIRlCs_hvVJ_4x3fT9Xipo85OMoPTmwoM",
"kty": "RSA",
"q": "uk0U0Wx9GfpyLLJhJSJ1gyx5M31kAP9vMGQLrp0P40g-BOl7YYusv8H1EVcscZQuMrNBQvlIyJRTx229r_Wz961YqQ6fw-hJetQQ08CiLj8AuYMm6KHNbEyhzFV8lFyIZCwsDXbA-Ek6F5FLfOqfeFHjo0fOq77R7kcp8cepcP0",
"d": "bd1sMKhIu3QDbEvzrb8cj6f0egUQQU0S1M53okm9ByBWYOFCNPayoluvO9BWBBY2kMsIWQ64q7-sQgjc894yaffppF4Cz0jFCzIIIgoWmT4L4syQkdiiV7xoIrbKvZkloH7bSATf6QioxkcX-9Dz4Z5VHUg0GKzFkK74a655odc-VzlDp7cbrfeL29ETlbb9864IJIoC_hAhzfaVOHCMb8y5Rr5UTgGjf1QlYjSjMhE5L0BOty4QeyxDoy_dI7sosJPj9tVFQCa6yDJaOAFF_h3QTLyhXU1qL6GJbG_JklsTn5z93Vy8haVyf3FvHDW2PVkKy46HRTINpB72QWkV-Q",
"e": "AQAB",
"use": "enc",
"kid": "tester",
"qi": "tItxE9gwrpmifSWur8DdsaMZN7rylhhR18PIgtZaHhw58i9EBRpzW_CMZIHuzl8-ujh8ZsuUsmU2HbYX0VWZnPty3z_-hCWV1DjoElQ93WThcQa0HviIVSxbXEtYoLoNM_gzIFjihICGCtXhlF9Di3C4FKcCP0dYzYWpVFq4uac",
"dp": "M8j-IH7TWg0E3noWQSWy5MteJ9l0dyCLHTDlrRMp02yGb4KcWy_HErgY91IoxbUkl7sA-fGY5WIvdDFw-q99PhvOu9_54vDZBTLNY_gptCWVEovMU7ikCA4dqxTT_a904eNjiEib_0rt2PgywuhS9K03Ujg3d_nnOVxhAptUA-M",
"alg": "RSA-OAEP-256",
"dq": "NsOr5_gNOlK9t1fkaKcdhibPpgwpFoX_6Giwam7vGa_F02nTBBSr_l6ErMlEXkrh3bOF7qsa8yNvEUO4K_59HcSOOHv9CPjCiOHH5IdO5WtNyjq8eEv_9-L6-Pb0PSSKT3AQrxCGnzXfZsgmOZ06rYLc-MWGAkSAr5upv9Iig_0",
"n": "jHNxl1hhNxvYEn5PPObRia7LM6_koGcrcHLgWVVc-zU5loWn33xdd3R3EPs10ZwrhRBmXthN1WLFB0V4w-1QrGSM5wuBm2AqIFglDaYWW7d_aFCYMubCC6YiKYgrXezZtjngGtjBJPNwov4PC6KJgh7xwtqt5MTXX7TH8H6BhvQvNiD_IEH_vxF9hEhN-f5wKR6yNGlCT3X0NWwUiavG0vgtW0y1g6BHUskA1HogdrpURAfmcSSrDya9IoYjlAmUmql-0JGeEJIU53hoDB0ZVNREZbhJxZLN1hV8KrYeVPjfHCjXaJ6-Fw9MvnC9m-FGiX9dvE1A-Yp5sowUOqKLdw"
}
keypairob = await jose.JWK.asKey(JSON.stringify(keypair));
const contentAlg = "A256GCM";
const payload = JSON.stringify({test: 'test'});
const options = {
compact: true,
contentAlg: contentAlg,
protect: Object.keys({
alg: keypairob.alg,
kid: keypairob.kid,
enc: contentAlg
}),
fields: {
alg: keypairob.alg,
kid: keypairob.kid,
enc: contentAlg
}
};
const enc = await jose.JWE.createEncrypt(options, keypairob).update(payload, "utf8").final();
console.log("encrypted payload node jose : "+enc);
const dec = await jose.JWE.createDecrypt(keypairob,options).decrypt(enc)
let decryptJSON = JSON.stringify(dec);
let originalInput = Buffer.from(dec.plaintext).toString("utf-8");
console.log("decrypted payload node jose :"+originalInput);
})();
控制台输出:
加密负载节点 jose :eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJraWQiOiJ0ZXN0ZXIiLCJlbmMiOiJBMjU2R0NNIn0.Dw_EDEJnahKdk8mwlY3juSWd4jTIM8Go8NdQLpLWeH2kCmDHNg0RZKvbINEc3j F70bPECCzuIi_fH3ovlUeQ_o7KqbF_5ZMiyOIYQvfsVlgSZpl1YSRHgoX1QaRg_FvOvUilE6 xGJ_nRnLSk1VjdTWJFARu-IGefed0aQ1mCsh5Hq9e2q55iQGlMUQVIHQ7X6ZzXNHu4QW7jSMY LskP7Vg-EjQizrGvHC04S2SsglZ0znm5eyz0KR71zcdkR2O80_v6u2GM6APMCQrJ3GQ7SSsguwEKMxxvCpQvZ65lCV4bujvP7tD4A8pn_SCKTjFGU8MulGzHi2z-K4EZU6yZXFQ.DokezaeJU sRg6h8q.oXC0AtIkn53UgjF2KdxW.1NBMp9IdXW-U5emoiIrE7A
解密的有效负载节点 jose :{"test":"test"}
节点加密解密:
const crypto = require('crypto');
const testJwk = {
"p": "wP8U4hF_Dyj-Z-YY_89gmT1ngnKER_0e_WTxOS5SWQp-giImmsCEVpP9mqKrwHBrkPJenFwa8kIxNgmJtGSuXkNMd8NoHdqzwtq50-mMCV1oDAIjXl0UUX8_CdVyy9TFe3-P5lJuQoOIRlCs_hvVJ_4x3fT9Xipo85OMoPTmwoM",
"kty": "RSA",
"q": "uk0U0Wx9GfpyLLJhJSJ1gyx5M31kAP9vMGQLrp0P40g-BOl7YYusv8H1EVcscZQuMrNBQvlIyJRTx229r_Wz961YqQ6fw-hJetQQ08CiLj8AuYMm6KHNbEyhzFV8lFyIZCwsDXbA-Ek6F5FLfOqfeFHjo0fOq77R7kcp8cepcP0",
"d": "bd1sMKhIu3QDbEvzrb8cj6f0egUQQU0S1M53okm9ByBWYOFCNPayoluvO9BWBBY2kMsIWQ64q7-sQgjc894yaffppF4Cz0jFCzIIIgoWmT4L4syQkdiiV7xoIrbKvZkloH7bSATf6QioxkcX-9Dz4Z5VHUg0GKzFkK74a655odc-VzlDp7cbrfeL29ETlbb9864IJIoC_hAhzfaVOHCMb8y5Rr5UTgGjf1QlYjSjMhE5L0BOty4QeyxDoy_dI7sosJPj9tVFQCa6yDJaOAFF_h3QTLyhXU1qL6GJbG_JklsTn5z93Vy8haVyf3FvHDW2PVkKy46HRTINpB72QWkV-Q",
"e": "AQAB",
"use": "enc",
"kid": "tester",
"qi": "tItxE9gwrpmifSWur8DdsaMZN7rylhhR18PIgtZaHhw58i9EBRpzW_CMZIHuzl8-ujh8ZsuUsmU2HbYX0VWZnPty3z_-hCWV1DjoElQ93WThcQa0HviIVSxbXEtYoLoNM_gzIFjihICGCtXhlF9Di3C4FKcCP0dYzYWpVFq4uac",
"dp": "M8j-IH7TWg0E3noWQSWy5MteJ9l0dyCLHTDlrRMp02yGb4KcWy_HErgY91IoxbUkl7sA-fGY5WIvdDFw-q99PhvOu9_54vDZBTLNY_gptCWVEovMU7ikCA4dqxTT_a904eNjiEib_0rt2PgywuhS9K03Ujg3d_nnOVxhAptUA-M",
"alg": "RSA-OAEP-256",
"dq": "NsOr5_gNOlK9t1fkaKcdhibPpgwpFoX_6Giwam7vGa_F02nTBBSr_l6ErMlEXkrh3bOF7qsa8yNvEUO4K_59HcSOOHv9CPjCiOHH5IdO5WtNyjq8eEv_9-L6-Pb0PSSKT3AQrxCGnzXfZsgmOZ06rYLc-MWGAkSAr5upv9Iig_0",
"n": "jHNxl1hhNxvYEn5PPObRia7LM6_koGcrcHLgWVVc-zU5loWn33xdd3R3EPs10ZwrhRBmXthN1WLFB0V4w-1QrGSM5wuBm2AqIFglDaYWW7d_aFCYMubCC6YiKYgrXezZtjngGtjBJPNwov4PC6KJgh7xwtqt5MTXX7TH8H6BhvQvNiD_IEH_vxF9hEhN-f5wKR6yNGlCT3X0NWwUiavG0vgtW0y1g6BHUskA1HogdrpURAfmcSSrDya9IoYjlAmUmql-0JGeEJIU53hoDB0ZVNREZbhJxZLN1hV8KrYeVPjfHCjXaJ6-Fw9MvnC9m-FGiX9dvE1A-Yp5sowUOqKLdw"
}
// node crypto generated JWE ciphertext part - decrypt with node crypto code : success
const nodeCryptoEncPayload = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoidGVzdGVyIn0.FHEgUliMX84RcU_Vh50VsyvpradjaUclFyXpyTikqx6bhsBJ3KCUiE34plNNtI_e-CdU-NQEH-Aa7lhkpW4WsVoBi_2WO7IeIZ_Q2RkASlyB4Br0U98ExA6_vXzmZ1RtWaXsvcsRSpcwQTc5fmAah2eOG0A0glxYqf-dwM7PjOoIdP0ryc-6jrp5w1d2dE3ImoC-pMyOLTagsDj5iVUtIq8Y4xCFdXh4J1WYSZ6UAnyxYY0ctPVTv3NXJvlcbccy_4GsqgpSIk0Whcun0Jyjr0jPXw5Jaxl50VVW2Me8srgTSbFWugqEAfxkgVQHu2GDwd60oYyznMrfmYI8t4Q8lQ.8yeQaqpcresDf6Gy.7Bz_yj77Vfa1QTlfKZD1.ODRr0itIlYurZ3bbNH47jQ"
// node jose generated JWE ciphertext part - decrypt with node crypto code : error
const nodejoseEncPayload = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJraWQiOiJ0ZXN0ZXIiLCJlbmMiOiJBMjU2R0NNIn0.Dw_EDEJnahKdk8mwlY3juSWd4jTIM8Go8NdQLpLWeH2kCmDHNg0RZKvbINEc3jF70bPECCzuIi_fH3ovlUeQ_o7KqbF_5ZMiyOIYQvfsVlgSZpl1YSRHgoX1QaRg_FvOvUilE6xGJ_nRnLSk1VjdTWJFARu-IGEfed0aQ1mCsh5Hq9e2q55iQGlMUQVIHQ7X6ZzXNHu4QW7jSMYLskP7Vg-EjQizrGvHC04S2SsglZ0znm5eyz0KR71zcdkR2O80_v6u2GM6APMCQrJ3GQ7SSsguwEKMxxvCpQvZ65lCV4bujvP7tD4A8pn_SCKTjFGU8MulGzHi2z-K4EZU6yZXFQ.DOkezaeJUsRg6h8q.oXC0AtIkn53UgjF2KdxW.1NBMp9IdXW-U5emoiIrE7A"
// Step 1 : Split JWE string
// const jweParts = nodejoseEncPayload.split(".");
const jweParts = nodeCryptoEncPayload.split(".");
const jweProtectedHeaderPart = jweParts[0];
const jweEncryptedKeyPart = jweParts[1];
const jweIvPart = jweParts[2];
const jweCipherTextPart = jweParts[3];
const jweAuthTagPart = jweParts[4];
//step 2 : Decrypt CEK
const sk = crypto.createPrivateKey({ key: testJwk, format: 'jwk',encoding: "utf-8" });
const decryptedCek = crypto.privateDecrypt({key:sk,oaepHash: 'sha256',padding: crypto.constants.RSA_PKCS1_OAEP_PADDING},
Buffer.from(jweEncryptedKeyPart,"base64")
);
//step 3 : Decrypt Content
const dataToDecryptPart = jweCipherTextPart.slice(0, jweCipherTextPart.length - jweCipherTextPart.length);
const deCipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(decryptedCek, 'base64'), Buffer.from(jweIvPart, 'base64'));
deCipher.setAuthTag(Buffer.from(jweAuthTagPart, 'base64'))
deCipher.setAAD(Buffer.from(jweProtectedHeaderPart, 'base64'));
let decrypted = deCipher.update(Buffer.from(jweCipherTextPart, 'base64'), null, 'utf8');
deCipher.setAutoPadding(false);
decrypted += deCipher.final('utf8');
console.log("Decrypted text : "+decrypted)
**Console output**
Decrypted text : {"test":"test"}
节点加密解密代码块与节点加密生成的 JWE nodeCryptoEncPayload 一起使用,但使用节点 jose 生成的 JWE nodejoseEncPayload 时出错
标头必须按原样作为 AAD 传递,即 Base64 编码。当前代码中的错误是 Base64 解码。修复:
deCipher.setAAD(Buffer.from(jweProtectedHeaderPart, 'utf8'));