AESGCMContentEncryptionHandler.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
*/
package org.forgerock.json.jose.jwe.handlers.encryption;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
import org.forgerock.json.jose.exceptions.JweDecryptionException;
import org.forgerock.json.jose.exceptions.JweEncryptionException;
import org.forgerock.json.jose.jwe.EncryptionMethod;
import org.forgerock.json.jose.jwe.JweEncryption;
/**
* Encrypts content using Galois Counter Mode (GCM), an efficient authenticated encryption with associated data
* (AEAD) mode. Compared to {@link AESCBCHMACSHA2ContentEncryptionHandler} this is a more efficient combined AEAD
* mode that does not require padding. On the flip side, if you ever repeat a key/IV pair then the security of the
* cipher is totally compromised.
*/
final class AESGCMContentEncryptionHandler extends ContentEncryptionHandler {
private static final Logger LOGGER = Logger.getLogger(AESGCMContentEncryptionHandler.class.getName());
private static final int TAG_LENGTH = 128;
private static final int IV_LENGTH = 96 / 8;
private final EncryptionMethod encryptionMethod;
AESGCMContentEncryptionHandler(final EncryptionMethod encryptionMethod) {
this.encryptionMethod = encryptionMethod;
}
@Override
JweEncryption encrypt(final Key key, final byte[] iv, final byte[] plainText, final byte[] additionalData) {
try {
final Cipher cipher = Cipher.getInstance(encryptionMethod.getTransformation());
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_LENGTH, iv));
cipher.updateAAD(additionalData);
final byte[] cipherText = cipher.doFinal(plainText);
final int tagStart = cipherText.length - TAG_LENGTH / 8;
return new JweEncryption(Arrays.copyOfRange(cipherText, 0, tagStart),
Arrays.copyOfRange(cipherText, tagStart, cipherText.length));
} catch (GeneralSecurityException ex) {
throw new JweEncryptionException(ex);
}
}
@Override
byte[] decrypt(final Key key, final byte[] iv, final JweEncryption cipherText, final byte[] additionalData) {
try {
final Cipher cipher = Cipher.getInstance(encryptionMethod.getTransformation());
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(TAG_LENGTH, iv));
cipher.updateAAD(additionalData);
cipher.update(cipherText.getCiphertext());
return cipher.doFinal(cipherText.getAuthenticationTag());
} catch (GeneralSecurityException ex) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Decryption failed: " + ex, ex);
}
throw new JweDecryptionException();
}
}
@Override
Key generateEncryptionKey() {
try {
final KeyGenerator encKeyGenerator = KeyGenerator.getInstance(encryptionMethod.getEncryptionAlgorithm());
encKeyGenerator.init(encryptionMethod.getKeySize());
return encKeyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
throw new JweEncryptionException("Unsupported Encryption Algorithm, "
+ encryptionMethod.getEncryptionAlgorithm(), e);
}
}
@Override
int getIVByteLength() {
return IV_LENGTH;
}
}