io.jsonwebtoken.security.Keys Maven / Gradle / Ivy
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
/**
* Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s.
*
* @since 0.10.0
*/
public final class Keys {
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge";
private static final Class> BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME);
private static final Class>[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class};
private static final Class>[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class};
private static final Class>[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class};
private static T invokeStatic(String method, Class>[] argTypes, Object... args) {
return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args);
}
//prevent instantiation
private Keys() {
}
/**
* Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array.
*
* @param bytes the key byte array
* @return a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array.
* @throws WeakKeyException if the key byte array length is less than 256 bits (32 bytes) as mandated by the
* JWT JWA Specification
* (RFC 7518, Section 3.2)
*/
public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException {
if (bytes == null) {
throw new InvalidKeyException("SecretKey byte array cannot be null.");
}
int bitLength = bytes.length * 8;
//Purposefully ordered higher to lower to ensure the strongest key possible can be generated.
if (bitLength >= 512) {
return new SecretKeySpec(bytes, "HmacSHA512");
} else if (bitLength >= 384) {
return new SecretKeySpec(bytes, "HmacSHA384");
} else if (bitLength >= 256) {
return new SecretKeySpec(bytes, "HmacSHA256");
}
String msg = "The specified key byte array is " + bitLength + " bits which " +
"is not secure enough for any JWT HMAC-SHA algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " +
"size >= 256 bits (the key size must be greater than or equal to the hash " +
"output size). Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() " +
"or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " +
"algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
throw new WeakKeyException(msg);
}
/**
* Deprecation Notice
*
* As of JJWT 0.12.0, symmetric (secret) key algorithm instances can generate a key of suitable
* length for that specific algorithm by calling their {@code key()} builder method directly. For example:
*
*
* {@link Jwts.SIG#HS256}.key().build();
* {@link Jwts.SIG#HS384}.key().build();
* {@link Jwts.SIG#HS512}.key().build();
*
*
* Call those methods as needed instead of this static {@code secretKeyFor} helper method - the returned
* {@link KeyBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if
* desired, whereas this {@code secretKeyFor} method does not. Consequently this helper method will be removed
* before the 1.0 release.
*
* Previous Documentation
*
* Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.
*
* JWA Specification (RFC 7518), Section 3.2
* requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a
* secure-random generated SecretKey that adheres to the required minimum key length. The lengths are:
*
*
* JWA HMAC-SHA Key Length Requirements
*
* Algorithm
* Key Length
*
*
* HS256
* 256 bits (32 bytes)
*
*
* HS384
* 384 bits (48 bytes)
*
*
* HS512
* 512 bits (64 bytes)
*
*
*
* @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use.
* @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}.
* @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256},
* {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512}
* @deprecated since 0.12.0. Use your preferred {@link MacAlgorithm} instance's
* {@link MacAlgorithm#key() key()} builder method directly.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
SecureDigestAlgorithm, ?> salg = Jwts.SIG.get().get(alg.name());
if (!(salg instanceof MacAlgorithm)) {
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
throw new IllegalArgumentException(msg);
}
return ((MacAlgorithm) salg).key().build();
}
/**
* Deprecation Notice
*
* As of JJWT 0.12.0, asymmetric key algorithm instances can generate KeyPairs of suitable strength
* for that specific algorithm by calling their {@code keyPair()} builder method directly. For example:
*
*
* Jwts.SIG.{@link Jwts.SIG#RS256 RS256}.keyPair().build();
* Jwts.SIG.{@link Jwts.SIG#RS384 RS384}.keyPair().build();
* Jwts.SIG.{@link Jwts.SIG#RS512 RS512}.keyPair().build();
* ... etc ...
* Jwts.SIG.{@link Jwts.SIG#ES512 ES512}.keyPair().build();
*
* Call those methods as needed instead of this static {@code keyPairFor} helper method - the returned
* {@link KeyPairBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if
* desired, whereas this {@code keyPairFor} method does not. Consequently this helper method will be removed
* before the 1.0 release.
*
* Previous Documentation
*
* Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
*
* If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:
*
*
* Generated RSA Key Sizes
*
* JWA Algorithm
* Key Size
*
*
* RS256
* 2048 bits
*
*
* PS256
* 2048 bits
*
*
* RS384
* 3072 bits
*
*
* PS384
* 3072 bits
*
*
* RS512
* 4096 bits
*
*
* PS512
* 4096 bits
*
*
*
* If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following:
*
*
* Generated Elliptic Curve Key Parameters
*
* JWA Algorithm
* Key Size
* JWA Curve Name
* ASN1 OID Curve Name
*
*
* ES256
* 256 bits
* {@code P-256}
* {@code secp256r1}
*
*
* ES384
* 384 bits
* {@code P-384}
* {@code secp384r1}
*
*
* ES512
* 521 bits
* {@code P-521}
* {@code secp521r1}
*
*
*
* @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use.
* @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
* @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm
* @deprecated since 0.12.0 in favor of your preferred
* {@link io.jsonwebtoken.security.SignatureAlgorithm} instance's
* {@link SignatureAlgorithm#keyPair() keyPair()} builder method directly.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
SecureDigestAlgorithm, ?> salg = Jwts.SIG.get().get(alg.name());
if (!(salg instanceof SignatureAlgorithm)) {
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
throw new IllegalArgumentException(msg);
}
SignatureAlgorithm asalg = ((SignatureAlgorithm) salg);
return asalg.keyPair().build();
}
/**
* Returns a new {@link Password} instance suitable for use with password-based key derivation algorithms.
*
* Usage Note: Using {@code Password}s outside of key derivation contexts will likely
* fail. See the {@link Password} JavaDoc for more, and also note the Password Safety section below.
*
* Password Safety
*
* Instances returned by this method use a clone of the specified {@code password} character array
* argument - changes to the argument array will NOT be reflected in the returned key, and vice versa. If you wish
* to clear a {@code Password} instance to ensure it is no longer usable, call its {@link Password#destroy()}
* method will clear/overwrite its internal cloned char array. Also note that each subsequent call to
* {@link Password#toCharArray()} will also return a new clone of the underlying password character array per
* standard JCE key behavior.
*
* @param password the raw password character array to clone for use with password-based key derivation algorithms.
* @return a new {@link Password} instance that wraps a new clone of the specified {@code password} character array.
* @see Password#toCharArray()
* @since 0.12.0
*/
public static Password password(char[] password) {
return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password});
}
/**
* Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a
* {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic
* operations. For example:
*
*
* SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();
*
* Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its
* mandatory {@code Provider} if necessary.
*
* This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM
* (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic
* operations.
*
* @param key the secret key to use for cryptographic operations, potentially associated with a configured
* {@link Provider}
* @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any
* specified provider.
* @since 0.12.0
*/
public static SecretKeyBuilder builder(SecretKey key) {
Assert.notNull(key, "SecretKey cannot be null.");
return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key);
}
/**
* Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a
* {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a
* {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic
* operations. For example:
*
*
* PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();
*
* Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its
* mandatory {@code Provider} or {@code PublicKey} if necessary.
*
* This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM
* (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used
* during cryptographic operations.
*
* @param key the private key to use for cryptographic operations, potentially associated with a configured
* {@link Provider} or {@link PublicKey}.
* @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any
* specified provider or {@code PublicKey}
* @since 0.12.0
*/
public static PrivateKeyBuilder builder(PrivateKey key) {
Assert.notNull(key, "PrivateKey cannot be null.");
return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key);
}
}