EncryptionAlgorithm changes, class cleanup, test coverage, etc. AES encryption, both GCM and HmacSha2 variants are complete. Classes might be moved to another package. Have not yet started Builder and Parser work to support JWE compact strings.

This commit is contained in:
Les Hazlewood 2016-04-21 18:16:32 -07:00
parent d111dc8b22
commit 9b434cdf9c
31 changed files with 503 additions and 642 deletions

24
pom.xml
View File

@ -288,6 +288,30 @@
<packageBranchRate>100</packageBranchRate> -->
<haltOnFailure>true</haltOnFailure>
<regexes>
<regex>
<!-- Work in progress: -->
<pattern>io.jsonwebtoken.impl.serialization.*</pattern>
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<!-- Work in progress: -->
<pattern>io.jsonwebtoken.impl.DefaultJweFactory</pattern>
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<!-- Work in progress: -->
<pattern>io.jsonwebtoken.impl.DefaultJweHeader</pattern>
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<!-- Work in progress: -->
<pattern>io.jsonwebtoken.impl.DispatchingParser</pattern>
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<!-- This was pulled in from another project (without pulling in the tests) -
we don't care about coverage here really.: -->

View File

@ -64,7 +64,7 @@ public enum EncryptionAlgorithmName {
}
}
throw new CryptoException("Unsupported JWE Content Encryption Algorithm name: " + name);
throw new IllegalArgumentException("Unsupported JWE Content Encryption Algorithm name: " + name);
}
@Override

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken;
import io.jsonwebtoken.impl.crypto.AesEncryptionAlgorithm;
import io.jsonwebtoken.impl.crypto.AbstractAesEncryptionAlgorithm;
import io.jsonwebtoken.impl.crypto.GcmAesEncryptionAlgorithm;
import io.jsonwebtoken.impl.crypto.HmacAesEncryptionAlgorithm;
import io.jsonwebtoken.lang.Collections;
@ -9,6 +9,12 @@ import java.util.List;
public final class EncryptionAlgorithms {
//for code coverage only
private static final EncryptionAlgorithms INSTANCE = new EncryptionAlgorithms();
//prevent instantiation
private EncryptionAlgorithms(){}
public static final HmacAesEncryptionAlgorithm A128CBC_HS256 =
new HmacAesEncryptionAlgorithm(EncryptionAlgorithmName.A128CBC_HS256.getValue(), SignatureAlgorithm.HS256);
@ -27,6 +33,6 @@ public final class EncryptionAlgorithms {
public static final GcmAesEncryptionAlgorithm A256GCM =
new GcmAesEncryptionAlgorithm(EncryptionAlgorithmName.A256GCM.getValue(), 32);
public static List<? extends AesEncryptionAlgorithm> VALUES =
public static List<? extends AbstractAesEncryptionAlgorithm> VALUES =
Collections.of(A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM, A256GCM);
}

View File

@ -74,7 +74,7 @@ public interface JweHeader<T extends JweHeader<T>> extends Header<T> {
* @return the JWE Key Management Algorithm header value or {@code null} if not present. This will always be
* {@code non-null} on validly constructed JWE instances, but could be {@code null} during construction.
*/
KeyManagementAlgorithm getKeyManagementAlgorithm();
KeyManagementAlgorithmName getKeyManagementAlgorithm();
/**
* Sets the JWE Key Management
@ -88,5 +88,5 @@ public interface JweHeader<T extends JweHeader<T>> extends Header<T> {
* @return the JWE Key Management Algorithm header value or {@code null} if not present. This will always be
* {@code non-null} on validly constructed JWE instances, but could be {@code null} during construction.
*/
T setKeyManagementAlgorithm(KeyManagementAlgorithm alg);
T setKeyManagementAlgorithm(KeyManagementAlgorithmName alg);
}

View File

@ -11,7 +11,7 @@ import java.util.List;
*
* @since 0.7.0
*/
public enum KeyManagementAlgorithm {
public enum KeyManagementAlgorithmName {
RSA1_5("RSA1_5", "RSAES-PKCS1-v1_5", Collections.<String>emptyList(), "RSA/ECB/PKCS1Padding"),
RSA_OAEP("RSA-OAEP", "RSAES OAEP using default parameters", Collections.<String>emptyList(), "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"),
@ -36,7 +36,7 @@ public enum KeyManagementAlgorithm {
private final List<String> moreHeaderParams;
private final String jcaName;
KeyManagementAlgorithm(String value, String description, List<String> moreHeaderParams, String jcaName) {
KeyManagementAlgorithmName(String value, String description, List<String> moreHeaderParams, String jcaName) {
this.value = value;
this.description = description;
this.moreHeaderParams = moreHeaderParams;
@ -82,22 +82,22 @@ public enum KeyManagementAlgorithm {
}
/**
* Returns the corresponding {@code KeyManagementAlgorithm} enum instance based on a
* Returns the corresponding {@code KeyManagementAlgorithmName} enum instance based on a
* case-<em>insensitive</em> name comparison of the specified JWE <code>alg</code> value.
*
* @param name the case-insensitive JWE <code>alg</code> header value.
* @return Returns the corresponding {@code KeyManagementAlgorithm} enum instance based on a
* @return Returns the corresponding {@code KeyManagementAlgorithmName} enum instance based on a
* case-<em>insensitive</em> name comparison of the specified JWE <code>alg</code> value.
* @throws CryptoException if the specified value does not match any JWE {@code KeyManagementAlgorithm} value.
* @throws CryptoException if the specified value does not match any JWE {@code KeyManagementAlgorithmName} value.
*/
public static KeyManagementAlgorithm forName(String name) throws CryptoException {
for (KeyManagementAlgorithm alg : values()) {
public static KeyManagementAlgorithmName forName(String name) throws CryptoException {
for (KeyManagementAlgorithmName alg : values()) {
if (alg.getValue().equalsIgnoreCase(name)) {
return alg;
}
}
throw new CryptoException("Unsupported JWE Key Management Algorithm name: " + name);
throw new IllegalArgumentException("Unsupported JWE Key Management Algorithm name: " + name);
}
@Override

View File

@ -29,7 +29,7 @@ public class DefaultJweFactory {
EncryptionAlgorithm encryptionAlgorithm, DecryptionKeyResolver decryptionKeyResolver) {
Assert.notNull(base64UrlCodec, "Base64Url TextCodec cannot be null.");
Assert.notNull(serializationCodec, "SerializationCodec cannot be null.");
Assert.notNull(encryptionAlgorithm, "EncryptionService cannot be null.");
Assert.notNull(encryptionAlgorithm, "EncryptionAlgorithm cannot be null.");
Assert.notNull(decryptionKeyResolver, "DecryptionKeyResolver cannot be null.");
this.serializationCodec = serializationCodec;
this.encryptionAlgorithm = encryptionAlgorithm;

View File

@ -1,7 +1,7 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.KeyManagementAlgorithm;
import io.jsonwebtoken.KeyManagementAlgorithmName;
import java.util.Map;
@ -16,16 +16,16 @@ public class DefaultJweHeader extends DefaultHeader implements JweHeader {
}
@Override
public KeyManagementAlgorithm getKeyManagementAlgorithm() {
public KeyManagementAlgorithmName getKeyManagementAlgorithm() {
String value = getString(JweHeader.ALGORITHM);
if (value != null) {
return KeyManagementAlgorithm.forName(value);
return KeyManagementAlgorithmName.forName(value);
}
return null;
}
@Override
public JweHeader setKeyManagementAlgorithm(KeyManagementAlgorithm alg) {
public JweHeader setKeyManagementAlgorithm(KeyManagementAlgorithmName alg) {
setValue(ALGORITHM, alg.getValue());
return this;
}

View File

@ -12,8 +12,7 @@ import java.security.spec.AlgorithmParameterSpec;
import static io.jsonwebtoken.lang.Arrays.length;
@SuppressWarnings("Duplicates")
public abstract class AesEncryptionAlgorithm implements EncryptionAlgorithm {
public abstract class AbstractAesEncryptionAlgorithm implements EncryptionAlgorithm {
public static final SecureRandom DEFAULT_RANDOM = new SecureRandom();
@ -30,7 +29,7 @@ public abstract class AesEncryptionAlgorithm implements EncryptionAlgorithm {
private final int generatedIvLength;
private final int requiredKeyLength;
public AesEncryptionAlgorithm(String name, String transformationString, int generatedIvLength, int requiredKeyLength) {
public AbstractAesEncryptionAlgorithm(String name, String transformationString, int generatedIvLength, int requiredKeyLength) {
Assert.hasText(name, "Name cannot be null or empty.");
this.name = name;
@ -52,7 +51,7 @@ public abstract class AesEncryptionAlgorithm implements EncryptionAlgorithm {
try {
return doGenerateKey();
} catch (Exception e) {
throw new CryptoException("Unable to obtain AES KeyGenerator: " + e.getMessage(), e);
throw new CryptoException("Unable to generate a new " + getName() + " SecretKey: " + e.getMessage(), e);
}
}
@ -100,7 +99,7 @@ public abstract class AesEncryptionAlgorithm implements EncryptionAlgorithm {
return iv;
}
protected SecureRandom getSecureRandom(CryptoRequest request) {
protected SecureRandom getSecureRandom(EncryptionRequest request) {
SecureRandom random = request.getSecureRandom();
return random != null ? random : DEFAULT_RANDOM;
}

View File

@ -15,25 +15,16 @@
*/
package io.jsonwebtoken.impl.crypto;
import java.security.SecureRandom;
public abstract class AbstractCryptoRequest implements CryptoRequest {
private final SecureRandom random;
private final byte[] key;
private final byte[] iv;
public AbstractCryptoRequest(SecureRandom random, byte[] key, byte[] iv) {
this.random = random;
public AbstractCryptoRequest(byte[] key, byte[] iv) {
this.key = key;
this.iv = iv;
}
@Override
public SecureRandom getSecureRandom() {
return this.random;
}
@Override
public byte[] getKey() {
return this.key;

View File

@ -15,26 +15,15 @@
*/
package io.jsonwebtoken.impl.crypto;
import java.security.SecureRandom;
/**
* @since 0.7.0
*/
public interface CryptoRequest {
/**
* Returns the {@code SecureRandom} to use when performing cryptographic operations when processing the request, or
* {@code null} if a default {@link SecureRandom} should be used.
* Returns the key to use for encryption or decryption depending on the type of request.
*
* @return the {@code SecureRandom} to use when performing cryptographic operations when processing the request, or
* {@code null} if a default {@link SecureRandom} should be used.
*/
SecureRandom getSecureRandom();
/**
* Returns the encryption key to use for encryption or decryption depending on the type of request.
*
* @return the encryption key to use for encryption or decryption depending on the type of request.
* @return the key to use for encryption or decryption depending on the type of request.
*/
byte[] getKey();

View File

@ -15,12 +15,8 @@
*/
package io.jsonwebtoken.impl.crypto;
import java.security.SecureRandom;
public interface DecryptionRequestBuilder {
DecryptionRequestBuilder setSecureRandom(SecureRandom secureRandom);
DecryptionRequestBuilder setInitializationVector(byte[] iv);
DecryptionRequestBuilder setKey(byte[] key);

View File

@ -1,240 +0,0 @@
/*
* Copyright (C) 2016 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.impl.crypto;
import io.jsonwebtoken.lang.Assert;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import static io.jsonwebtoken.lang.Arrays.length;
/**
* Default {@link EncryptionService} implementation that uses AES in GCM mode.
*/
@SuppressWarnings("Duplicates")
public class DefaultAesEncryptionService implements EncryptionService {
private static final int GCM_TAG_SIZE = 16; //number of bytes, not bits. Highest for GCM is 128 bits and recommended
private static final int GCM_NONCE_SIZE = 12; //number of bytes, not bits. 12 is recommended for GCM for efficiency
public static final SecureRandom DEFAULT_RANDOM = new SecureRandom();
protected static final String DECRYPT_NO_IV = "This EncryptionService implementation rejects decryption " +
"requests that do not include initialization vectors. AES " +
"ciphertext without an IV is weak and should never be used.";
private static final byte[] RANDOM_KEY; //TODO: remove this concept
static {
byte[] key = new byte[32];
DEFAULT_RANDOM.nextBytes(key);
RANDOM_KEY = key;
}
private final SecretKey key;
private final SecureRandom random;
public DefaultAesEncryptionService() {
this(RANDOM_KEY, DEFAULT_RANDOM);
}
public DefaultAesEncryptionService(byte[] key) {
this(key, DEFAULT_RANDOM);
}
public DefaultAesEncryptionService(byte[] key, SecureRandom random) {
Assert.notEmpty(key, "Encryption key cannot be null or empty.");
Assert.notNull(random, "SecureRandom instance cannot be null or empty.");
this.key = new SecretKeySpec(key, "AES");
this.random = random;
}
@Override
public byte[] encrypt(byte[] plaintext) {
EncryptionRequest req = EncryptionRequests.builder().setPlaintext(plaintext).build();
EncryptionResult res = encrypt(req);
return res.compact();
}
protected Cipher newCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/GCM/NoPadding");
}
protected Cipher newCipher(int mode, byte[] nonce, SecretKey key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
InvalidKeyException {
Cipher aesGcm = newCipher();
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce);
aesGcm.init(mode, key, spec);
return aesGcm;
}
@Override
public byte[] decrypt(byte[] compact) {
byte[] nonce = getCiphertextNonce(compact);
byte[] ciphertext = getTaggedCiphertext(compact);
DecryptionRequest req = DecryptionRequests.builder()
.setInitializationVector(nonce).setCiphertext(ciphertext).build();
return decrypt(req);
}
protected byte[] getCiphertextNonce(byte[] ciphertext) {
byte[] nonce = new byte[GCM_NONCE_SIZE];
System.arraycopy(ciphertext, 0, nonce, 0, GCM_NONCE_SIZE);
return nonce;
}
protected byte[] getTaggedCiphertext(byte[] ciphertext) {
int taggedCiphertextLength = ciphertext.length - GCM_NONCE_SIZE;
byte[] taggedCipherText = new byte[taggedCiphertextLength];
//remaining data is the tagged ciphertext. Isolate it:
System.arraycopy(ciphertext, GCM_NONCE_SIZE, taggedCipherText, 0, taggedCiphertextLength);
return taggedCipherText;
}
@Override
public EncryptionResult encrypt(EncryptionRequest req) throws CryptoException {
try {
Assert.notNull(req, "EncryptionRequest cannot be null.");
return doEncrypt(req);
} catch (Exception e) {
String msg = "Unable to perform encryption: " + e.getMessage();
throw new CryptoException(msg, e);
}
}
protected EncryptionResult doEncrypt(EncryptionRequest req)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
//Ensure IV:
byte[] iv = req.getInitializationVector();
int ivLength = length(iv);
if (ivLength == 0) {
iv = new byte[GCM_NONCE_SIZE]; //for AES GCM, the IV is often called the nonce
random.nextBytes(iv);
}
//Ensure Key:
SecretKey key = this.key;
byte[] keyBytes = req.getKey();
int keyBytesLength = length(keyBytes);
if (keyBytesLength > 0) {
key = new SecretKeySpec(keyBytes, "AES");
}
Cipher aesGcm = newCipher(Cipher.ENCRYPT_MODE, iv, key);
//Support Additional Associated Data if necessary:
int aadLength = 0;
if (req instanceof AssociatedDataSource) {
byte[] aad = ((AssociatedDataSource) req).getAssociatedData();
aadLength = length(aad);
if (aadLength > 0) {
aesGcm.updateAAD(aad);
}
}
//now for the actual encryption:
byte[] plaintext = req.getPlaintext();
byte[] ciphertext = aesGcm.doFinal(plaintext);
if (aadLength > 0) { //authenticated
byte[] taggedCiphertext = ciphertext; //ciphertext is actually tagged
//separate the tag from the ciphertext:
int ciphertextLength = taggedCiphertext.length - GCM_TAG_SIZE;
ciphertext = new byte[ciphertextLength];
System.arraycopy(taggedCiphertext, 0, ciphertext, 0, ciphertextLength);
byte[] tag = new byte[GCM_TAG_SIZE];
System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, GCM_TAG_SIZE);
return new DefaultAuthenticatedEncryptionResult(iv, ciphertext, tag);
}
return new DefaultEncryptionResult(iv, ciphertext);
}
@Override
public byte[] decrypt(DecryptionRequest req) throws CryptoException {
try {
Assert.notNull(req, "DecryptionRequest cannot be null.");
return doDecrypt(req);
} catch (Exception e) {
String msg = "Unable to perform decryption: " + e.getMessage();
throw new CryptoException(msg, e);
}
}
protected byte[] doDecrypt(DecryptionRequest req)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
byte[] iv = req.getInitializationVector();
Assert.notEmpty(iv, DECRYPT_NO_IV);
//Ensure Key:
SecretKey key = this.key;
byte[] keyBytes = req.getKey();
int keyLen = length(keyBytes);
if (keyLen > 0) {
key = new SecretKeySpec(keyBytes, "AES");
}
final byte[] ciphertext = req.getCiphertext();
byte[] finalBytes = ciphertext; //by default, unless there is an authentication tag
Cipher aesGcm = newCipher(Cipher.DECRYPT_MODE, iv, key);
//Support Additional Associated Data:
if (req instanceof AuthenticatedDecryptionRequest) {
AuthenticatedDecryptionRequest areq = (AuthenticatedDecryptionRequest) req;
byte[] aad = areq.getAssociatedData();
Assert.notEmpty(aad, "AuthenticatedDecryptionRequests must include Additional Authenticated Data.");
aesGcm.updateAAD(aad);
//for tagged GCM, the JVM spec requires that the tag be appended to the end of the ciphertext
//byte array. So we'll append it here:
byte[] tag = areq.getAuthenticationTag();
Assert.notEmpty(tag, "AuthenticatedDecryptionRequests must include an authentication tag.");
byte[] taggedCiphertext = new byte[ciphertext.length + tag.length];
System.arraycopy(ciphertext, 0, taggedCiphertext, 0, ciphertext.length);
System.arraycopy(tag, 0, taggedCiphertext, ciphertext.length, tag.length);
finalBytes = taggedCiphertext;
}
return aesGcm.doFinal(finalBytes);
}
}

View File

@ -17,8 +17,6 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.lang.Assert;
import java.security.SecureRandom;
public class DefaultAuthenticatedDecryptionRequest extends DefaultDecryptionRequest
implements AuthenticatedDecryptionRequest {
@ -26,8 +24,8 @@ public class DefaultAuthenticatedDecryptionRequest extends DefaultDecryptionRequ
private final byte[] tag;
public DefaultAuthenticatedDecryptionRequest(SecureRandom secureRandom, byte[] key, byte[] iv, byte[] ciphertext, byte[] aad, byte[] tag) {
super(secureRandom, key, iv, ciphertext);
public DefaultAuthenticatedDecryptionRequest(byte[] key, byte[] iv, byte[] ciphertext, byte[] aad, byte[] tag) {
super(key, iv, ciphertext);
Assert.notEmpty(tag, "Authentication tag cannot be null or empty.");
this.aad = aad;
this.tag = tag;

View File

@ -17,14 +17,12 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.lang.Assert;
import java.security.SecureRandom;
public class DefaultDecryptionRequest extends AbstractCryptoRequest implements DecryptionRequest {
private final byte[] ciphertext;
public DefaultDecryptionRequest(SecureRandom secureRandom, byte[] key, byte[] iv, byte[] ciphertext) {
super(secureRandom, key, iv);
public DefaultDecryptionRequest(byte[] key, byte[] iv, byte[] ciphertext) {
super(key, iv);
Assert.notEmpty(ciphertext, "ciphertext cannot be null or empty.");
this.ciphertext = ciphertext;
}

View File

@ -17,29 +17,20 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.lang.Assert;
import java.security.SecureRandom;
import static io.jsonwebtoken.lang.Arrays.clean;
public class DefaultDecryptionRequestBuilder implements DecryptionRequestBuilder {
public static final String AAD_NEEDS_TAG_MSG = "If you specify additional authentication data during " +
"decryption, you must also specify the authentication tag " +
"computed during encryption.";
"decryption, you must also specify the authentication tag " +
"computed during encryption.";
private SecureRandom secureRandom;
private byte[] iv;
private byte[] key;
private byte[] ciphertext;
private byte[] aad;
private byte[] tag;
@Override
public DecryptionRequestBuilder setSecureRandom(SecureRandom secureRandom) {
this.secureRandom = secureRandom;
return this;
}
@Override
public DecryptionRequestBuilder setInitializationVector(byte[] iv) {
this.iv = clean(iv);
@ -80,10 +71,10 @@ public class DefaultDecryptionRequestBuilder implements DecryptionRequestBuilder
}
if (aad != null || tag != null) {
return new DefaultAuthenticatedDecryptionRequest(secureRandom, key, iv, ciphertext, aad, tag);
return new DefaultAuthenticatedDecryptionRequest(key, iv, ciphertext, aad, tag);
}
return new DefaultDecryptionRequest(secureRandom, key, iv, ciphertext);
return new DefaultDecryptionRequest(key, iv, ciphertext);
}
}

View File

@ -21,12 +21,20 @@ import java.security.SecureRandom;
public class DefaultEncryptionRequest extends AbstractCryptoRequest implements EncryptionRequest {
private final SecureRandom random;
private final byte[] plaintext;
public DefaultEncryptionRequest(SecureRandom secureRandom, byte[] key, byte[] iv, byte[] plaintext) {
super(secureRandom, key, iv);
super(key, iv);
Assert.notEmpty(plaintext, "plaintext cannot be null or empty.");
this.plaintext = plaintext;
this.random = secureRandom;
}
@Override
public SecureRandom getSecureRandom() {
return this.random;
}
@Override

View File

@ -10,6 +10,11 @@ import java.security.Key;
*/
public class DisabledDecryptionKeyResolver implements DecryptionKeyResolver {
/**
* Singleton instance that may be used if direct instantiation is not desired.
*/
public static final DisabledDecryptionKeyResolver INSTANCE = new DisabledDecryptionKeyResolver();
@Override
public Key resolveDecryptionKey(JweHeader header) {
return null;

View File

@ -15,8 +15,19 @@
*/
package io.jsonwebtoken.impl.crypto;
import java.security.SecureRandom;
public interface EncryptionRequest extends CryptoRequest {
/**
* Returns the {@code SecureRandom} to use when performing cryptographic operations when processing the request, or
* {@code null} if a default {@link SecureRandom} should be used.
*
* @return the {@code SecureRandom} to use when performing cryptographic operations when processing the request, or
* {@code null} if a default {@link SecureRandom} should be used.
*/
SecureRandom getSecureRandom();
byte[] getPlaintext();
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (C) 2016 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.impl.crypto;
public interface EncryptionService {
byte[] encrypt(byte[] plaintext) throws CryptoException;
byte[] decrypt(byte[] ciphertext) throws CryptoException;
EncryptionResult encrypt(EncryptionRequest request) throws CryptoException;
byte[] decrypt(DecryptionRequest request) throws CryptoException;
}

View File

@ -8,7 +8,7 @@ import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
public class GcmAesEncryptionAlgorithm extends AesEncryptionAlgorithm {
public class GcmAesEncryptionAlgorithm extends AbstractAesEncryptionAlgorithm {
private static final int GCM_IV_SIZE = 12; //number of bytes, not bits. 12 is recommended for GCM for efficiency
private static final String TRANSFORMATION_STRING = "AES/GCM/NoPadding";

View File

@ -11,7 +11,7 @@ import java.util.Arrays;
import static io.jsonwebtoken.lang.Arrays.length;
public class HmacAesEncryptionAlgorithm extends AesEncryptionAlgorithm {
public class HmacAesEncryptionAlgorithm extends AbstractAesEncryptionAlgorithm {
protected static final String TRANSFORMATION_STRING = "AES/CBC/PKCS5Padding";
@ -46,8 +46,8 @@ public class HmacAesEncryptionAlgorithm extends AesEncryptionAlgorithm {
int subKeyLength = getRequiredKeyLength() / 2;
SecretKey macKey = MacProvider.generateKey(SIGALG);
byte[] macKeyBytes = macKey.getEncoded();
byte[] macKeyBytes = generateHmacKeyBytes();
Assert.notEmpty(macKeyBytes, "Generated HMAC key byte array cannot be null or empty.");
if (macKeyBytes.length > subKeyLength) {
byte[] subKeyBytes = new byte[subKeyLength];
@ -55,6 +55,14 @@ public class HmacAesEncryptionAlgorithm extends AesEncryptionAlgorithm {
macKeyBytes = subKeyBytes;
}
if (macKeyBytes.length != subKeyLength) {
String msg = "Generated HMAC key must be " + subKeyLength + " bytes (" +
subKeyLength * Byte.SIZE + " bits) long. The " + getClass().getName() + " implementation " +
"generated a key " + macKeyBytes.length + " bytes (" +
macKeyBytes.length * Byte.SIZE + " bits) long";
throw new IllegalStateException(msg);
}
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(subKeyLength * Byte.SIZE);
@ -71,6 +79,11 @@ public class HmacAesEncryptionAlgorithm extends AesEncryptionAlgorithm {
return new SecretKeySpec(combinedKeyBytes, getName());
}
protected byte[] generateHmacKeyBytes() {
SecretKey macKey = MacProvider.generateKey(SIGALG);
return macKey.getEncoded();
}
@Override
protected EncryptionResult doEncrypt(EncryptionRequest req) throws Exception {

View File

@ -0,0 +1,60 @@
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.*
class EncryptionAlgorithmNameTest {
@Test
void testGetValue() {
assertEquals 'A128CBC-HS256', EncryptionAlgorithmName.A128CBC_HS256.getValue()
assertEquals 'A192CBC-HS384', EncryptionAlgorithmName.A192CBC_HS384.getValue()
assertEquals 'A256CBC-HS512', EncryptionAlgorithmName.A256CBC_HS512.getValue()
assertEquals 'A128GCM', EncryptionAlgorithmName.A128GCM.getValue()
assertEquals 'A192GCM', EncryptionAlgorithmName.A192GCM.getValue()
assertEquals 'A256GCM', EncryptionAlgorithmName.A256GCM.getValue()
}
@Test
void testGetDescription() {
assertEquals 'AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.3', EncryptionAlgorithmName.A128CBC_HS256.getDescription()
assertEquals 'AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.4', EncryptionAlgorithmName.A192CBC_HS384.getDescription()
assertEquals 'AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, as defined in https://tools.ietf.org/html/rfc7518#section-5.2.5', EncryptionAlgorithmName.A256CBC_HS512.getDescription()
assertEquals 'AES GCM using 128-bit key', EncryptionAlgorithmName.A128GCM.getDescription()
assertEquals 'AES GCM using 192-bit key', EncryptionAlgorithmName.A192GCM.getDescription()
assertEquals 'AES GCM using 256-bit key', EncryptionAlgorithmName.A256GCM.getDescription()
}
@Test
void testGetJcaName() {
for( def name : EncryptionAlgorithmName.values() ) {
if (name.getValue().contains("GCM")) {
assertEquals 'AES/GCM/NoPadding', name.getJcaName()
} else {
assertEquals 'AES/CBC/PKCS5Padding', name.getJcaName()
}
}
}
@Test
void testToString() {
for( def name : EncryptionAlgorithmName.values() ) {
assertEquals name.toString(), name.getValue()
}
}
@Test
void testForName() {
def name = EncryptionAlgorithmName.forName('A128GCM')
assertSame name, EncryptionAlgorithmName.A128GCM
}
@Test
void testForNameFailure() {
try {
EncryptionAlgorithmName.forName('foo')
fail()
} catch (IllegalArgumentException expected) {
}
}
}

View File

@ -0,0 +1,51 @@
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.*
class KeyManagementAlgorithmNameTest {
@Test
void testToString() {
for( def name : KeyManagementAlgorithmName.values()) {
assertEquals name.value, name.toString()
}
}
@Test
void testGetDescription() {
for( def name : KeyManagementAlgorithmName.values()) {
assertNotNull name.getDescription() //TODO improve this for actual value testing
}
}
@Test
void testGetMoreHeaderParams() {
for( def name : KeyManagementAlgorithmName.values()) {
assertNotNull name.getMoreHeaderParams() //TODO improve this for actual value testing
}
}
@Test
void testGetJcaName() {
for( def name : KeyManagementAlgorithmName.values()) {
assertNotNull name.getJcaName() //TODO improve this for actual value testing
}
}
@Test
void testForName() {
def name = KeyManagementAlgorithmName.forName('A128KW')
assertSame name, KeyManagementAlgorithmName.A128KW
}
@Test
void testForNameFailure() {
try {
KeyManagementAlgorithmName.forName('foo')
fail()
} catch (IllegalArgumentException expected) {
}
}
}

View File

@ -112,4 +112,11 @@ class SignatureAlgorithmTest {
}
}
}
@Test
void testToString() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
assertEquals alg.value, alg.toString()
}
}
}

View File

@ -0,0 +1,120 @@
package io.jsonwebtoken.impl.crypto
import org.junit.Test
import java.security.SecureRandom
import static org.junit.Assert.*
class AbstractAesEncryptionAlgorithmTest {
@Test
void testConstructorWithIvLargerThanAesBlockSize() {
try {
new TestAesEncryptionAlgorithm('foo', 'foo', 17, 16);
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testConstructorWithoutIvLength() {
try {
new TestAesEncryptionAlgorithm('foo', 'foo', 0, 16);
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testConstructorWithoutRequiredKeyLength() {
try {
new TestAesEncryptionAlgorithm('foo', 'foo', 16, 0);
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testDoEncryptFailure() {
def alg = new TestAesEncryptionAlgorithm('foo', 'foo', 16, 16) {
@Override
protected EncryptionResult doEncrypt(EncryptionRequest req) throws Exception {
throw new IllegalArgumentException('broken')
}
}
def req = EncryptionRequests.builder()
.setAdditionalAuthenticatedData('foo'.getBytes())
.setInitializationVector('iv'.getBytes())
.setKey(alg.generateKey().getEncoded())
.setPlaintext('bar'.getBytes())
.build();
try {
alg.encrypt(req)
} catch (CryptoException expected) {
assertTrue expected.getCause() instanceof IllegalArgumentException
assertTrue expected.getCause().getMessage().equals('broken')
}
}
@Test
void testAssertKeyLength() {
def requiredKeyLength = 16
def alg = new TestAesEncryptionAlgorithm('foo', 'foo', 16, requiredKeyLength)
byte[] bytes = new byte[requiredKeyLength + 1] //not same as requiredKeyLength, but it should be
AbstractAesEncryptionAlgorithm.DEFAULT_RANDOM.nextBytes(bytes)
try {
alg.assertKeyLength(bytes)
fail()
} catch (CryptoException expected) {
}
}
@Test
void testGetSecureRandomWhenRequestHasSpecifiedASecureRandom() {
def alg = new TestAesEncryptionAlgorithm('foo', 'foo', 16, 16)
def secureRandom = new SecureRandom()
def req = EncryptionRequests.builder()
.setAdditionalAuthenticatedData('foo'.getBytes())
.setInitializationVector('iv'.getBytes())
.setKey(alg.generateKey().getEncoded())
.setPlaintext('bar'.getBytes())
.setSecureRandom(secureRandom)
.build();
def returnedSecureRandom = alg.getSecureRandom(req)
assertSame(secureRandom, returnedSecureRandom)
}
static class TestAesEncryptionAlgorithm extends AbstractAesEncryptionAlgorithm {
TestAesEncryptionAlgorithm(String name, String transformationString, int generatedIvLength, int requiredKeyLength) {
super(name, transformationString, generatedIvLength, requiredKeyLength)
}
@Override
protected EncryptionResult doEncrypt(EncryptionRequest req) throws Exception {
return null
}
@Override
protected byte[] doDecrypt(DecryptionRequest req) throws Exception {
return new byte[0]
}
}
}

View File

@ -1,6 +1,5 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.EncryptionAlgorithmName
import io.jsonwebtoken.EncryptionAlgorithms
import org.junit.Test
@ -87,7 +86,7 @@ class Aes128CbcHmacSha256Test {
// now test decryption:
AuthenticatedDecryptionRequest decryptionRequest = DecryptionRequests.builder()
def dreq = DecryptionRequests.builder()
.setAdditionalAuthenticatedData(A)
.setCiphertext(resultCiphertext)
.setInitializationVector(resultIv)
@ -95,7 +94,7 @@ class Aes128CbcHmacSha256Test {
.setAuthenticationTag(resultTag)
.build();
byte[] decryptionResult = alg.decrypt(decryptionRequest)
byte[] decryptionResult = alg.decrypt(dreq)
assertArrayEquals(P, decryptionResult);
}

View File

@ -1,286 +0,0 @@
/*
* Copyright (C) 2016 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.impl.crypto
import groovy.json.internal.Charsets
import org.junit.Test
import javax.crypto.*
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import static org.junit.Assert.*
class DefaultAesEncryptionServiceTest {
private static final String PLAINTEXT =
'''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra.
Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round
biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs
shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon
tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs.
Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin
cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami
alcatra sirloin.
,
,
,
,
, '''
private static final byte[] PLAINTEXT_BYTES = PLAINTEXT.getBytes(Charsets.UTF_8)
private static final String AAD = 'You can get with this, or you can get with that'
private static final byte[] AAD_BYTES = AAD.getBytes(Charsets.UTF_8)
private byte[] generateKey() {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
return kg.generateKey().getEncoded();
}
@Test
void testSimple() {
byte[] key = generateKey();
def service = new DefaultAesEncryptionService(key);
def ciphertext = service.encrypt(PLAINTEXT_BYTES);
def decryptedPlaintextBytes = service.decrypt(ciphertext);
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
@Test
void testDoEncryptFailure() {
String msg = 'foo'
def key = generateKey()
def service = new DefaultAesEncryptionService(key) {
@Override
protected EncryptionResult doEncrypt(EncryptionRequest req) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
throw new IllegalArgumentException(msg)
}
}
try {
service.encrypt(key /*any byte array will do */)
fail("Encryption should have failed")
} catch (CryptoException expected) {
assertEquals('Unable to perform encryption: ' + msg, expected.message)
}
}
@Test
void testDoDecryptFailure() {
String msg = 'foo'
def key = generateKey()
def service = new DefaultAesEncryptionService(key) {
@Override
protected byte[] doDecrypt(DecryptionRequest req) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
throw new IllegalArgumentException(msg)
}
}
try {
service.decrypt(key /*any byte array will do */)
fail("Decryption should have failed")
} catch (CryptoException expected) {
assertEquals('Unable to perform decryption: ' + msg, expected.message)
}
}
@Test
void testEncryptWithSpecifiedKey() {
def service = new DefaultAesEncryptionService(generateKey());
//use a custom key for this request:
def key = generateKey()
EncryptionRequest ereq = EncryptionRequests.builder().setKey(key).setPlaintext(PLAINTEXT_BYTES).build()
EncryptionResult eres = service.encrypt(ereq);
DecryptionRequest dreq = DecryptionRequests.builder().setKey(key)
.setInitializationVector(eres.getInitializationVector())
.setCiphertext(eres.getCiphertext())
.build()
def decryptedPlaintextBytes = service.decrypt(dreq)
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
@Test
void testEncryptWithSpecifiedIv() {
def service = new DefaultAesEncryptionService(generateKey());
byte[] iv = new byte[12]; //AES GCM tends to use nonces of 12 bytes for efficiency
new SecureRandom().nextBytes(iv);
EncryptionRequest ereq = EncryptionRequests.builder()
.setInitializationVector(iv)
.setPlaintext(PLAINTEXT_BYTES)
.build()
EncryptionResult eres = service.encrypt(ereq);
DecryptionRequest dreq = DecryptionRequests.builder()
.setInitializationVector(iv)
.setCiphertext(eres.getCiphertext())
.build()
def decryptedPlaintextBytes = service.decrypt(dreq)
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
@Test
void testDecryptWithEmptyIv() {
def service = new DefaultAesEncryptionService(generateKey());
DecryptionRequest dreq = DecryptionRequests.builder().setCiphertext(generateKey()).build()
try {
service.decrypt(dreq)
fail()
} catch (CryptoException expected) {
assertTrue expected.getMessage().endsWith(DefaultAesEncryptionService.DECRYPT_NO_IV);
}
}
@Test
void testEncryptAdditionalAuthenticatedData() {
def service = new DefaultAesEncryptionService(generateKey());
EncryptionRequest ereq = EncryptionRequests.builder()
.setPlaintext(PLAINTEXT_BYTES)
.setAdditionalAuthenticatedData(AAD_BYTES)
.build()
AuthenticatedEncryptionResult eres = (AuthenticatedEncryptionResult) service.encrypt(ereq);
DecryptionRequest dreq = DecryptionRequests.builder()
.setInitializationVector(eres.getInitializationVector())
.setCiphertext(eres.getCiphertext())
.setAdditionalAuthenticatedData(AAD_BYTES)
.setAuthenticationTag(eres.getAuthenticationTag())
.build()
def decryptedPlaintextBytes = service.decrypt(dreq)
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
@Test
void testEncryptWithEmptyAdditionalAuthenticatedData() {
def service = new DefaultAesEncryptionService(generateKey());
def ereq = new DummyEncryptionRequest(plaintext: PLAINTEXT_BYTES)
def eres = service.encrypt(ereq)
assertTrue eres instanceof DefaultEncryptionResult
assertFalse eres instanceof AuthenticatedEncryptionResult
DecryptionRequest dreq = DecryptionRequests.builder()
.setInitializationVector(eres.getInitializationVector())
.setCiphertext(eres.getCiphertext())
.build()
def decryptedPlaintextBytes = service.decrypt(dreq)
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
@Test
void testDecryptWithSpecifiedKey() {
def key = generateKey()
def service = new DefaultAesEncryptionService(key);
EncryptionRequest ereq = EncryptionRequests.builder()
.setPlaintext(PLAINTEXT_BYTES)
.build()
def eres = service.encrypt(ereq)
assertTrue eres instanceof DefaultEncryptionResult
assertFalse eres instanceof AuthenticatedEncryptionResult
DecryptionRequest dreq = DecryptionRequests.builder()
.setKey(key)
.setInitializationVector(eres.getInitializationVector())
.setCiphertext(eres.getCiphertext())
.build()
def decryptedPlaintextBytes = service.decrypt(dreq)
def decryptedPlaintext = new String(decryptedPlaintextBytes, Charsets.UTF_8);
assertEquals(PLAINTEXT, decryptedPlaintext);
}
private static class DummyEncryptionRequest implements EncryptionRequest, AssociatedDataSource {
byte[] plaintext;
@Override
SecureRandom getSecureRandom() {
return null;
}
@Override
byte[] getAssociatedData() {
return new byte[0]
}
@Override
byte[] getPlaintext() {
return this.plaintext
}
@Override
byte[] getKey() {
return null
}
@Override
byte[] getInitializationVector() {
return null
}
}
}

View File

@ -0,0 +1,12 @@
package io.jsonwebtoken.impl.crypto
import org.junit.Test
import static org.junit.Assert.*
class DisabledDecryptionKeyResolverTest {
@Test
void test() {
assertNull DisabledDecryptionKeyResolver.INSTANCE.resolveDecryptionKey(null)
}
}

View File

@ -32,7 +32,7 @@ class EncryptionAlgorithmsTest {
@Test
void testWithoutAad() {
for (AesEncryptionAlgorithm alg : EncryptionAlgorithms.VALUES) {
for (AbstractAesEncryptionAlgorithm alg : EncryptionAlgorithms.VALUES) {
def skey = alg.generateKey()
def key = skey.getEncoded()
@ -67,7 +67,7 @@ class EncryptionAlgorithmsTest {
@Test
void testWithAad() {
for (AesEncryptionAlgorithm alg : EncryptionAlgorithms.VALUES) {
for (AbstractAesEncryptionAlgorithm alg : EncryptionAlgorithms.VALUES) {
def skey = alg.generateKey()
def key = skey.getEncoded()

View File

@ -1,14 +1,11 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.EncryptionAlgorithmName
import io.jsonwebtoken.EncryptionAlgorithms
import org.junit.Test
import static org.junit.Assert.assertArrayEquals
import static org.junit.Assert.assertTrue
import static org.junit.Assert.*
/**
* Test defined in https://tools.ietf.org/html/rfc7516#appendix-A.1
*/
class GcmAesEncryptionServiceTest {
final byte[] K =
@ -33,9 +30,11 @@ class GcmAesEncryptionServiceTest {
final byte[] T =
[0x5c, 0x50, 0x68, 0x31, 0x85, 0x19, 0xa1, 0xd7, 0xad, 0x65, 0xdb, 0xd3, 0x88, 0x5b, 0xd2, 0x91] as byte[]
/**
* Test that reflects https://tools.ietf.org/html/rfc7516#appendix-A.1
*/
@Test
void test() {
void testEncryptionAndDecryption() {
def alg = EncryptionAlgorithms.A256GCM
@ -78,4 +77,13 @@ class GcmAesEncryptionServiceTest {
println '[' + c.join(', ') + ']' */
}
@Test
void testInstantiationWithInvalidKeyLength() {
try {
new GcmAesEncryptionAlgorithm(EncryptionAlgorithmName.A128GCM.getValue(), 5);
fail()
} catch (IllegalArgumentException expected) {
}
}
}

View File

@ -0,0 +1,129 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.EncryptionAlgorithmName
import io.jsonwebtoken.EncryptionAlgorithms
import io.jsonwebtoken.SignatureAlgorithm
import org.junit.Test
import static org.junit.Assert.*
class HmacAesEncryptionAlgorithmTest {
@Test
void testGetRequiredKeyLengthWithNullSignatureAlgorithm() {
try {
HmacAesEncryptionAlgorithm.getRequiredKeyLength(null)
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testGetRequiredKeyLengthWithInvalidSignatureAlgorithm() {
try {
HmacAesEncryptionAlgorithm.getRequiredKeyLength(SignatureAlgorithm.ES256)
fail()
} catch (IllegalArgumentException expected) {
}
}
@Test
void testGenerateHmacKeyBytesWithExactNumExpectedBytes() {
int hmacKeySize = EncryptionAlgorithms.A128CBC_HS256.getRequiredKeyLength() / 2;
def alg = new TestHmacAesEncryptionAlgorithm() {
@Override
protected byte[] generateHmacKeyBytes() {
byte[] bytes = new byte[hmacKeySize]
AbstractAesEncryptionAlgorithm.DEFAULT_RANDOM.nextBytes(bytes);
return bytes;
}
}
def skey = alg.generateKey()
def key = skey.getEncoded()
def plaintext = "Hello World! Nice to meet you!".getBytes("UTF-8")
def request = EncryptionRequests.builder().setKey(key).setPlaintext(plaintext).build()
def result = alg.encrypt(request);
assert result instanceof AuthenticatedEncryptionResult
def dreq = DecryptionRequests.builder()
.setKey(key)
.setInitializationVector(result.getInitializationVector())
.setAuthenticationTag(result.getAuthenticationTag())
.setCiphertext(result.getCiphertext())
.build()
byte[] decryptedPlaintextBytes = alg.decrypt(dreq)
assertArrayEquals(plaintext, decryptedPlaintextBytes);
}
@Test
void testGenerateHmacKeyBytesWithInsufficientNumExpectedBytes() {
int hmacKeySize = EncryptionAlgorithms.A128CBC_HS256.getRequiredKeyLength() / 2;
def alg = new TestHmacAesEncryptionAlgorithm() {
@Override
protected byte[] generateHmacKeyBytes() {
byte[] bytes = new byte[hmacKeySize - 1]
AbstractAesEncryptionAlgorithm.DEFAULT_RANDOM.nextBytes(bytes);
return bytes;
}
}
try {
alg.generateKey()
fail()
} catch (CryptoException expected) {
}
}
@Test
void testDecryptWithInvalidTag() {
def alg = EncryptionAlgorithms.A128CBC_HS256;
def skey = alg.generateKey()
def key = skey.getEncoded()
def plaintext = "Hello World! Nice to meet you!".getBytes("UTF-8")
def request = EncryptionRequests.builder().setKey(key).setPlaintext(plaintext).build()
def result = alg.encrypt(request);
assert result instanceof AuthenticatedEncryptionResult
def realTag = result.getAuthenticationTag();
//fake it:
def fakeTag = new byte[realTag.length]
AbstractAesEncryptionAlgorithm.DEFAULT_RANDOM.nextBytes(fakeTag)
def dreq = DecryptionRequests.builder()
.setKey(key)
.setInitializationVector(result.getInitializationVector())
.setAuthenticationTag(fakeTag)
.setCiphertext(result.getCiphertext())
.build()
try {
alg.decrypt(dreq)
fail()
} catch (CryptoException expected) {
}
}
static class TestHmacAesEncryptionAlgorithm extends HmacAesEncryptionAlgorithm {
TestHmacAesEncryptionAlgorithm() {
super(EncryptionAlgorithmName.A128CBC_HS256.getValue(), SignatureAlgorithm.HS256);
}
}
}