com.amazonaws.encryptionsdk.internal.AesGcmJceKeyCipher Maven / Gradle / Ivy
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
* in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.encryptionsdk.internal;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.util.Map;
/**
* A JceKeyCipher based on the Advanced Encryption Standard in Galois/Counter Mode.
*/
class AesGcmJceKeyCipher extends JceKeyCipher {
private static final int NONCE_LENGTH = 12;
private static final int TAG_LENGTH = 128;
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final int SPEC_LENGTH = Integer.BYTES + Integer.BYTES + NONCE_LENGTH;
AesGcmJceKeyCipher(SecretKey key) {
super(key, key);
}
private static byte[] specToBytes(final GCMParameterSpec spec) {
final byte[] nonce = spec.getIV();
final byte[] result = new byte[SPEC_LENGTH];
final ByteBuffer buffer = ByteBuffer.wrap(result);
buffer.putInt(spec.getTLen());
buffer.putInt(nonce.length);
buffer.put(nonce);
return result;
}
private static GCMParameterSpec bytesToSpec(final byte[] data, final int offset) throws InvalidKeyException {
if (data.length - offset != SPEC_LENGTH) {
throw new InvalidKeyException("Algorithm specification was an invalid data size");
}
final ByteBuffer buffer = ByteBuffer.wrap(data, offset, SPEC_LENGTH);
final int tagLen = buffer.getInt();
final int nonceLen = buffer.getInt();
if (tagLen != TAG_LENGTH) {
throw new InvalidKeyException(String.format("Authentication tag length must be %s", TAG_LENGTH));
}
if (nonceLen != NONCE_LENGTH) {
throw new InvalidKeyException(String.format("Initialization vector (IV) length must be %s", NONCE_LENGTH));
}
final byte[] nonce = new byte[nonceLen];
buffer.get(nonce);
return new GCMParameterSpec(tagLen, nonce);
}
@Override
WrappingData buildWrappingCipher(final Key key, final Map encryptionContext)
throws GeneralSecurityException {
final byte[] nonce = new byte[NONCE_LENGTH];
Utils.getSecureRandom().nextBytes(nonce);
final GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, nonce);
final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext);
cipher.updateAAD(aad);
return new WrappingData(cipher, specToBytes(spec));
}
@Override
Cipher buildUnwrappingCipher(final Key key, final byte[] extraInfo, final int offset,
final Map encryptionContext) throws GeneralSecurityException {
final GCMParameterSpec spec = bytesToSpec(extraInfo, offset);
final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext);
cipher.updateAAD(aad);
return cipher;
}
}