I have run a simple test to measure the AES-GCM performance in Java 9, by encrypting byte buffers in a loop. The results were somewhat confusing. The native (hardware) acceleration seems to work - but not always. More specifically,
My test code looks like this:
int plen = 1024*1024;
byte[] input = new byte[plen];
for (int i=0; i < input.length; i++) { input[i] = (byte)i;}
byte[] nonce = new byte[12];
...
// Uses SunJCE provider
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] key_code = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
SecretKey key = new SecretKeySpec(key_code, "AES");
SecureRandom random = new SecureRandom();
long total = 0;
while (true) {
random.nextBytes(nonce);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] cipherText = cipher.doFinal(input);
total += plen;
// print delta_total/delta_time, once in a while
}
Feb 2019 update: HotSpot had been modified to address this issue. The fix is applied in Java 13, and also backported to Java 11 and 12.
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8201633, https://hg.openjdk.java.net/jdk/jdk/rev/f35a8aaabcb9
July 16, 2019 update: The newly released Java version (Java 11.0.4) fixes this problem.
As GCM uses AES for encryption, the IV or the counter is 16 bytes. Therefore, we use the first 12 bytes as the IV and the last 4 bytes nonce as a counter. Here also, we need a unique IV, else one can decipher the plaintext.
GCM. Nonce. A value used once during a cryptographic operation and then discarded.
Yesno. The plaintext is not padded because it uses counter mode encryption (CTR), and CTR mode doesn't require any padding. However, GMAC - which produces the authentication tag within the algorithm - only works on blocks of 128 bits.
The AES-GCM specification recommends that it should be 96, 104, 112, 120 or 128, although 32 or 64 bits may be acceptable in some applications: Appendix C of the specification provides additional guidance here. tagLength is optional and defaults to 128 if it is not specified.
Thanks @Holger for pointing in the right direction. Prepending cipher.doFinal
with multiple cipher.update
calls will trigger the hardware acceleration almost immediately.
Based on this reference, GCM Analysis , I'm using 4KB chunks in each update. Now both 1MB and 100MB buffers are encrypted at 1100 MB/sec speed (after a few dozen milliseconds) .
The solution is to replace
byte[] cipherText = cipher.doFinal(input);
with
int clen = plen + GCM_TAG_LENGTH;
byte[] cipherText = new byte[clen];
int chunkLen = 4 * 1024;
int left = plen;
int inputOffset = 0;
int outputOffset = 0;
while (left > chunkLen) {
int written = cipher.update(input, inputOffset, chunkLen, cipherText, outputOffset);
inputOffset += chunkLen;
outputOffset += written;
left -= chunkLen;
}
cipher.doFinal(input, inputOffset, left, cipherText, outputOffset);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With