Google
 
Web unafbapune.blogspot.com

Wednesday, June 20, 2012

 

AES/GCM with Associated Data

Via the excellent Coursera online crypto course offered by Stanford University, I learned from Professor Dan Boneh that we should never use symmetric cipher alone. Instead, we should always use authenticated encryption with optional associated data to simultaneously provide confidentiality, integrity and authenticity assurances. One such recommended standard is AES/GCM, which is mentioned to be supported both by openssl and Java.

In Java, unfortunately, although the SPI for AES/GCM has arrived in Java 7 as described in the javadoc of the Cipher class, there is actually no such implementation in the JDK. This prompted me to look into the latest Bouncy Castle library. In the latest BC version 1.4.7, even though there exists a GCMBlockCipher, it is apparently not properly exposed via JCE in the sense that no associated data can be updated via the JCE API without causing an exception if "BC" is used as the security provider.

On one hand, the GCMBlockCipher per se does seem to be fully functional. On the other hand, I don't seem to be able to find any example of doing authenticated encryption with associated data with it. So I went ahead and try to come up with some sample code in the form of a unit test below. Can anyone see if I am doing something silly ?

import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Arrays; import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; import org.bouncycastle.crypto.params.KeyParameter; import org.junit.Test; /** * @author rot13(Unafba Pune) */ public class GCMBlockCipherTest { private static final SecureRandom rand = new SecureRandom(); private static final int MAC_SIZE = 128; private static final byte[] keybytes = new byte[16*2]; private static final byte[] nouncebytes = new byte[16]; private static final KeyParameter key = new KeyParameter(keybytes); private static final byte[] associatedText = "Some Associated Text".getBytes(StandardCharsets.UTF_8); private static final byte[] plaintext = "Some plaintext".getBytes(StandardCharsets.UTF_8); static { rand.nextBytes(keybytes); // a random key rand.nextBytes(nouncebytes); // a random nounce } @Test public void testGCMEncryptDecryptWithAD() throws InvalidCipherTextException { System.out.println("plaintext: " + hex(plaintext)); AEADParameters params = new AEADParameters(key, MAC_SIZE, nouncebytes, associatedText); byte[] ciphertext = encrypt(plaintext, params); System.out.println("ciphertext: " + hex(ciphertext)); byte[] decrypted = decrypt(ciphertext, params); System.out.println("decrypted: " + hex(decrypted)); System.out.println("nounce: " + hex(nouncebytes)); assertTrue(Arrays.equals(plaintext, decrypted)); } @Test(expected=InvalidCipherTextException.class) public void testNounceMismatch() throws InvalidCipherTextException { AEADParameters params = new AEADParameters(key, MAC_SIZE, nouncebytes, associatedText); byte[] ciphertext = encrypt(plaintext, params); AEADParameters paramsWithBadNounce = new AEADParameters(key, MAC_SIZE, new byte[16], associatedText); try { decrypt(ciphertext, paramsWithBadNounce); fail(); } catch(InvalidCipherTextException ex) { assertTrue(ex.getMessage().contains("mac check in GCM failed")); throw ex; } } @Test(expected=InvalidCipherTextException.class) public void testADMismatch() throws InvalidCipherTextException { AEADParameters params = new AEADParameters(key, MAC_SIZE, nouncebytes, associatedText); byte[] ciphertext = encrypt(plaintext, params); AEADParameters paramsWithBadAD = new AEADParameters(key, MAC_SIZE, nouncebytes, new byte[16]); try { decrypt(ciphertext, paramsWithBadAD); fail(); } catch(InvalidCipherTextException ex) { assertTrue(ex.getMessage().contains("mac check in GCM failed")); throw ex; } } /** Returns the ciphertext encrypted from the given plaintext and AEAD parameters. */ private static byte[] encrypt(byte[] plaintext, AEADParameters params) throws InvalidCipherTextException { GCMBlockCipher gcm = new GCMBlockCipher(new AESEngine()); gcm.init(true, params); int outsize = gcm.getOutputSize(plaintext.length); byte[] out = new byte[outsize]; int offOut = gcm.processBytes(plaintext, 0, plaintext.length, out, 0); gcm.doFinal(out, offOut); return out; } /** Returns the plaintext decrypted from the given ciphertext and AEAD parameters. */ private static byte[] decrypt(byte[] ciphertext, AEADParameters params) throws InvalidCipherTextException { GCMBlockCipher gcm = new GCMBlockCipher(new AESEngine()); gcm.init(false, params); int outsize = gcm.getOutputSize(ciphertext.length); byte[] out = new byte[outsize]; int offOut = gcm.processBytes(ciphertext, 0, ciphertext.length, out, 0); gcm.doFinal(out, offOut); return out; } private static String hex(byte[] values) { return new HexBinaryAdapter().marshal(values); } }

Comments:
GREAT test - i've been taking the same CRYPTO course and trying to comer up java examples. GOOD job keep it up!!
 
Taken the same class, thanks for the test class!
 
This code has a very nasty bug.
rand.nextBytes(keybytes) is called after new KeyParameter(keybytes), which means that the key is never initialized.
To fix it just move the declaration of key below the static {} block.

I discovered this because, with the same nouncebytes and associatedText, the ciphertext was always the same.
So I coded a new test unit to be sure that another random key wasn't able to decrypt it. This proved that the printed key was in fact not the used key.

@Test(expected=InvalidCipherTextException.class)
public void testKeyMismatch() throws InvalidCipherTextException {
AEADParameters params = new AEADParameters(key, MAC_SIZE, nouncebytes, associatedText);
byte[] ciphertext = encrypt(plaintext, params);
byte[] badKey = new byte[16*2];
rand.nextBytes(badKey);
System.out.println("new badKey: " + hex(badKey));
AEADParameters paramsWithBadKey = new AEADParameters(new KeyParameter(badKey), MAC_SIZE, nouncebytes, associatedText);
try {
System.out.println("calculating decryptedWithBadKey...");
byte[] decryptedWithBadKey = decrypt(ciphertext, paramsWithBadKey);
System.out.println("decryptedWithBadKey: " + hex(decryptedWithBadKey));
fail();
} catch(InvalidCipherTextException ex) {
System.out.println("failure (expected)");
assertTrue(ex.getMessage().contains("mac check in GCM failed"));
throw ex;
}
}
 
Nice catch! Thanks, tillo.
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?