Created
November 8, 2015 00:03
-
-
Save overheadhunter/70207b7afb15aeefbc4c to your computer and use it in GitHub Desktop.
Benchmarking GCM vs CTR+HMAC in Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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