Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make HMAC_SHA256 key from secret string to use it with JWT in jose4j?

I want to produce JWTs and sign them with HMAC_SHA256. For that task I must use jose4j. I have tried to generate key based on secret with:

SecretKeySpec key = new SecretKeySpec(("secret").getBytes("UTF-8"), AlgorithmIdentifiers.HMAC_SHA512);

but it generates 40bits key while 512bit one is required for signing using HMAC_SHA256.

  • The primary issue - how to sign tokens with HMAC_SHA512 using jose4j?
  • Issue created by my approach solving issue above - how to make 512bit long secret key based on secret string?
like image 347
foki Avatar asked Aug 14 '15 09:08

foki


2 Answers

Section 3.2 of JWA / RFC 7518 says that a key of the same size as the hash output or larger must be used with the JWS HMAC SHA-2 algorithms (i.e, 256 bits for "HS256", 384bits/"HS384", & 512 bits/"HS512"). It's generally a good idea to follow this advice from the IETF and NIST. Roughly speaking the security of an HMAC comes from the size of the hash output and the key length, whichever is smaller. So using the bytes of "secret" as the key gives you a key that's only 48 bits long and, in practice, provides considerably less security than even that because it's a dictionary word, regardless of the strength of the HMAC SHA-2 algorithm you chose.

By default jose4j enforces the minimum key sizes mandated by JWA/RFC 7518. However, as Hans points out, there are ways to tell jose4j to relax the key length requirement. This can be done with JwtConsumer by calling .setRelaxVerificationKeyValidation() on JwtConsumerBuilder and on JsonWebSignature directly with .setDoKeyValidation(false). Below is a quick example producing and consuming a JWT using HMAC SHA256 that shows both.

JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(5);
claims.setSubject("foki");
claims.setIssuer("the issuer");
claims.setAudience("the audience");

String secret = "secret";
Key key = new HmacKey(secret.getBytes("UTF-8"));

JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setKey(key);
jws.setDoKeyValidation(false); // relaxes the key length requirement

String jwt = jws.getCompactSerialization();
System.out.println(jwt);

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
        .setRequireExpirationTime()
        .setAllowedClockSkewInSeconds(30)
        .setRequireSubject()
        .setExpectedIssuer("the issuer")
        .setExpectedAudience("the audience")
        .setVerificationKey(key)
        .setRelaxVerificationKeyValidation() // relaxes key length requirement 
        .build();

JwtClaims processedClaims = jwtConsumer.processToClaims(jwt);
System.out.println(processedClaims);
like image 81
Brian Campbell Avatar answered Nov 09 '22 09:11

Brian Campbell


A common approach is to hash the secret before using it as a signing key.

MessageDigest md = MessageDigest.getInstance("SHA-256");
String secret = "secret";
md.update(secret.getBytes("UTF-8"));
byte[] key = md.digest();

The alternative is to relax the requirement on the key length with something like:

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
     .setVerificationKey(new HmacKey(secret.getBytes())) 
     .setRelaxVerificationKeyValidation() // allow shorter HMAC keys when used w/ HSxxx algs 
     .build();
like image 29
Hans Z. Avatar answered Nov 09 '22 11:11

Hans Z.