javascriptnode.jsencryptionjwejose

Unable to decrypt JWE with NPM Jose package


I'm able to decrypt a JWE with an older version of jose but I'm struggling to use the latest version API.

My token headers are the following:

{
  "alg": "A128KW",
  "enc": "A128CBC-HS256",
  "typ": "JWT"
}

With jose 2.0.3 this code (copied from another stackoverflow post) is working to decrypt the payload:

const { JWK, JWE } = require('jose');
const privateKey = JWK.asKey("my key");
const jwe = "my JWE"
const jwt = JWE.decrypt(jwe, privateKey);
const payload = Buffer.from(jwt.toString().split('.')[1], 'base64');
const data = JSON.parse(payload);

Obviously I cannot share the the private key. It's a string with 16 characters.

I tried to used a later version of jose (4.9.2) but the API changed totally. I tried this but it does not end well:

const jose = require('jose');
const jwe = "my token";
const key = await jose.importJWK({ kty: 'oct', k: 'my key', alg: 'A128CBC-HS256' })
const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, key);
console.log(protectedHeader)
console.log(new TextDecoder().decode(plaintext))

I get this error :

TypeError: Invalid key size for alg: A128KW

I confirm my key is 16 characters long (so 128 bits normally) so I don't get this error. Any hint?


Solution

  • The issue is your secret key. You're using 16 character string and passing it in as a JWK. JWK k is meant to be base64url encoded, so your 16 characters "encoded" becomes a 12 byte secret when "decoded".

    Here's what will work, don't bother with a key import for symmetric secrets, just pass a Buffer instance instead.

    const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, Buffer.from('my 16 bytes secret'));
    console.log(protectedHeader)
    console.log(new TextDecoder().decode(plaintext))
    

    Additionally, if your payload is a JWT claims set, you might as well do a proper JWT validation combined with the decrypt operation. docs

    const { plaintext, protectedHeader } = await jose.jwtDecrypt(jwe, Buffer.from('my 16 bytes secret'));
    console.log(protectedHeader);
    console.log(payload);