package org.bouncycastle.jce.provider.test; import java.io.IOException; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.spec.InvalidParameterSpecException; 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.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import junit.framework.TestCase; import org.bouncycastle.asn1.cms.GCMParameters; import org.bouncycastle.jcajce.spec.AEADParameterSpec; import org.bouncycastle.jcajce.spec.RepeatedSecretKeySpec; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.test.SimpleTest; public class AEADTest extends SimpleTest { // EAX test vector from EAXTest private byte[] K2 = Hex.decode("91945D3F4DCBEE0BF45EF52255F095A4"); private byte[] N2 = Hex.decode("BECAF043B0A23D843194BA972C66DEBD"); private byte[] A2 = Hex.decode("FA3BFD4806EB53FA"); private byte[] P2 = Hex.decode("F7FB"); private byte[] C2 = Hex.decode("19DD5C4C9331049D0BDAB0277408F67967E5"); // C2 with only 64bit MAC (default for EAX) private byte[] C2_short = Hex.decode("19DD5C4C9331049D0BDA"); private byte[] KGCM = Hex.decode("00000000000000000000000000000000"); private byte[] NGCM = Hex.decode("000000000000000000000000"); private byte[] CGCM = Hex.decode("58e2fccefa7e3061367f1d57a4e7455a"); public String getName() { return "AEAD"; } public void performTest() throws Exception { boolean aeadAvailable = false; try { this.getClass().getClassLoader().loadClass("javax.crypto.spec.GCMParameterSpec"); aeadAvailable = true; } catch (ClassNotFoundException e) { } testAEADParameterSpec(K2, N2, A2, P2, C2); if (aeadAvailable) { checkCipherWithAD(K2, N2, A2, P2, C2_short); testGCMParameterSpec(K2, N2, A2, P2, C2); testGCMParameterSpecWithRepeatKey(K2, N2, A2, P2, C2); testGCMGeneric(KGCM, NGCM, new byte[0], new byte[0], CGCM); testGCMParameterSpecWithMultipleUpdates(K2, N2, A2, P2, C2); testRepeatedGCMWithSpec(KGCM, NGCM, A2, P2, Hex.decode("f4732d84342623f65b7d63c3c335dd44b87d")); } else { System.err.println("GCM AEADTests disabled due to JDK"); } testTampering(aeadAvailable); } private void testTampering(boolean aeadAvailable) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); final SecretKeySpec key = new SecretKeySpec(new byte[eax.getBlockSize()], eax.getAlgorithm()); final IvParameterSpec iv = new IvParameterSpec(new byte[eax.getBlockSize()]); eax.init(Cipher.ENCRYPT_MODE, key, iv); byte[] ciphertext = eax.doFinal(new byte[100]); ciphertext[0] = (byte)(ciphertext[0] + 1); // Tamper try { eax.init(Cipher.DECRYPT_MODE, key, iv); eax.doFinal(ciphertext); fail("Tampered ciphertext should be invalid"); } catch (BadPaddingException e) { if (aeadAvailable) { if (!e.getClass().getName().equals("javax.crypto.AEADBadTagException")) { fail("Tampered AEAD ciphertext should fail with AEADBadTagException when available."); } } } } private void checkCipherWithAD(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); IvParameterSpec iv = new IvParameterSpec(N); eax.init(Cipher.ENCRYPT_MODE, key, iv); eax.updateAAD(A); byte[] c = eax.doFinal(P); if (!areEqual(C, c)) { fail("JCE encrypt with additional data failed."); } eax.init(Cipher.DECRYPT_MODE, key, iv); eax.updateAAD(A); byte[] p = eax.doFinal(C); if (!areEqual(P, p)) { fail("JCE decrypt with additional data failed."); } } private void testAEADParameterSpec(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws Exception { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); AEADParameterSpec spec = new AEADParameterSpec(N, 128, A); eax.init(Cipher.ENCRYPT_MODE, key, spec); byte[] c = eax.doFinal(P); if (!Arrays.areEqual(C, c)) { TestCase.fail("JCE encrypt with additional data and AEADParameterSpec failed."); } // doFinal test c = eax.doFinal(P); if (!Arrays.areEqual(C, c)) { TestCase.fail("JCE encrypt with additional data and AEADParameterSpec failed after do final"); } eax.init(Cipher.DECRYPT_MODE, key, spec); byte[] p = eax.doFinal(C); if (!Arrays.areEqual(P, p)) { TestCase.fail("JCE decrypt with additional data and AEADParameterSpec failed."); } AlgorithmParameters algParams = eax.getParameters(); byte[] encParams = algParams.getEncoded(); GCMParameters gcmParameters = GCMParameters.getInstance(encParams); if (!Arrays.areEqual(spec.getIV(), gcmParameters.getNonce()) || spec.getMacSizeInBits() != gcmParameters.getIcvLen() * 8) { TestCase.fail("parameters mismatch"); } // note: associated data is not preserved AEADParameterSpec cSpec = algParams.getParameterSpec(AEADParameterSpec.class); if (!Arrays.areEqual(spec.getIV(), cSpec.getNonce()) || spec.getMacSizeInBits() != cSpec.getMacSizeInBits() || cSpec.getAssociatedData() != null) { TestCase.fail("parameters mismatch"); } AlgorithmParameters aeadParams = AlgorithmParameters.getInstance("GCM", "BC"); aeadParams.init(spec); cSpec = aeadParams.getParameterSpec(AEADParameterSpec.class); if (!Arrays.areEqual(spec.getIV(), cSpec.getNonce()) || spec.getMacSizeInBits() != cSpec.getMacSizeInBits() || cSpec.getAssociatedData() != null) { TestCase.fail("parameters mismatch"); } } private void testGCMParameterSpec(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IOException { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); // GCMParameterSpec mapped to AEADParameters and overrides default MAC // size GCMParameterSpec spec = new GCMParameterSpec(128, N); eax.init(Cipher.ENCRYPT_MODE, key, spec); eax.updateAAD(A); byte[] c = eax.doFinal(P); if (!areEqual(C, c)) { fail("JCE encrypt with additional data and GCMParameterSpec failed."); } eax.init(Cipher.DECRYPT_MODE, key, spec); eax.updateAAD(A); byte[] p = eax.doFinal(C); if (!areEqual(P, p)) { fail("JCE decrypt with additional data and GCMParameterSpec failed."); } AlgorithmParameters algParams = eax.getParameters(); byte[] encParams = algParams.getEncoded(); GCMParameters gcmParameters = GCMParameters.getInstance(encParams); if (!Arrays.areEqual(spec.getIV(), gcmParameters.getNonce()) || spec.getTLen() != gcmParameters.getIcvLen() * 8) { fail("parameters mismatch"); } } private void testGCMParameterSpecWithMultipleUpdates(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws Exception { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); SecureRandom random = new SecureRandom(); // GCMParameterSpec mapped to AEADParameters and overrides default MAC // size GCMParameterSpec spec = new GCMParameterSpec(128, N); for (int i = 900; i != 1024; i++) { byte[] message = new byte[i]; random.nextBytes(message); eax.init(Cipher.ENCRYPT_MODE, key, spec); byte[] out = new byte[eax.getOutputSize(i)]; int offSet = 0; int count; for (count = 0; count < i / 21; count++) { offSet += eax.update(message, count * 21, 21, out, offSet); } offSet += eax.doFinal(message, count * 21, i - (count * 21), out, offSet); byte[] dec = new byte[i]; int len = offSet; eax.init(Cipher.DECRYPT_MODE, key, spec); offSet = 0; for (count = 0; count < len / 10; count++) { offSet += eax.update(out, count * 10, 10, dec, offSet); } offSet += eax.doFinal(out, count * 10, len - (count * 10), dec, offSet); if (!Arrays.areEqual(message, dec) || offSet != message.length) { fail("message mismatch"); } } } private void testGCMParameterSpecWithRepeatKey(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IOException { Cipher eax = Cipher.getInstance("AES/EAX/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); GCMParameterSpec spec = new GCMParameterSpec(128, N); eax.init(Cipher.ENCRYPT_MODE, key, spec); eax.updateAAD(A); byte[] c = eax.doFinal(P); if (!areEqual(C, c)) { fail("JCE encrypt with additional data and RepeatedSecretKeySpec failed."); } // Check GCMParameterSpec handling knows about RepeatedSecretKeySpec eax.init(Cipher.DECRYPT_MODE, new RepeatedSecretKeySpec("AES"), spec); eax.updateAAD(A); byte[] p = eax.doFinal(C); if (!areEqual(P, p)) { fail("JCE decrypt with additional data and RepeatedSecretKeySpec failed."); } AlgorithmParameters algParams = eax.getParameters(); byte[] encParams = algParams.getEncoded(); GCMParameters gcmParameters = GCMParameters.getInstance(encParams); if (!Arrays.areEqual(spec.getIV(), gcmParameters.getNonce()) || spec.getTLen() != gcmParameters.getIcvLen() * 8) { fail("parameters mismatch"); } } private void testGCMGeneric(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IOException, InvalidParameterSpecException { Cipher eax = Cipher.getInstance("AES/GCM/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); // GCMParameterSpec mapped to AEADParameters and overrides default MAC // size GCMParameterSpec spec = new GCMParameterSpec(128, N); eax.init(Cipher.ENCRYPT_MODE, key, spec); eax.updateAAD(A); byte[] c = eax.doFinal(P); if (!areEqual(C, c)) { fail("JCE encrypt with additional data and GCMParameterSpec failed."); } eax = Cipher.getInstance("GCM", "BC"); eax.init(Cipher.DECRYPT_MODE, key, spec); eax.updateAAD(A); byte[] p = eax.doFinal(C); if (!areEqual(P, p)) { fail("JCE decrypt with additional data and GCMParameterSpec failed."); } AlgorithmParameters algParams = eax.getParameters(); byte[] encParams = algParams.getEncoded(); GCMParameters gcmParameters = GCMParameters.getInstance(encParams); if (!Arrays.areEqual(spec.getIV(), gcmParameters.getNonce()) || spec.getTLen() != gcmParameters.getIcvLen() * 8) { fail("parameters mismatch"); } GCMParameterSpec gcmSpec = algParams.getParameterSpec(GCMParameterSpec.class); if (!Arrays.areEqual(gcmSpec.getIV(), gcmParameters.getNonce()) || gcmSpec.getTLen() != gcmParameters.getIcvLen() * 8) { fail("spec parameters mismatch"); } if (!Arrays.areEqual(eax.getIV(), gcmParameters.getNonce())) { fail("iv mismatch"); } } private void testRepeatedGCMWithSpec(byte[] K, byte[] N, byte[] A, byte[] P, byte[] C) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IOException { Cipher eax = Cipher.getInstance("AES/GCM/NoPadding", "BC"); SecretKeySpec key = new SecretKeySpec(K, "AES"); GCMParameterSpec spec = new GCMParameterSpec(128, N); eax.init(Cipher.ENCRYPT_MODE, key, spec); eax.updateAAD(A); byte[] c = eax.doFinal(P); if (!areEqual(C, c)) { fail("JCE encrypt with additional data and RepeatedSecretKeySpec failed."); } eax = Cipher.getInstance("GCM", "BC"); eax.init(Cipher.DECRYPT_MODE, key, spec); eax.updateAAD(A); byte[] p = eax.doFinal(C); if (!areEqual(P, p)) { fail("JCE decrypt with additional data and GCMParameterSpec failed."); } try { eax.init(Cipher.ENCRYPT_MODE, new RepeatedSecretKeySpec("AES"), spec); fail("no exception"); } catch (InvalidKeyException e) { isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage())); } try { eax.init(Cipher.ENCRYPT_MODE, new RepeatedSecretKeySpec("AES"), new IvParameterSpec(spec.getIV())); fail("no exception"); } catch (InvalidKeyException e) { isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage())); } try { eax.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(K, "AES"), new IvParameterSpec(spec.getIV())); fail("no exception"); } catch (InvalidKeyException e) { isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage())); } } public static void main(String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); runTest(new AEADTest()); } }