Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static secret as byte[], Key or String?

I have started to work with JJWT to handle JWT on my server application.

My JWT secret will be stored at resources folder and I will load the secret with Properties class.

The JJWT provides three methods to sign the JWT, one uses byte[], other uses String and the other uses Key:

JwtBuilder signWith(SignatureAlgorithm var1, byte[] var2);

JwtBuilder signWith(SignatureAlgorithm var1, String var2);

JwtBuilder signWith(SignatureAlgorithm var1, Key var2);

The question: Regarding security, charset and other things, there are any recommendations of which one I should use?

For while, I stand with String, since Properties return a String.

like image 886
Paulo Avatar asked Oct 26 '16 02:10

Paulo


People also ask

How do you generate a secret key from a string?

Step 1: Create a KeyGenerator object The KeyGenerator class provides getInstance() method which accepts a String variable representing the required key-generating algorithm and returns a KeyGenerator object that generates secret keys. Create KeyGenerator object using the getInstance() method as shown below.

How do I convert SecretKeySpec to string?

Converting SecretKeySpec to String and vice-versa: you can use getEncoded() method in SecretKeySpec which will give byteArray , from that you can use encodeToString() to get string value of SecretKeySpec in Base64 object.

What is a secret key in Java?

A secret key is the piece of information or parameter that is used to encrypt and decrypt messages. In Java, we have SecretKey an interface that defines it as a secret (symmetric) key. The purpose of this interface is to group (and provide type safety for) all secret key interfaces.

What is JwtBuilder?

public class JwtBuilder extends java.lang.Object. This API is used for the creation of JSON Web Token (JWT) security tokens conforming the JWT specification as defined in: JSON Web Token (JWT).


1 Answers

With JJWT >= 0.10.0, signWith(SignatureAlgorithm var1, String var2) has been deprecated because of the confusion between raw strings and Base64-encoded strings:

/**
 * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS.
 *
 * <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
 * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.</p>
 *
 * <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.</h4>
 *
 * <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
 * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
 * obtained from the String argument.</p>
 *
 * <p>This method always expected a String argument that was effectively the same as the result of the following
 * (pseudocode):</p>
 *
 * <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
 *
 * <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to
 * use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is
 * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>
 *
 * <p>See this
 * <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
 * StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
 * signature operations.</p>
 *
 * <p>To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:
 * <pre><code>
 * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
 * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
 * jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
 * </code></pre>
 * </p>
 *
 * <p>This method will be removed in the 1.0 release.</p>
 *
 * @param alg                    the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
 * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the
 *                               JWT.
 * @return the builder for method chaining.
 * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
 *                             described by {@link SignatureAlgorithm#forSigningKey(Key)}.
 * @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead.  This
 * method will be removed in the 1.0 release.
 */
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey);

This method expects the string argument to be a Base64-encoded secret key byte array. It does not assume a general string, like a user password for example, as the signing key. JJWT assumes Base64 encoding because if you're specifying a string password that is not Base64-encoded, you're probably using a poorly formed or weak key.

The JWT JWA specification REQUIRES that HMAC signing keys have lengths equal to or greater than the signature byte array length.

That means that:

| If you're signing with: | your key (byte array) length MUST be: |
| ----------------------- | ------------------------------------- |
| HMAC SHA 256            | >= 256 bits (32 bytes)                |
| HMAC SHA 384            | >= 384 bits (48 bytes)                |
| HMAC SHA 512            | >= 512 bits (64 bytes)                |

Many online JWT sites and tools just just get this plain wrong - they allow you to think that you could type in or use any old string and you're good. Some go as far as even pre-populating the key with the word secret (clearly a bad idea and not even spec-compliant because it's too short!).

To help simplify things for you, JJWT provides a utility to help you generate sufficient secure-random keys suitable for spec-compliant signing via the io.jsonwebtoken.security.Keys class's secretKeyFor method. For example:

//creates a spec-compliant secure-random key:
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //or HS384 or HS512

If you wanted to store the generated key as a String, you could presumably Base64 encode it:

String base64Key = Encoders.BASE64.encode(key.getEncoded());

But note: the resulting base64Key string is not considered safe to show to anyone. Base64 encoding is not encryption - the value still needs to be kept secret. How you do this is up to you (encrypt it, etc).

Now, when it is time to create a JWS, you could pass in that base64Key value, and JJWT knows to base64 decode it first to get the real bytes, which are then used to compute the signature:

Jwts.builder()
    //...
    .signWith(SignatureAlgorithm.HS512, base64Key)
    .compact();

And while you could do this, it is not recommended per the above deprecation notice in the JavaDoc due to the ambiguity between raw strings and base64-encoded strings.

As a result, it is recommended to use either the JWT builder's signWith(Key) or signWith(Key, SignatureAlgorithm) methods which guarantee a type-safe Key argument. For example:

  Jwts.builder()
    //...
    .signWith(key) // or signWith(key, preferredSignatureAlgorithm)
    .compact();

signWith(Key) is recommended to let JJWT figure out the strongest algorithm possible based on the strength of your supplied key. signWith(Key,SignatureAlgorithm) allows you to specify a desired algorithm if you don't want the strongest possible one.

Both methods will reject any Key that doesn't meet the minimum RFC requirements.

like image 182
Les Hazlewood Avatar answered Sep 18 '22 15:09

Les Hazlewood