Reset and tamper testing for AEAD ciphers by timw · Pull Request #12 · bcgit/bc-java
@@ -0,0 +1,184 @@
package org.bouncycastle.crypto.test;
import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.test.SimpleTestResult; import org.bouncycastle.util.test.Test; import org.bouncycastle.util.test.TestFailedException;
public class AEADTestUtil {
public static void testTampering(Test test, AEADBlockCipher cipher, CipherParameters params) throws InvalidCipherTextException { byte[] plaintext = new byte[1000]; for (int i = 0; i < plaintext.length; i++) { plaintext[i] = (byte)i; } cipher.init(true, params);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)]; int len = cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0); cipher.doFinal(ciphertext, len);
int macLength = cipher.getMac().length;
// Test tampering with a single byte cipher.init(false, params); byte[] tampered = new byte[ciphertext.length]; byte[] output = new byte[plaintext.length]; System.arraycopy(ciphertext, 0, tampered, 0, tampered.length); tampered[0] += 1;
cipher.processBytes(tampered, 0, tampered.length, output, 0); try { cipher.doFinal(output, 0); throw new TestFailedException( new SimpleTestResult(false, test + " : tampering of ciphertext not detected.")); } catch (InvalidCipherTextException e) { // Expected }
// Test truncation of ciphertext to < tag length cipher.init(false, params); byte[] truncated = new byte[macLength - 1]; System.arraycopy(ciphertext, 0, truncated, 0, truncated.length);
cipher.processBytes(truncated, 0, truncated.length, output, 0); try { cipher.doFinal(output, 0); fail(test, "tampering of ciphertext not detected."); } catch (InvalidCipherTextException e) { // Expected } }
private static void fail(Test test, String message) { throw new TestFailedException(SimpleTestResult.failed(test, message)); }
private static void fail(Test test, String message, String expected, String result) { throw new TestFailedException(SimpleTestResult.failed(test, message, expected, result)); }
public static void testReset(Test test, AEADBlockCipher cipher1, AEADBlockCipher cipher2, CipherParameters params) throws InvalidCipherTextException { cipher1.init(true, params);
byte[] plaintext = new byte[1000]; byte[] ciphertext = new byte[cipher1.getOutputSize(plaintext.length)];
// Establish baseline answer crypt(cipher1, plaintext, ciphertext);
// Test encryption resets checkReset(test, cipher1, params, true, plaintext, ciphertext);
// Test decryption resets with fresh instance cipher2.init(false, params); checkReset(test, cipher2, params, false, ciphertext, plaintext); }
private static void checkReset(Test test, AEADBlockCipher cipher, CipherParameters params, boolean encrypt, byte[] pretext, byte[] posttext) throws InvalidCipherTextException { // Do initial run byte[] output = new byte[posttext.length]; crypt(cipher, pretext, output);
// Check encrypt resets cipher crypt(cipher, pretext, output); if (!Arrays.areEqual(output, posttext)) { fail(test, (encrypt ? "Encrypt" : "Decrypt") + " did not reset cipher."); }
// Check init resets data cipher.processBytes(pretext, 0, 100, output, 0); cipher.init(encrypt, params);
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Init did not reset data.", new String(Hex.encode(posttext)), new String(Hex.encode(output))); }
// Check init resets AD cipher.processAADBytes(pretext, 0, 100); cipher.init(encrypt, params);
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset additional data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Init did not reset additional data."); }
// Check reset resets data cipher.processBytes(pretext, 0, 100, output, 0); cipher.reset();
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Reset did not reset data."); }
// Check reset resets AD cipher.processAADBytes(pretext, 0, 100); cipher.reset();
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Reset did not reset additional data."); } }
private static void crypt(AEADBlockCipher cipher, byte[] plaintext, byte[] output) throws InvalidCipherTextException { int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0); cipher.doFinal(output, len); }
}
import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.test.SimpleTestResult; import org.bouncycastle.util.test.Test; import org.bouncycastle.util.test.TestFailedException;
public class AEADTestUtil {
public static void testTampering(Test test, AEADBlockCipher cipher, CipherParameters params) throws InvalidCipherTextException { byte[] plaintext = new byte[1000]; for (int i = 0; i < plaintext.length; i++) { plaintext[i] = (byte)i; } cipher.init(true, params);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)]; int len = cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0); cipher.doFinal(ciphertext, len);
int macLength = cipher.getMac().length;
// Test tampering with a single byte cipher.init(false, params); byte[] tampered = new byte[ciphertext.length]; byte[] output = new byte[plaintext.length]; System.arraycopy(ciphertext, 0, tampered, 0, tampered.length); tampered[0] += 1;
cipher.processBytes(tampered, 0, tampered.length, output, 0); try { cipher.doFinal(output, 0); throw new TestFailedException( new SimpleTestResult(false, test + " : tampering of ciphertext not detected.")); } catch (InvalidCipherTextException e) { // Expected }
// Test truncation of ciphertext to < tag length cipher.init(false, params); byte[] truncated = new byte[macLength - 1]; System.arraycopy(ciphertext, 0, truncated, 0, truncated.length);
cipher.processBytes(truncated, 0, truncated.length, output, 0); try { cipher.doFinal(output, 0); fail(test, "tampering of ciphertext not detected."); } catch (InvalidCipherTextException e) { // Expected } }
private static void fail(Test test, String message) { throw new TestFailedException(SimpleTestResult.failed(test, message)); }
private static void fail(Test test, String message, String expected, String result) { throw new TestFailedException(SimpleTestResult.failed(test, message, expected, result)); }
public static void testReset(Test test, AEADBlockCipher cipher1, AEADBlockCipher cipher2, CipherParameters params) throws InvalidCipherTextException { cipher1.init(true, params);
byte[] plaintext = new byte[1000]; byte[] ciphertext = new byte[cipher1.getOutputSize(plaintext.length)];
// Establish baseline answer crypt(cipher1, plaintext, ciphertext);
// Test encryption resets checkReset(test, cipher1, params, true, plaintext, ciphertext);
// Test decryption resets with fresh instance cipher2.init(false, params); checkReset(test, cipher2, params, false, ciphertext, plaintext); }
private static void checkReset(Test test, AEADBlockCipher cipher, CipherParameters params, boolean encrypt, byte[] pretext, byte[] posttext) throws InvalidCipherTextException { // Do initial run byte[] output = new byte[posttext.length]; crypt(cipher, pretext, output);
// Check encrypt resets cipher crypt(cipher, pretext, output); if (!Arrays.areEqual(output, posttext)) { fail(test, (encrypt ? "Encrypt" : "Decrypt") + " did not reset cipher."); }
// Check init resets data cipher.processBytes(pretext, 0, 100, output, 0); cipher.init(encrypt, params);
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Init did not reset data.", new String(Hex.encode(posttext)), new String(Hex.encode(output))); }
// Check init resets AD cipher.processAADBytes(pretext, 0, 100); cipher.init(encrypt, params);
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset additional data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Init did not reset additional data."); }
// Check reset resets data cipher.processBytes(pretext, 0, 100, output, 0); cipher.reset();
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Reset did not reset data."); }
// Check reset resets AD cipher.processAADBytes(pretext, 0, 100); cipher.reset();
try { crypt(cipher, pretext, output); } catch (DataLengthException e) { fail(test, "Init did not reset data."); } if (!Arrays.areEqual(output, posttext)) { fail(test, "Reset did not reset additional data."); } }
private static void crypt(AEADBlockCipher cipher, byte[] plaintext, byte[] output) throws InvalidCipherTextException { int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0); cipher.doFinal(output, len); }
}