| package org.bouncycastle.crypto.test; |
| |
| import java.security.SecureRandom; |
| |
| import org.bouncycastle.crypto.BlockCipher; |
| import org.bouncycastle.crypto.InvalidCipherTextException; |
| import org.bouncycastle.crypto.engines.AESEngine; |
| import org.bouncycastle.crypto.engines.DESEngine; |
| import org.bouncycastle.crypto.modes.AEADBlockCipher; |
| import org.bouncycastle.crypto.modes.OCBBlockCipher; |
| import org.bouncycastle.crypto.params.AEADParameters; |
| import org.bouncycastle.crypto.params.KeyParameter; |
| import org.bouncycastle.util.Arrays; |
| import org.bouncycastle.util.Times; |
| import org.bouncycastle.util.encoders.Hex; |
| import org.bouncycastle.util.test.SimpleTest; |
| |
| /** |
| * Test vectors from <a href="http://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB |
| * Authenticated-Encryption Algorithm</a> |
| */ |
| public class OCBTest |
| extends SimpleTest |
| { |
| private static final String KEY_128 = "000102030405060708090A0B0C0D0E0F"; |
| private static final String KEY_96 = "0F0E0D0C0B0A09080706050403020100"; |
| |
| /* |
| * Test vectors from Appendix A of the specification, containing the strings N, A, P, C in order |
| */ |
| |
| private static final String[][] TEST_VECTORS_128 = new String[][]{ |
| { "BBAA99887766554433221100", |
| "", |
| "", |
| "785407BFFFC8AD9EDCC5520AC9111EE6" }, |
| { "BBAA99887766554433221101", |
| "0001020304050607", |
| "0001020304050607", |
| "6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009" }, |
| { "BBAA99887766554433221102", |
| "0001020304050607", |
| "", |
| "81017F8203F081277152FADE694A0A00" }, |
| { "BBAA99887766554433221103", |
| "", |
| "0001020304050607", |
| "45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9" }, |
| { "BBAA99887766554433221104", |
| "000102030405060708090A0B0C0D0E0F", |
| "000102030405060708090A0B0C0D0E0F", |
| "571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358" }, |
| { "BBAA99887766554433221105", |
| "000102030405060708090A0B0C0D0E0F", |
| "", |
| "8CF761B6902EF764462AD86498CA6B97" }, |
| { "BBAA99887766554433221106", |
| "", |
| "000102030405060708090A0B0C0D0E0F", |
| "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D" }, |
| { "BBAA99887766554433221107", |
| "000102030405060708090A0B0C0D0E0F1011121314151617", |
| "000102030405060708090A0B0C0D0E0F1011121314151617", |
| "1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F" }, |
| { "BBAA99887766554433221108", |
| "000102030405060708090A0B0C0D0E0F1011121314151617", |
| "", |
| "6DC225A071FC1B9F7C69F93B0F1E10DE" }, |
| { "BBAA99887766554433221109", |
| "", |
| "000102030405060708090A0B0C0D0E0F1011121314151617", |
| "221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF" }, |
| { "BBAA9988776655443322110A", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", |
| "BD6F6C496201C69296C11EFD138A467ABD3C707924B964DEAFFC40319AF5A48540FBBA186C5553C68AD9F592A79A4240" }, |
| { "BBAA9988776655443322110B", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", |
| "", |
| "FE80690BEE8A485D11F32965BC9D2A32" }, |
| { "BBAA9988776655443322110C", |
| "", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", |
| "2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF" }, |
| { "BBAA9988776655443322110D", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "D5CA91748410C1751FF8A2F618255B68A0A12E093FF454606E59F9C1D0DDC54B65E8628E568BAD7AED07BA06A4A69483A7035490C5769E60" }, |
| { "BBAA9988776655443322110E", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "", |
| "C5CD9D1850C141E358649994EE701B68" }, |
| { "BBAA9988776655443322110F", |
| "", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479" }, |
| }; |
| |
| private static final String[][] TEST_VECTORS_96 = new String[][]{ |
| { "BBAA9988776655443322110D", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", |
| "1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA" }, |
| }; |
| |
| public String getName() |
| { |
| return "OCB"; |
| } |
| |
| public void performTest() |
| throws Exception |
| { |
| byte[] K128 = Hex.decode(KEY_128); |
| for (int i = 0; i < TEST_VECTORS_128.length; ++i) |
| { |
| runTestCase("Test Case " + i, TEST_VECTORS_128[i], 128, K128); |
| } |
| |
| byte[] K96 = Hex.decode(KEY_96); |
| for (int i = 0; i < TEST_VECTORS_96.length; ++i) |
| { |
| runTestCase("Test Case " + i, TEST_VECTORS_96[i], 96, K96); |
| } |
| |
| runLongerTestCase(128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"); |
| runLongerTestCase(192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"); |
| runLongerTestCase(256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"); |
| runLongerTestCase(128, 96, "77A3D8E73589158D25D01209"); |
| runLongerTestCase(192, 96, "05D56EAD2752C86BE6932C5E"); |
| runLongerTestCase(256, 96, "5458359AC23B0CBA9E6330DD"); |
| runLongerTestCase(128, 64, "192C9B7BD90BA06A"); |
| runLongerTestCase(192, 64, "0066BC6E0EF34E24"); |
| runLongerTestCase(256, 64, "7D4EA5D445501CBE"); |
| |
| randomTests(); |
| outputSizeTests(); |
| testExceptions(); |
| } |
| |
| private void testExceptions() throws InvalidCipherTextException |
| { |
| AEADBlockCipher ocb = createOCBCipher(); |
| |
| try |
| { |
| ocb = new OCBBlockCipher(new DESEngine(), new DESEngine()); |
| |
| fail("incorrect block size not picked up"); |
| } |
| catch (IllegalArgumentException e) |
| { |
| // expected |
| } |
| |
| try |
| { |
| ocb.init(false, new KeyParameter(new byte[16])); |
| |
| fail("illegal argument not picked up"); |
| } |
| catch (IllegalArgumentException e) |
| { |
| // expected |
| } |
| |
| AEADTestUtil.testReset(this, createOCBCipher(), createOCBCipher(), new AEADParameters(new KeyParameter(new byte[16]), 128, new byte[15])); |
| AEADTestUtil.testTampering(this, ocb, new AEADParameters(new KeyParameter(new byte[16]), 128, new byte[15])); |
| AEADTestUtil.testOutputSizes(this, createOCBCipher(), new AEADParameters(new KeyParameter(new byte[16]), 128, |
| new byte[15])); |
| AEADTestUtil.testBufferSizeChecks(this, createOCBCipher(), new AEADParameters(new KeyParameter(new byte[16]), |
| 128, new byte[15])); |
| } |
| |
| private void runTestCase(String testName, String[] testVector, int macLengthBits, byte[] K) |
| throws InvalidCipherTextException |
| { |
| int pos = 0; |
| byte[] N = Hex.decode(testVector[pos++]); |
| byte[] A = Hex.decode(testVector[pos++]); |
| byte[] P = Hex.decode(testVector[pos++]); |
| byte[] C = Hex.decode(testVector[pos++]); |
| |
| int macLengthBytes = macLengthBits / 8; |
| |
| KeyParameter keyParameter = new KeyParameter(K); |
| AEADParameters parameters = new AEADParameters(keyParameter, macLengthBits, N, A); |
| |
| AEADBlockCipher encCipher = initOCBCipher(true, parameters); |
| AEADBlockCipher decCipher = initOCBCipher(false, parameters); |
| |
| checkTestCase(encCipher, decCipher, testName, macLengthBytes, P, C); |
| checkTestCase(encCipher, decCipher, testName + " (reused)", macLengthBytes, P, C); |
| |
| // Key reuse |
| AEADParameters keyReuseParams = AEADTestUtil.reuseKey(parameters); |
| encCipher.init(true, keyReuseParams); |
| decCipher.init(false, keyReuseParams); |
| checkTestCase(encCipher, decCipher, testName + " (key reuse)", macLengthBytes, P, C); |
| } |
| |
| private BlockCipher createUnderlyingCipher() |
| { |
| return new AESEngine(); |
| } |
| |
| private AEADBlockCipher createOCBCipher() |
| { |
| return new OCBBlockCipher(createUnderlyingCipher(), createUnderlyingCipher()); |
| } |
| |
| private AEADBlockCipher initOCBCipher(boolean forEncryption, AEADParameters parameters) |
| { |
| AEADBlockCipher c = createOCBCipher(); |
| c.init(forEncryption, parameters); |
| return c; |
| } |
| |
| private void checkTestCase(AEADBlockCipher encCipher, AEADBlockCipher decCipher, String testName, |
| int macLengthBytes, byte[] P, byte[] C) |
| throws InvalidCipherTextException |
| { |
| byte[] tag = Arrays.copyOfRange(C, C.length - macLengthBytes, C.length); |
| |
| { |
| byte[] enc = new byte[encCipher.getOutputSize(P.length)]; |
| int len = encCipher.processBytes(P, 0, P.length, enc, 0); |
| len += encCipher.doFinal(enc, len); |
| |
| if (enc.length != len) |
| { |
| fail("encryption reported incorrect length: " + testName); |
| } |
| |
| if (!areEqual(C, enc)) |
| { |
| fail("incorrect encrypt in: " + testName); |
| } |
| |
| if (!areEqual(tag, encCipher.getMac())) |
| { |
| fail("getMac() not the same as the appended tag: " + testName); |
| } |
| } |
| |
| { |
| byte[] dec = new byte[decCipher.getOutputSize(C.length)]; |
| int len = decCipher.processBytes(C, 0, C.length, dec, 0); |
| len += decCipher.doFinal(dec, len); |
| |
| if (dec.length != len) |
| { |
| fail("decryption reported incorrect length: " + testName); |
| } |
| |
| if (!areEqual(P, dec)) |
| { |
| fail("incorrect decrypt in: " + testName); |
| } |
| |
| if (!areEqual(tag, decCipher.getMac())) |
| { |
| fail("getMac() not the same as the appended tag: " + testName); |
| } |
| } |
| } |
| |
| private void runLongerTestCase(int keyLen, int tagLen, String expectedOutputHex) |
| throws InvalidCipherTextException |
| { |
| byte[] expectedOutput = Hex.decode(expectedOutputHex); |
| byte[] keyBytes = new byte[keyLen / 8]; |
| keyBytes[keyBytes.length - 1] = (byte)tagLen; |
| KeyParameter key = new KeyParameter(keyBytes); |
| |
| AEADBlockCipher c1 = initOCBCipher(true, new AEADParameters(key, tagLen, createNonce(385))); |
| AEADBlockCipher c2 = createOCBCipher(); |
| |
| long total = 0; |
| |
| byte[] S = new byte[128]; |
| |
| int n = 0; |
| for (int i = 0; i < 128; ++i) |
| { |
| c2.init(true, new AEADParameters(key, tagLen, createNonce(++n))); |
| total += updateCiphers(c1, c2, S, i, true, true); |
| c2.init(true, new AEADParameters(key, tagLen, createNonce(++n))); |
| total += updateCiphers(c1, c2, S, i, false, true); |
| c2.init(true, new AEADParameters(key, tagLen, createNonce(++n))); |
| total += updateCiphers(c1, c2, S, i, true, false); |
| } |
| |
| long expectedTotal = 16256 + (48 * tagLen); |
| |
| if (total != expectedTotal) |
| { |
| fail("test generated the wrong amount of input: " + total); |
| } |
| |
| byte[] output = new byte[c1.getOutputSize(0)]; |
| c1.doFinal(output, 0); |
| |
| if (!areEqual(expectedOutput, output)) |
| { |
| fail("incorrect encrypt in long-form test"); |
| } |
| } |
| |
| private byte[] createNonce(int n) |
| { |
| return new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)(n >>> 8), (byte)n }; |
| } |
| |
| private int updateCiphers(AEADBlockCipher c1, AEADBlockCipher c2, byte[] S, int i, |
| boolean includeAAD, boolean includePlaintext) |
| throws InvalidCipherTextException |
| { |
| int inputLen = includePlaintext ? i : 0; |
| int outputLen = c2.getOutputSize(inputLen); |
| |
| byte[] output = new byte[outputLen]; |
| |
| int len = 0; |
| |
| if (includeAAD) |
| { |
| c2.processAADBytes(S, 0, i); |
| } |
| |
| if (includePlaintext) |
| { |
| len += c2.processBytes(S, 0, i, output, len); |
| } |
| |
| len += c2.doFinal(output, len); |
| |
| c1.processAADBytes(output, 0, len); |
| |
| return len; |
| } |
| |
| private void randomTests() |
| throws InvalidCipherTextException |
| { |
| SecureRandom srng = new SecureRandom(); |
| srng.setSeed(Times.nanoTime()); |
| for (int i = 0; i < 10; ++i) |
| { |
| randomTest(srng); |
| } |
| } |
| |
| private void randomTest(SecureRandom srng) |
| throws InvalidCipherTextException |
| { |
| int kLength = 16 + 8 * (Math.abs(srng.nextInt()) % 3); |
| byte[] K = new byte[kLength]; |
| srng.nextBytes(K); |
| |
| int pLength = srng.nextInt() >>> 16; |
| byte[] P = new byte[pLength]; |
| srng.nextBytes(P); |
| |
| int aLength = srng.nextInt() >>> 24; |
| byte[] A = new byte[aLength]; |
| srng.nextBytes(A); |
| |
| int saLength = srng.nextInt() >>> 24; |
| byte[] SA = new byte[saLength]; |
| srng.nextBytes(SA); |
| |
| int ivLength = 1 + nextInt(srng, 15); |
| byte[] IV = new byte[ivLength]; |
| srng.nextBytes(IV); |
| |
| AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, IV, A); |
| AEADBlockCipher cipher = initOCBCipher(true, parameters); |
| byte[] C = new byte[cipher.getOutputSize(P.length)]; |
| int predicted = cipher.getUpdateOutputSize(P.length); |
| |
| int split = nextInt(srng, SA.length + 1); |
| cipher.processAADBytes(SA, 0, split); |
| int len = cipher.processBytes(P, 0, P.length, C, 0); |
| cipher.processAADBytes(SA, split, SA.length - split); |
| |
| if (predicted != len) |
| { |
| fail("encryption reported incorrect update length in randomised test"); |
| } |
| |
| len += cipher.doFinal(C, len); |
| |
| if (C.length != len) |
| { |
| fail("encryption reported incorrect length in randomised test"); |
| } |
| |
| byte[] encT = cipher.getMac(); |
| byte[] tail = new byte[C.length - P.length]; |
| System.arraycopy(C, P.length, tail, 0, tail.length); |
| |
| if (!areEqual(encT, tail)) |
| { |
| fail("stream contained wrong mac in randomised test"); |
| } |
| |
| cipher.init(false, parameters); |
| byte[] decP = new byte[cipher.getOutputSize(C.length)]; |
| predicted = cipher.getUpdateOutputSize(C.length); |
| |
| split = nextInt(srng, SA.length + 1); |
| cipher.processAADBytes(SA, 0, split); |
| len = cipher.processBytes(C, 0, C.length, decP, 0); |
| cipher.processAADBytes(SA, split, SA.length - split); |
| |
| if (predicted != len) |
| { |
| fail("decryption reported incorrect update length in randomised test"); |
| } |
| |
| len += cipher.doFinal(decP, len); |
| |
| if (!areEqual(P, decP)) |
| { |
| fail("incorrect decrypt in randomised test"); |
| } |
| |
| byte[] decT = cipher.getMac(); |
| if (!areEqual(encT, decT)) |
| { |
| fail("decryption produced different mac from encryption"); |
| } |
| |
| // |
| // key reuse test |
| // |
| cipher.init(false, AEADTestUtil.reuseKey(parameters)); |
| decP = new byte[cipher.getOutputSize(C.length)]; |
| |
| split = nextInt(srng, SA.length + 1); |
| cipher.processAADBytes(SA, 0, split); |
| len = cipher.processBytes(C, 0, C.length, decP, 0); |
| cipher.processAADBytes(SA, split, SA.length - split); |
| |
| len += cipher.doFinal(decP, len); |
| |
| if (!areEqual(P, decP)) |
| { |
| fail("incorrect decrypt in randomised test"); |
| } |
| |
| decT = cipher.getMac(); |
| if (!areEqual(encT, decT)) |
| { |
| fail("decryption produced different mac from encryption"); |
| } |
| } |
| |
| private void outputSizeTests() |
| { |
| byte[] K = new byte[16]; |
| byte[] A = null; |
| byte[] IV = new byte[15]; |
| |
| AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, IV, A); |
| AEADBlockCipher cipher = initOCBCipher(true, parameters); |
| |
| if (cipher.getUpdateOutputSize(0) != 0) |
| { |
| fail("incorrect getUpdateOutputSize for initial 0 bytes encryption"); |
| } |
| |
| if (cipher.getOutputSize(0) != 16) |
| { |
| fail("incorrect getOutputSize for initial 0 bytes encryption"); |
| } |
| |
| cipher.init(false, parameters); |
| |
| if (cipher.getUpdateOutputSize(0) != 0) |
| { |
| fail("incorrect getUpdateOutputSize for initial 0 bytes decryption"); |
| } |
| |
| // NOTE: 0 bytes would be truncated data, but we want it to fail in the doFinal, not here |
| if (cipher.getOutputSize(0) != 0) |
| { |
| fail("fragile getOutputSize for initial 0 bytes decryption"); |
| } |
| |
| if (cipher.getOutputSize(16) != 0) |
| { |
| fail("incorrect getOutputSize for initial MAC-size bytes decryption"); |
| } |
| } |
| |
| private static int nextInt(SecureRandom rand, int n) |
| { |
| if ((n & -n) == n) // i.e., n is a power of 2 |
| { |
| return (int)((n * (long)(rand.nextInt() >>> 1)) >> 31); |
| } |
| |
| int bits, value; |
| do |
| { |
| bits = rand.nextInt() >>> 1; |
| value = bits % n; |
| } |
| while (bits - value + (n - 1) < 0); |
| |
| return value; |
| } |
| |
| public static void main(String[] args) |
| { |
| runTest(new OCBTest()); |
| } |
| } |