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);
}
}