Skip to content

Instantly share code, notes, and snippets.

@overheadhunter
Created November 8, 2015 00:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save overheadhunter/70207b7afb15aeefbc4c to your computer and use it in GitHub Desktop.
Save overheadhunter/70207b7afb15aeefbc4c to your computer and use it in GitHub Desktop.
Benchmarking GCM vs CTR+HMAC in Java
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Test;
public class AesBenchmark {
private static final ByteBuffer TEST_PLAINTEXT = ByteBuffer.wrap(new byte[32 * 1024]).asReadOnlyBuffer();
private static final int TAG_LENGTH = 16; // 128 bit
private static final byte[] FILE_KEY_BYTES = new byte[16]; // 128 bit key length
static {
try {
SecureRandom.getInstanceStrong().nextBytes(FILE_KEY_BYTES);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No secure PRNG available.");
}
}
@Test
public void testGcmEncryption() throws NoSuchAlgorithmException, NoSuchPaddingException {
final SecretKey fileKey = new SecretKeySpec(FILE_KEY_BYTES, "AES");
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
for (int i = 0; i < 300; i++) {
final long t0 = System.nanoTime();
// ENCRYPT:
final ByteBuffer in = TEST_PLAINTEXT.duplicate();
final ByteBuffer cipherBuf = ByteBuffer.allocate(in.remaining() + TAG_LENGTH);
try {
// nonce MUST be unique per key, but CAN be predictable. Thus we use a per-file counter:
final byte[] nonce = new byte[12]; // strongly recommended to use 96 bit!
// our demo nonce: chunk number 0, version 0.
ByteBuffer.wrap(nonce).putLong(0l).putInt(i);
cipher.init(Cipher.ENCRYPT_MODE, fileKey, new GCMParameterSpec(TAG_LENGTH * Byte.SIZE, nonce));
cipher.updateAAD(nonce);
cipher.update(in, cipherBuf);
cipher.doFinal(in, cipherBuf);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("Hard coded cipher setup known to work.");
} catch (ShortBufferException e) {
throw new IllegalStateException("output buffer size hard coded.");
}
// DECRYPT:
cipherBuf.flip();
final ByteBuffer plainBuf = ByteBuffer.allocate(cipherBuf.remaining() - TAG_LENGTH);
try {
// nonce MUST be unique per key, but CAN be predictable. Thus we use a per-file counter:
final byte[] nonce = new byte[12]; // strongly recommended to use 96 bit!
// our demo nonce: chunk number 0, version 0.
ByteBuffer.wrap(nonce).putLong(0l).putInt(i);
cipher.init(Cipher.DECRYPT_MODE, fileKey, new GCMParameterSpec(TAG_LENGTH * Byte.SIZE, nonce));
cipher.updateAAD(nonce);
cipher.update(cipherBuf, plainBuf);
cipher.doFinal(cipherBuf, plainBuf);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("Hard coded cipher setup known to work.");
} catch (ShortBufferException e) {
throw new IllegalStateException("output buffer size hard coded.");
}
final long t1 = System.nanoTime();
if (i > 280) {
System.out.println("GCM ENC+DEC: " + (t1 - t0) / 1000.0 + "µs");
}
}
}
@Test
public void testCtrThenMac() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
final SecretKey fileKey = new SecretKeySpec(FILE_KEY_BYTES, "AES");
final SecretKey macKey = new SecretKeySpec(FILE_KEY_BYTES, "AES");
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
for (int i = 0; i < 300; i++) {
final long t0 = System.nanoTime();
// ENCRYPT:
final ByteBuffer in = TEST_PLAINTEXT.duplicate();
final ByteBuffer cipherBuf = ByteBuffer.allocate(in.remaining() + mac.getMacLength());
try {
// nonce MUST be unique per key, but CAN be predictable. Thus we use a per-file counter:
final byte[] nonce = new byte[16]; // strongly recommended to use 96 bit!
// our demo nonce: chunk number 0, version 0.
ByteBuffer.wrap(nonce).putLong(0l).putLong(i);
cipher.init(Cipher.ENCRYPT_MODE, fileKey, new IvParameterSpec(nonce));
cipher.update(in, cipherBuf);
// calc mac over ciphertext:
ByteBuffer encryptedBuf = cipherBuf.asReadOnlyBuffer();
encryptedBuf.flip();
mac.update(encryptedBuf);
final byte[] calculatedMac = mac.doFinal();
cipherBuf.put(calculatedMac);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException("Hard coded cipher setup known to work.");
} catch (ShortBufferException e) {
throw new IllegalStateException("output buffer size hard coded.");
}
// DECRYPT:
cipherBuf.flip();
final ByteBuffer plainBuf = ByteBuffer.allocate(cipherBuf.remaining() - mac.getMacLength());
try {
// nonce MUST be unique per key, but CAN be predictable. Thus we use a per-file counter:
final byte[] nonce = new byte[16]; // strongly recommended to use 96 bit!
// our demo nonce: chunk number 0, version 0.
ByteBuffer.wrap(nonce).putLong(0l).putLong(i);
ByteBuffer encryptedBuf = cipherBuf.asReadOnlyBuffer();
encryptedBuf.limit(encryptedBuf.limit() - mac.getMacLength());
mac.update(encryptedBuf);
final byte[] calculatedMac = mac.doFinal();
ByteBuffer macBuf = cipherBuf.asReadOnlyBuffer();
macBuf.position(encryptedBuf.limit() - mac.getMacLength());
final byte[] storedMac = new byte[mac.getMacLength()];
macBuf.get(storedMac);
if (MessageDigest.isEqual(storedMac, calculatedMac)) {
throw new IllegalStateException("MAC doesnt't match.");
}
encryptedBuf.rewind();
cipher.init(Cipher.DECRYPT_MODE, fileKey, new IvParameterSpec(nonce));
cipher.update(encryptedBuf, plainBuf);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException("Hard coded cipher setup known to work.");
} catch (ShortBufferException e) {
throw new IllegalStateException("output buffer size hard coded.");
}
final long t1 = System.nanoTime();
if (i > 280) {
System.out.println("CTR+MAC ENC+DEC: " + (t1 - t0) / 1000.0 + "µs");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment