I'm implementing a PAKE-protocol as a university project and one step of the protocol involves sending encrypted data from a key created via a hash function (I'm using SHA-256). I want to use AES/GCM but do not have an IV so I'm using HKDF to expand the key into an IV and key with the following implementation:
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
import org.bouncycastle.crypto.params.HKDFParameters;
import org.bouncycastle.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class AesGcmSymmetricCipher {
public static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
public static final int GCM_TAG_BITS = 128;
public static final int IV_BYTES = 12;
public static final int KEY_BYTES = 32; // AES-256
private final Cipher cipher;
public AesGcmSymmetricCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
this.cipher = Cipher.getInstance(CIPHER_ALGORITHM);
}
public byte[] encrypt(byte[] key, byte[] clearData) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
var spec = expandKeys(key);
cipher.init(Cipher.ENCRYPT_MODE, spec.getKeySpec(), spec.getParameterSpec());
return cipher.doFinal(clearData);
}
public byte[] decrypt(byte[] key, byte[] cipherData) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
var spec = expandKeys(key);
cipher.init(Cipher.DECRYPT_MODE, spec.getKeySpec(), spec.getParameterSpec());
return cipher.doFinal(cipherData);
}
public String getAlgorithm() {
return "AES-256/GCM/NoPadding using HKDF";
}
private KeyedMaterial expandKeys(byte[] initialKey) {
var iv = new byte[IV_BYTES];
var key = new byte[KEY_BYTES];
var digest = new SHA256Digest();
var kdf = new HKDFBytesGenerator(digest);
var parameters = new HKDFParameters(initialKey, null, null);
kdf.init(parameters);
kdf.generateBytes(iv, 0, IV_BYTES);
kdf.generateBytes(key, 0, KEY_BYTES);
var gcmParameters = new GCMParameterSpec(GCM_TAG_BITS, iv);
var keySpec = new SecretKeySpec(key, CIPHER_ALGORITHM);
Arrays.clear(iv);
Arrays.clear(key);
return new KeyedMaterial(gcmParameters, keySpec);
}
private static class KeyedMaterial {
private final GCMParameterSpec parameterSpec;
private final SecretKeySpec keySpec;
public KeyedMaterial(GCMParameterSpec parameterSpec, SecretKeySpec keySpec) {
this.parameterSpec = parameterSpec;
this.keySpec = keySpec;
}
public GCMParameterSpec getParameterSpec() {
return parameterSpec;
}
public SecretKeySpec getKeySpec() {
return keySpec;
}
}
}
I'm interested in feedback especially from a security perspective, if relevant the initial key passed to encrypt
/decrypt
is ephemeral and will only be used once for encryption and once for decryption.