package gnu.testlet.gnu.crypto.cipher; // --------------------------------------------------------------------------- // $Id: TestOfNistVectors.java,v 1.5 2005/10/06 04:24:19 rsdio Exp $ // // Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. // // This file is part of GNU Crypto. // // GNU Crypto is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or (at your option) // any later version. // // GNU Crypto is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, write to the // // Free Software Foundation Inc., // 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301 // USA // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give // you permission to link this library with independent modules to // produce an executable, regardless of the license terms of these // independent modules, and to copy and distribute the resulting // executable under terms of your choice, provided that you also meet, // for each linked independent module, the terms and conditions of the // license of that module. An independent module is a module which is // not derived from or based on this library. If you modify this // library, you may extend this exception to your version of the // library, but you are not obligated to do so. If you do not wish to // do so, delete this exception statement from your version. // --------------------------------------------------------------------------- // Tags: GNU-CRYPTO import gnu.crypto.cipher.CipherFactory; import gnu.crypto.cipher.IBlockCipher; import gnu.crypto.util.Util; import gnu.testlet.TestHarness; import gnu.testlet.Testlet; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.net.URL; import java.util.Arrays; import java.util.HashMap; /** * <p>A generic cipher conformance test against NIST-style test vectors. To * run a test for a particular cipher, include the files `ecb_vt.txt', * `ecb_vk.txt', `ecb_e_m.txt', `ecb_d_m.txt', `cbc_e_m.txt', and * `cbc_d_m.txt' in the directory `tv/ciphername-blocksize'.</p> * * <p>That is, to test Rijndael with a 128 bit block size, put the appropriate * (and appropriately named) files into the directory `tv/rijndael-128'. Then * create an instance of this class with e.g. * * <blockquote> * <code>test = new TestOfNistVectors("rijndael", 16);</code> * </blockquote> * * or * * <blockquote> * <code>test = new TestOfNistVectors("rijndael");</code> * </blockquote> * * to exercise the algorithm with a 128-bit (16 bytes) block size --which is * the block size value for AES.</p> * * <p>Use <code>test()</code> as your test case.</p> * * <p>Note that a full-conformance test will likely take a while to finish * (it would have to do 48,001,152 encryptions or decryptions).</p> * * <p>References:</p> * <ol> * <li><a href="http://csrc.nist.gov/encryption/aes/katmct/katmct.htm">Known * Answer Tests and Monte Carlo Tests for AES Submissions</a> for an * explanation of the tests and the format of the resulting files.</li> * </ol> * * @version $Revision: 1.5 $ */ public class TestOfNistVectors implements Testlet { // Constants and variables // ------------------------------------------------------------------------ // file names. protected static final String ECB_VK = "ecb_vk.txt"; protected static final String ECB_VT = "ecb_vt.txt"; protected static final String ECB_E_M = "ecb_e_m.txt"; protected static final String ECB_D_M = "ecb_d_m.txt"; protected static final String CBC_E_M = "cbc_e_m.txt"; protected static final String CBC_D_M = "cbc_d_m.txt"; protected static final int ENCRYPTION = 0; protected static final int DECRYPTION = 1; // Endianness. public static final int BIG_ENDIAN = 0; public static final int LITTLE_ENDIAN = 1; protected String algorithm; protected IBlockCipher cipher; protected HashMap attrib; protected URL ecb_vk; protected URL ecb_vt; protected URL ecb_e_m; protected URL ecb_d_m; protected URL cbc_e_m; protected URL cbc_d_m; protected int endianness; // Constructor(s) // ------------------------------------------------------------------------ /** * <p>Constructor to instantiate a test case for the AES block cipher * algorithm, with 128-bit block size.</p> * * @throws InternalError if the designated algorithm is not supported. * @throws ExceptionInInitializerError if an error occurs during the * instantiation of the desired cipher algorithm. */ public TestOfNistVectors() { this("aes", BIG_ENDIAN); } /** * <p>Constructor to instantiate a test case for a designated symmetric key * block cipher algorithm, with 128-bit block size (AES block size).</p> * * @param algorithm the name of the symmetric key block cipher to exercise. * @throws InternalError if the designated algorithm is not supported. * @throws ExceptionInInitializerError if an error occurs during the * instantiation of the desired cipher algorithm. */ public TestOfNistVectors(String algorithm, int endianness) { this(algorithm, 16, endianness); } /** * <p>Constructor to instantiate a test case for a designated symmetric key * block cipher algorithm, with a given block-size (in bytes).</p> * * @param algorithm the name of the symmetric key block cipher to exercise. * @param blockSize the block size to use, in bytes, with the designated * algorithm. * @throws InternalError if the designated algorithm is not supported. * @throws ExceptionInInitializerError if an error occurs during the * instantiation of the desired cipher algorithm. */ public TestOfNistVectors(String algorithm, int blockSize, int endianness) { super(); this.endianness = endianness; this.algorithm = algorithm; cipher = CipherFactory.getInstance(algorithm); attrib = new HashMap(); attrib.put(IBlockCipher.CIPHER_BLOCK_SIZE, new Integer(blockSize)); attrib.put(IBlockCipher.KEY_MATERIAL, new byte[cipher.defaultKeySize()]); try { cipher.init(attrib); } catch (Exception x) { throw new ExceptionInInitializerError(x); } } // Class methods. // ----------------------------------------------------------------------- // Instance methods. // ------------------------------------------------------------------------ /** * <p>Converts a given hexadecimal string to a byte array.</p> * * @param s the string consisting of hexadecimal characters to convert into * a byte array. */ protected byte[] stringToBytes(String s) { if (endianness == BIG_ENDIAN) { return Util.toBytesFromString(s); } else { return Util.toReversedBytesFromString(s); } } public void test(TestHarness harness) { harness.checkPoint("TestOfNistVectors("+algorithm+")"); String path1 = "/tv/nist/" + cipher.name().toLowerCase() + "/"; String path2 = "/tv/nist/" + this.algorithm.trim() + "/"; String s = "Conformance(" + cipher.name() + "): "; try { ecb_vk = this.getClass().getResource(path1 + ECB_VK); if (ecb_vk == null) { ecb_vk = this.getClass().getResource(path2 + ECB_VK); } ecb_vt = this.getClass().getResource(path1 + ECB_VT); if (ecb_vt == null) { ecb_vt = this.getClass().getResource(path2 + ECB_VT); } ecb_e_m = this.getClass().getResource(path1 + ECB_E_M); if (ecb_e_m == null) { ecb_e_m = this.getClass().getResource(path2 + ECB_E_M); } ecb_d_m = this.getClass().getResource(path1 + ECB_D_M); if (ecb_d_m == null) { ecb_d_m = this.getClass().getResource(path2 + ECB_D_M); } cbc_e_m = this.getClass().getResource(path1 + CBC_E_M); if (cbc_e_m == null) { cbc_e_m = this.getClass().getResource(path2 + CBC_E_M); } cbc_d_m = this.getClass().getResource(path1 + CBC_D_M); if (cbc_d_m == null) { cbc_d_m = this.getClass().getResource(path2 + CBC_D_M); } if (ecb_vk != null) { KatTest(harness, ecb_vk.openStream()); } if (ecb_vt != null) { KatTest(harness, ecb_vt.openStream()); } if (ecb_e_m != null) { MCTestECB(harness, ecb_e_m.openStream(), ENCRYPTION); } if (ecb_d_m != null) { MCTestECB(harness, ecb_d_m.openStream(), DECRYPTION); } if (cbc_e_m != null) { MCTestCBC(harness, cbc_e_m.openStream(), ENCRYPTION); } if (cbc_d_m != null) { MCTestCBC(harness, cbc_d_m.openStream(), DECRYPTION); } } catch (Exception x) { harness.debug(x); harness.fail(s + x.getMessage()); } } // Own methods ------------------------------------------------------------- /** Variable-key and variable-text known answer tests. */ protected void KatTest(TestHarness harness, InputStream tvIn) throws Exception { LineNumberReader in = new LineNumberReader(new InputStreamReader(tvIn)); String line; byte[] key = null; byte[] pt = null; byte[] ct = new byte[cipher.currentBlockSize()]; byte[] ect = null; while ((line = in.readLine()) != null) { if (line.startsWith("KEYSIZE=")) { int ks = Integer.parseInt(line.substring(line.indexOf('=')+1)); key = new byte[ks / 8]; } else if (line.startsWith("PT=")) { pt = stringToBytes(line.substring(line.indexOf('=')+1)); } else if (line.startsWith("KEY=")) { key = stringToBytes(line.substring(line.indexOf('=')+1)); attrib.put(IBlockCipher.KEY_MATERIAL, key); } else if (line.startsWith("CT=")) { ect = stringToBytes(line.substring(line.indexOf('=')+1)); cipher.reset(); cipher.init(attrib); cipher.encryptBlock(pt, 0, ct, 0); harness.check(Arrays.equals(ct, ect)); } // Other lines are ignored. } in.close(); } /** Electronic codebook mode monte carlo tests. */ protected void MCTestECB(TestHarness harness, InputStream tvIn, int mode) throws Exception { LineNumberReader in = new LineNumberReader(new InputStreamReader(tvIn)); String line; byte[] key = new byte[cipher.defaultKeySize()]; byte[] pt = new byte[cipher.currentBlockSize()]; byte[] ct = new byte[cipher.currentBlockSize()]; byte[] et = new byte[cipher.currentBlockSize()]; while ((line = in.readLine()) != null) { if (line.startsWith("KEYSIZE=")) { int ks = Integer.parseInt(line.substring(line.indexOf('=')+1)); key = new byte[ks / 8]; } else if (line.startsWith("PT=")) { if (mode == DECRYPTION) { et = stringToBytes(line.substring(line.indexOf('=')+1)); cipher.reset(); cipher.init(attrib); for (int i = 0; i < 10000; i++) { cipher.decryptBlock(ct, 0, pt, 0); System.arraycopy(pt, 0, ct, 0, pt.length); } harness.check(Arrays.equals(pt, et)); } else { pt = stringToBytes(line.substring(line.indexOf('=')+1)); } } else if (line.startsWith("KEY=")) { key = stringToBytes(line.substring(line.indexOf('=')+1)); attrib.put(IBlockCipher.KEY_MATERIAL, key); } else if (line.startsWith("CT=")) { if (mode == ENCRYPTION) { et = stringToBytes(line.substring(line.indexOf('=')+1)); cipher.reset(); cipher.init(attrib); for (int i = 0; i < 10000; i++) { cipher.encryptBlock(pt, 0, ct, 0); System.arraycopy(ct, 0, pt, 0, ct.length); } harness.check(Arrays.equals(ct, et)); } else { ct = stringToBytes(line.substring(line.indexOf('=')+1)); } } // Other lines are ignored. } in.close(); } /** Cipher block chaining mode monte carlo test. */ protected void MCTestCBC(TestHarness harness, InputStream tvIn, int mode) throws Exception { LineNumberReader in = new LineNumberReader(new InputStreamReader(tvIn)); String line; byte[] key = new byte[cipher.defaultKeySize()]; byte[] pt = new byte[cipher.currentBlockSize()]; byte[] ct = new byte[cipher.currentBlockSize()]; byte[] et = new byte[cipher.currentBlockSize()]; byte[] last = new byte[cipher.currentBlockSize()]; byte[] iv = new byte[cipher.currentBlockSize()]; cipher.reset(); while ((line = in.readLine()) != null) { if (line.startsWith("KEYSIZE=")) { int ks = Integer.parseInt(line.substring(line.indexOf('=')+1)); key = new byte[ks / 8]; if (mode == ENCRYPTION) { for (int i = 0; i < ct.length; i++) { ct[i] = 0; } } else { for (int i = 0; i < pt.length; i++) { pt[i] = 0; } } } else if (line.startsWith("PT=")) { if (mode == DECRYPTION) { et = stringToBytes(line.substring(line.indexOf('=')+1)); cipher.reset(); cipher.init(attrib); for (int i = 0; i < 10000; i++) { cipher.decryptBlock(ct, 0, pt, 0); for (int j = 0; j < pt.length; j++) { pt[j] ^= iv[j]; } System.arraycopy(ct, 0, iv, 0, ct.length); System.arraycopy(pt, 0, ct, 0, pt.length); } harness.check(Arrays.equals(pt, et)); } else { pt = stringToBytes(line.substring(line.indexOf('=')+1)); } } else if (line.startsWith("KEY=")) { key = stringToBytes(line.substring(line.indexOf('=')+1)); attrib.put(IBlockCipher.KEY_MATERIAL, key); } else if (line.startsWith("CT=")) { if (mode == ENCRYPTION) { et = stringToBytes(line.substring(line.indexOf('=')+1)); cipher.reset(); cipher.init(attrib); for (int i = 0; i < 10000; i++) { for (int j = 0; j < pt.length; j++) { pt[j] ^= iv[j]; } System.arraycopy(ct, 0, last, 0, ct.length); cipher.encryptBlock(pt, 0, ct, 0); System.arraycopy(ct, 0, iv, 0, ct.length); System.arraycopy(last, 0, pt, 0, last.length); } harness.check(Arrays.equals(ct, et)); } else { ct = stringToBytes(line.substring(line.indexOf('=')+1)); } } else if (line.startsWith("IV=")) { iv = stringToBytes(line.substring(line.indexOf('=')+1)); } // Other lines are ignored. } in.close(); } }