diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-07-02 10:01:13 +0400 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-07-02 10:01:13 +0400 |
commit | 05229802fc899f3dd4266c81b29497da5c201083 (patch) | |
tree | a4727e35294b2d91160ab7a9d4eb91e66bf59b82 /core | |
parent | 73fecb98eace7c09028830add53c05ea57d1c42e (diff) |
Final work for OCB mode
- Change javadoc to reflect draft has become RFC 7253
- Implement key reuse
- Add randomized tests and key reuse tests
Diffstat (limited to 'core')
-rw-r--r-- | core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java | 23 | ||||
-rw-r--r-- | core/src/test/java/org/bouncycastle/crypto/test/OCBTest.java | 221 |
2 files changed, 211 insertions, 33 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java index b942fbfb..86263914 100644 --- a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java +++ b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java @@ -13,9 +13,8 @@ import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.util.Arrays; /** - * An implementation of the "work in progress" Internet-Draft <a - * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-07">The OCB Authenticated-Encryption - * Algorithm</a>, licensed per: + * An implementation of <a href="http://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB + * Authenticated-Encryption Algorithm</a>, licensed per: * <p> * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) — “License 1” <br> @@ -113,6 +112,7 @@ public class OCBBlockCipher public void init(boolean forEncryption, CipherParameters parameters) throws IllegalArgumentException { + boolean oldForEncryption = this.forEncryption; this.forEncryption = forEncryption; this.macBlock = null; @@ -166,19 +166,18 @@ public class OCBBlockCipher * KEY-DEPENDENT INITIALISATION */ - if (keyParameter == null) + if (keyParameter != null) { - // TODO If 'keyParameter' is null we're re-using the last key. + // hashCipher always used in forward mode + hashCipher.init(true, keyParameter); + mainCipher.init(forEncryption, keyParameter); + KtopInput = null; } - else + else if (oldForEncryption != forEncryption) { - KtopInput = null; + throw new IllegalArgumentException("cannot change encrypting state without providing key."); } - // hashCipher always used in forward mode - hashCipher.init(true, keyParameter); - mainCipher.init(forEncryption, keyParameter); - this.L_Asterisk = new byte[16]; hashCipher.processBlock(L_Asterisk, 0, L_Asterisk, 0); @@ -571,7 +570,7 @@ public class OCBBlockCipher while ((x & 1L) == 0L) { ++n; - x >>= 1; + x >>>= 1; } return n; } diff --git a/core/src/test/java/org/bouncycastle/crypto/test/OCBTest.java b/core/src/test/java/org/bouncycastle/crypto/test/OCBTest.java index a4574b9b..26d7d124 100644 --- a/core/src/test/java/org/bouncycastle/crypto/test/OCBTest.java +++ b/core/src/test/java/org/bouncycastle/crypto/test/OCBTest.java @@ -1,5 +1,7 @@ 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; @@ -9,13 +11,13 @@ 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 the "work in progress" Internet-Draft <a - * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-07">The OCB Authenticated-Encryption - * Algorithm</a> + * 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 @@ -121,16 +123,18 @@ public class OCBTest runTestCase("Test Case " + i, TEST_VECTORS_96[i], 96, K96); } - runLongerTestCase(128, 128, Hex.decode("67E944D23256C5E0B6C61FA22FDF1EA2")); - runLongerTestCase(192, 128, Hex.decode("F673F2C3E7174AAE7BAE986CA9F29E17")); - runLongerTestCase(256, 128, Hex.decode("D90EB8E9C977C88B79DD793D7FFA161C")); - runLongerTestCase(128, 96, Hex.decode("77A3D8E73589158D25D01209")); - runLongerTestCase(192, 96, Hex.decode("05D56EAD2752C86BE6932C5E")); - runLongerTestCase(256, 96, Hex.decode("5458359AC23B0CBA9E6330DD")); - runLongerTestCase(128, 64, Hex.decode("192C9B7BD90BA06A")); - runLongerTestCase(192, 64, Hex.decode("0066BC6E0EF34E24")); - runLongerTestCase(256, 64, Hex.decode("7D4EA5D445501CBE")); - + 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(); } @@ -179,18 +183,20 @@ public class OCBTest int macLengthBytes = macLengthBits / 8; - // TODO Variations processing AAD and cipher bytes incrementally - KeyParameter keyParameter = new KeyParameter(K); - AEADParameters aeadParameters = new AEADParameters(keyParameter, macLengthBits, N, A); + AEADParameters parameters = new AEADParameters(keyParameter, macLengthBits, N, A); - AEADBlockCipher encCipher = initOCBCipher(true, aeadParameters); - AEADBlockCipher decCipher = initOCBCipher(false, aeadParameters); + 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); - // TODO Key reuse + // 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() @@ -259,15 +265,15 @@ public class OCBTest } } - private void runLongerTestCase(int keyLen, int tagLen, byte[] expectedOutput) + 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; @@ -334,6 +340,179 @@ public class OCBTest 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()); |