Search code examples
javaencryptionjwtjwejose4j

How to decrypt a JWE using JOSE4J using public key


Feel kind of out of my depth here.

I have a message that I'm trying to encrypt on a react front end using a public key and the jose library. Then that message will be sent to the java back end and needs to be decrypted by the same public key so that the message can be read.

On the front end, this is my code:

  const secret = jose.base64url.decode('zH4NRP1HMALxxCFnRZABFA7GOJtzUAgIj02alfL1lvI');
  const jwt = await new jose.EncryptJWT({ foo: 'bar' })
    .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256' })
    .setIssuedAt()
    .setIssuer('urn:example:issuer')
    .setAudience('urn:example:audience')
    .setExpirationTime('2h')
    .encrypt(secret);

And then this is my attempt at decrypting on the backend

         byte[] encoded = Base64.getUrlDecoder().decode("zH4NRP1HMALxxCFnRZABFA7GOJtzUAgIj02alfL1lvI");
         
         PublicKey pk = null;
         
          try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
              X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
              pk = keyFactory.generatePublic(keySpec);
            } catch (NoSuchAlgorithmException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
          
         
         JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                 .setRequireExpirationTime()
                 .setVerificationKey(pk)
                 .build();
         
         try {
            JwtClaims jwtDecoded = jwtConsumer.processToClaims(forwardKey);
            System.out.println(jwtDecoded.getStringClaimValue("foo"));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

However Java doesn't seem to have the HS256 algorithm available when creating the PublicKey object using the keyfactory, but then I can't find any information on what algorithms to use for the encryption on the front end.

Decrypting on the front end using the same secret works fine. But when trying to do the same thing in JAVA its like 20 more lines and I'm not sure what those lines are supposed to be at all


Solution

  • A128CBC-HS256 stands for AES_128_CBC_HMAC_SHA_256. This algorithm encrypts with AES-128 in CBC mode, authentication is done with HMAC/SHA256. A 32 bytes key is required as primary key (from which two secondary keys are derived: the first 16 bytes are used for authentication, the second for encryption).

    The current Java code tries to import the key with X509EncodedKeySpec. This class is intended for importing a DER encoded SPKI key in the context of RSA, so it is completely wrong here.

    Instead, a possible Java implementation is:

    import java.security.Key;
    import java.util.Base64;
    import org.jose4j.jwe.JsonWebEncryption;
    import org.jose4j.jwt.JwtClaims;
    import org.jose4j.keys.HmacKey;
    
    ...
     
    String jwt = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..WhLXSXBUTRSERZuRTMBLrw.PqYS4ZoVHEnLA3nKyurYtO8oPRI9ncnd9gWftneUQmbdV1y8EaL3rBFYCxp1MFm7c5kZe4hx98uHpdAAkrznhmqiQ7Z2MGNlmp9aqZquo8dq1HJOUj-DoqSdsVGx7a1XoAR35bNW7rhru62zYMJfeA.-18U12AoCxO9NNOG6mHnNQ"; // encrypted JWT
    byte[] encoded = Base64.getUrlDecoder().decode("zH4NRP1HMALxxCFnRZABFA7GOJtzUAgIj02alfL1lvI"); // primary key
    Key key = new HmacKey(encoded); // import primary key
        
    JsonWebEncryption jwe = new JsonWebEncryption();
    jwe.setKey(key);
    jwe.setCompactSerialization(jwt);
    String payload = jwe.getPayload(); // decrypt
    
    JwtClaims claims = JwtClaims.parse(payload);
    String fooClaim = claims.getStringClaimValue("foo"); // get foo claim
    
    System.out.println(payload);  // {"foo":"bar","iat":1692645846,"iss":"urn:example:issuer","aud":"urn:example:audience","exp":1692653046}
    System.out.println(fooClaim); // bar
    

    Here, the encrypted token (jwt) was created using the posted NodeJS code.