package freenet.crypt;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import junit.framework.TestCase;
import freenet.crypt.ciphers.Rijndael;
import freenet.support.HexUtil;
import freenet.support.math.MersenneTwister;
public class CTRBlockCipherTest extends TestCase {
/** Whether to assume JCA is available, and non-crippled. */
public static final boolean TEST_JCA = Rijndael.AesCtrProvider != null;
static {
if(!TEST_JCA)
System.out.println("JCA is crippled, not doing tests requiring JCA");
}
private MersenneTwister mt = new MersenneTwister(1634);
// FIXME test decryptability.
byte[] NIST_128_ENCRYPT_KEY = HexUtil
.hexToBytes("2b7e151628aed2a6abf7158809cf4f3c");
byte[] NIST_128_ENCRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_128_ENCRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_128_ENCRYPT_CIPHERTEXT = HexUtil
.hexToBytes("874d6191b620e3261bef6864990db6ce"
+ "9806f66b7970fdff8617187bb9fffdff"
+ "5ae4df3edbd5d35e5b4f09020db03eab"
+ "1e031dda2fbe03d1792170a0f3009cee");
byte[] NIST_128_DECRYPT_KEY = HexUtil
.hexToBytes("2b7e151628aed2a6abf7158809cf4f3c");
byte[] NIST_128_DECRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_128_DECRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_128_DECRYPT_CIPHERTEXT = HexUtil
.hexToBytes("874d6191b620e3261bef6864990db6ce"
+ "9806f66b7970fdff8617187bb9fffdff"
+ "5ae4df3edbd5d35e5b4f09020db03eab"
+ "1e031dda2fbe03d1792170a0f3009cee");
byte[] NIST_192_ENCRYPT_KEY = HexUtil
.hexToBytes("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b");
byte[] NIST_192_ENCRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_192_ENCRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_192_ENCRYPT_CIPHERTEXT = HexUtil
.hexToBytes("1abc932417521ca24f2b0459fe7e6e0b"
+ "090339ec0aa6faefd5ccc2c6f4ce8e94"
+ "1e36b26bd1ebc670d1bd1d665620abf7"
+ "4f78a7f6d29809585a97daec58c6b050");
byte[] NIST_192_DECRYPT_KEY = HexUtil
.hexToBytes("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b");
byte[] NIST_192_DECRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_192_DECRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_192_DECRYPT_CIPHERTEXT = HexUtil
.hexToBytes("1abc932417521ca24f2b0459fe7e6e0b"
+ "090339ec0aa6faefd5ccc2c6f4ce8e94"
+ "1e36b26bd1ebc670d1bd1d665620abf7"
+ "4f78a7f6d29809585a97daec58c6b050");
byte[] NIST_256_ENCRYPT_KEY = HexUtil
.hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
byte[] NIST_256_ENCRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_256_ENCRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_256_ENCRYPT_CIPHERTEXT = HexUtil
.hexToBytes("601ec313775789a5b7a7f504bbf3d228"
+ "f443e3ca4d62b59aca84e990cacaf5c5"
+ "2b0930daa23de94ce87017ba2d84988d"
+ "dfc9c58db67aada613c2dd08457941a6");
byte[] NIST_256_DECRYPT_KEY = HexUtil
.hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
byte[] NIST_256_DECRYPT_IV = HexUtil
.hexToBytes("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
byte[] NIST_256_DECRYPT_PLAINTEXT = HexUtil
.hexToBytes("6bc1bee22e409f96e93d7e117393172a"
+ "ae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52ef"
+ "f69f2445df4f9b17ad2b417be66c3710");
byte[] NIST_256_DECRYPT_CIPHERTEXT = HexUtil
.hexToBytes("601ec313775789a5b7a7f504bbf3d228"
+ "f443e3ca4d62b59aca84e990cacaf5c5"
+ "2b0930daa23de94ce87017ba2d84988d"
+ "dfc9c58db67aada613c2dd08457941a6");
public void testNIST() throws UnsupportedCipherException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
// CTR mode test vectors.
// AES128
checkNIST(128, NIST_128_ENCRYPT_KEY, NIST_128_ENCRYPT_IV,
NIST_128_ENCRYPT_PLAINTEXT, NIST_128_ENCRYPT_CIPHERTEXT);
checkNIST(128, NIST_128_DECRYPT_KEY, NIST_128_DECRYPT_IV,
NIST_128_DECRYPT_PLAINTEXT, NIST_128_DECRYPT_CIPHERTEXT);
// AES192
checkNIST(192, NIST_192_ENCRYPT_KEY, NIST_192_ENCRYPT_IV,
NIST_192_ENCRYPT_PLAINTEXT, NIST_192_ENCRYPT_CIPHERTEXT);
checkNIST(192, NIST_192_DECRYPT_KEY, NIST_192_DECRYPT_IV,
NIST_192_DECRYPT_PLAINTEXT, NIST_192_DECRYPT_CIPHERTEXT);
// AES256
checkNIST(256, NIST_256_ENCRYPT_KEY, NIST_256_ENCRYPT_IV,
NIST_256_ENCRYPT_PLAINTEXT, NIST_256_ENCRYPT_CIPHERTEXT);
checkNIST(256, NIST_256_DECRYPT_KEY, NIST_256_DECRYPT_IV,
NIST_256_DECRYPT_PLAINTEXT, NIST_256_DECRYPT_CIPHERTEXT);
}
public void testNISTRandomLength() throws UnsupportedCipherException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException,
ShortBufferException {
// CTR mode test vectors.
// AES128
checkNISTRandomLength(128, NIST_128_ENCRYPT_KEY, NIST_128_ENCRYPT_IV,
NIST_128_ENCRYPT_PLAINTEXT, NIST_128_ENCRYPT_CIPHERTEXT);
checkNISTRandomLength(128, NIST_128_DECRYPT_KEY, NIST_128_DECRYPT_IV,
NIST_128_DECRYPT_PLAINTEXT, NIST_128_DECRYPT_CIPHERTEXT);
// AES192
checkNISTRandomLength(192, NIST_192_ENCRYPT_KEY, NIST_192_ENCRYPT_IV,
NIST_192_ENCRYPT_PLAINTEXT, NIST_192_ENCRYPT_CIPHERTEXT);
checkNISTRandomLength(192, NIST_192_DECRYPT_KEY, NIST_192_DECRYPT_IV,
NIST_192_DECRYPT_PLAINTEXT, NIST_192_DECRYPT_CIPHERTEXT);
// AES256
checkNISTRandomLength(256, NIST_256_ENCRYPT_KEY, NIST_256_ENCRYPT_IV,
NIST_256_ENCRYPT_PLAINTEXT, NIST_256_ENCRYPT_CIPHERTEXT);
checkNISTRandomLength(256, NIST_256_DECRYPT_KEY, NIST_256_DECRYPT_IV,
NIST_256_DECRYPT_PLAINTEXT, NIST_256_DECRYPT_CIPHERTEXT);
}
private void checkNIST(int bits, byte[] key, byte[] iv, byte[] plaintext,
byte[] ciphertext) throws UnsupportedCipherException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
// First test it with JCA.
if (TEST_JCA) {
SecretKeySpec k = new SecretKeySpec(key, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(iv));
byte[] output = c.doFinal(plaintext);
assertTrue(Arrays.equals(output, ciphertext));
}
Rijndael cipher = new Rijndael(bits, 128);
cipher.initialize(key);
CTRBlockCipher ctr = new CTRBlockCipher(cipher);
ctr.init(iv);
byte[] output = new byte[plaintext.length];
ctr.processBytes(plaintext, 0, plaintext.length, output, 0);
assertTrue(Arrays.equals(output, ciphertext));
}
private void checkNISTRandomLength(int bits, byte[] key, byte[] iv,
byte[] plaintext, byte[] ciphertext)
throws UnsupportedCipherException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, ShortBufferException {
for (int i = 0; i < 1024; i++) {
// First test it with JCA.
long seed = mt.nextLong();
if (TEST_JCA) {
SecretKeySpec k = new SecretKeySpec(key, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(iv));
MersenneTwister random = new MersenneTwister(seed);
byte[] output = new byte[plaintext.length];
int inputPtr = 0;
int outputPtr = 0;
// Odd API designed for block ciphers etc.
// For CTR it should be able to return immediately each time.
// ... Actually, no. BouncyCastle's CTR breaks this assumption.
// You must handle when update() produce less than was in input.
while (inputPtr < plaintext.length) {
int max = plaintext.length - inputPtr;
int count = (max == 1) ? 1 : (random.nextInt(max - 1) + 1);
int moved = c.update(plaintext, inputPtr, count, output,
outputPtr);
outputPtr += moved;
inputPtr += count;
}
c.doFinal(plaintext, 0, plaintext.length - inputPtr, output,
outputPtr);
assertTrue(Arrays.equals(output, ciphertext));
}
Rijndael cipher = new Rijndael(bits, 128);
cipher.initialize(key);
CTRBlockCipher ctr = new CTRBlockCipher(cipher);
ctr.init(iv);
byte[] output = new byte[plaintext.length];
MersenneTwister random = new MersenneTwister(seed);
int ptr = 0;
while (ptr < plaintext.length) {
int max = plaintext.length - ptr;
int count = (max == 1) ? 1 : (random.nextInt(max - 1) + 1);
ctr.processBytes(plaintext, ptr, count, output, ptr);
ptr += count;
}
assertTrue(Arrays.equals(output, ciphertext));
}
}
public void testRandomJCA() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if(!TEST_JCA) return;
for(int i=0;i<1024;i++) {
byte[] plaintext = new byte[mt.nextInt(4096)+1];
byte[] key = new byte[32];
byte[] iv = new byte[16];
mt.nextBytes(plaintext);
mt.nextBytes(key);
mt.nextBytes(iv);
SecretKeySpec k = new SecretKeySpec(key, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(iv));
byte[] output = c.doFinal(plaintext);
c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv));
byte[] decrypted = c.doFinal(output);
assertTrue(Arrays.equals(decrypted, plaintext));
}
}
public void testRandom() throws UnsupportedCipherException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
for(int i=0;i<1024;i++) {
byte[] plaintext = new byte[mt.nextInt(4096)+1];
byte[] key = new byte[32];
byte[] iv = new byte[16];
mt.nextBytes(plaintext);
mt.nextBytes(key);
mt.nextBytes(iv);
// First encrypt as a block.
Rijndael cipher = new Rijndael(256, 128);
cipher.initialize(key);
CTRBlockCipher ctr = new CTRBlockCipher(cipher);
ctr.init(iv);
byte[] ciphertext = new byte[plaintext.length];
ctr.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
// Now decrypt.
ctr = new CTRBlockCipher(cipher);
ctr.init(iv);
byte[] finalPlaintext = new byte[plaintext.length];
ctr.processBytes(ciphertext, 0, ciphertext.length, finalPlaintext, 0);
assertTrue(Arrays.equals(finalPlaintext, plaintext));
if(TEST_JCA) {
SecretKeySpec k = new SecretKeySpec(key, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(iv));
byte[] output = c.doFinal(plaintext);
assertTrue(Arrays.equals(output, ciphertext));
c = Cipher.getInstance("AES/CTR/NOPADDING", Rijndael.AesCtrProvider);
c.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv));
byte[] decrypted = c.doFinal(output);
assertTrue(Arrays.equals(decrypted, plaintext));
}
// Now encrypt again, in random pieces.
cipher.initialize(key);
ctr = new CTRBlockCipher(cipher);
ctr.init(iv);
byte[] output = new byte[plaintext.length];
MersenneTwister random = new MersenneTwister(mt.nextLong());
int ptr = 0;
while (ptr < plaintext.length) {
int max = plaintext.length - ptr;
int count = (max == 1) ? 1 : (random.nextInt(max - 1) + 1);
ctr.processBytes(plaintext, ptr, count, output, ptr);
ptr += count;
}
assertTrue(Arrays.equals(output, ciphertext));
}
}
}