diff options
author | David Hook <dgh@cryptoworkshop.com> | 2014-05-12 15:24:20 +0400 |
---|---|---|
committer | David Hook <dgh@cryptoworkshop.com> | 2014-05-12 15:24:20 +0400 |
commit | 3c5d2f379d29a802903cb36b22eb05470cdbb5b9 (patch) | |
tree | 1e8ee6305aee136d2a065ee2e07690d6815f38f4 /core/src | |
parent | 5cfa84e6052ca461c54421dd20cdded8199f6c8e (diff) |
first cut at "skipping cipher".
Diffstat (limited to 'core/src')
5 files changed, 286 insertions, 1 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java b/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java new file mode 100644 index 00000000..8f137bd2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java @@ -0,0 +1,16 @@ +package org.bouncycastle.crypto; + +/** + * Ciphers producing a keystream which can be moved around + */ +public interface SkippingCipher +{ + /** + * Skip numberOfBlocks forwards, or backwards. If the cipher is a streamcipher a block + * size of 1 is assumed. + * + * @param numberOfBlocks the number of blocks to skip (positive forward, negative backwards). + * @return the number of blocks actually skipped. + */ + long skip(long numberOfBlocks); +} diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java b/core/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java index 2ab53d8f..81b34dfa 100644 --- a/core/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java +++ b/core/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java @@ -37,6 +37,19 @@ public class ChaChaEngine extends Salsa20Engine } } + protected boolean isCounterAtZero() + { + return engineState[12] == 0 && engineState[13] == 0; + } + + protected void retreatCounter() + { + if (--engineState[12] == Integer.MIN_VALUE) + { + --engineState[13]; + } + } + protected void resetCounter() { engineState[12] = engineState[13] = 0; diff --git a/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java b/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java index 3f20ff31..b4e89900 100644 --- a/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java +++ b/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java @@ -4,6 +4,7 @@ import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.MaxBytesExceededException; import org.bouncycastle.crypto.OutputLengthException; +import org.bouncycastle.crypto.SkippingCipher; import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; @@ -14,7 +15,7 @@ import org.bouncycastle.util.Strings; * Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 */ public class Salsa20Engine - implements StreamCipher + implements StreamCipher, SkippingCipher { public final static int DEFAULT_ROUNDS = 20; @@ -162,6 +163,19 @@ public class Salsa20Engine } } + protected boolean isCounterAtZero() + { + return engineState[8] == 0 && engineState[9] == 0; + } + + protected void retreatCounter() + { + if (--engineState[8] == Integer.MIN_VALUE) + { + --engineState[9]; + } + } + public void processBytes( byte[] in, int inOff, @@ -202,6 +216,54 @@ public class Salsa20Engine } } + public long skip(long numberOfBlocks) + { + if (numberOfBlocks >= 0) + { + for (long i = 0; i < numberOfBlocks; i++) + { + if (index == 0) + { + if (numberOfBlocks - i < 64) + { + generateKeyStream(keyStream); + } + + advanceCounter(); + } + + index = (index + 1) & 63; + } + } + else + { + for (long i = 0; i > numberOfBlocks; i--) + { + index = (index - 1) & 63; + + if (index == 0) + { + retreatCounter(); + if (i - numberOfBlocks < 63) + { + if (isCounterAtZero()) + { + generateKeyStream(keyStream); + } + else + { + retreatCounter(); + generateKeyStream(keyStream); + advanceCounter(); + } + } + } + } + } + + return numberOfBlocks; + } + public void reset() { index = 0; diff --git a/core/src/test/java/org/bouncycastle/crypto/test/ChaChaTest.java b/core/src/test/java/org/bouncycastle/crypto/test/ChaChaTest.java index 67fbdfc3..e5b3967b 100644 --- a/core/src/test/java/org/bouncycastle/crypto/test/ChaChaTest.java +++ b/core/src/test/java/org/bouncycastle/crypto/test/ChaChaTest.java @@ -1,5 +1,7 @@ package org.bouncycastle.crypto.test; +import java.security.SecureRandom; + import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.engines.ChaChaEngine; @@ -156,6 +158,7 @@ public class ChaChaTest chachaTest2(new ParametersWithIV(new KeyParameter(Hex.decode("0558ABFE51A4F74A9DF04396E93C8FE23588DB2E81D4277ACD2073C6196CBF12")), Hex.decode("167DE44BB21980E7")), set6v1_0, set6v1_65472, set6v1_65536); reinitBug(); + skipTest(); } private void chachaTest1(int rounds, CipherParameters params, String v0, String v192, String v256, String v448) @@ -265,6 +268,100 @@ public class ChaChaTest } } + private boolean areEqual(byte[] a, int aOff, byte[] b, int bOff) + { + for (int i = bOff; i != b.length; i++) + { + if (a[aOff + i - bOff] != b[i]) + { + return false; + } + } + + return true; + } + + private void skipTest() + { + SecureRandom rand = new SecureRandom(); + byte[] plain = new byte[5000]; + byte[] cipher = new byte[5000]; + + rand.nextBytes(plain); + + CipherParameters params = new ParametersWithIV(new KeyParameter(Hex.decode("0053A6F94C9FF24598EB3E91E4378ADD3083D6297CCF2275C81B6EC11467BA0D")), Hex.decode("0D74DB42A91077DE")); + ChaChaEngine engine = new ChaChaEngine(); + + engine.init(true, params); + + engine.processBytes(plain, 0, plain.length, cipher, 0); + + byte[] fragment = new byte[20]; + + engine.init(true, params); + + engine.skip(10); + + engine.processBytes(plain, 10, fragment.length, fragment, 0); + + if (!areEqual(cipher, 10, fragment, 0)) + { + fail("skip forward 10 failed"); + } + + engine.skip(1000); + + engine.processBytes(plain, 1010 + fragment.length, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010 + fragment.length, fragment, 0)) + { + fail("skip forward 1000 failed"); + } + + engine.skip(-10); + + engine.processBytes(plain, 1010 + 2 * fragment.length - 10, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010 + 2 * fragment.length - 10, fragment, 0)) + { + fail("skip back 10 failed"); + } + + engine.skip(-1000); + + engine.processBytes(plain, 60, fragment.length, fragment, 0); + + if (!areEqual(cipher, 60, fragment, 0)) + { + fail("skip back 1000 failed"); + } + + engine.reset(); + + for (int i = 0; i != 1000; i++) + { + engine.skip(i); + + engine.processBytes(plain, i, fragment.length, fragment, 0); + + if (!areEqual(cipher, i, fragment, 0)) + { + fail("skip forward i failed: " + i); + } + + engine.skip(-fragment.length); + + engine.processBytes(plain, i, fragment.length, fragment, 0); + + if (!areEqual(cipher, i, fragment, 0)) + { + fail("skip back i failed: " + i); + } + + engine.reset(); + } + } + public static void main( String[] args) { diff --git a/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java b/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java index 8cd35cc9..8e1de2c9 100644 --- a/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java +++ b/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java @@ -1,5 +1,7 @@ package org.bouncycastle.crypto.test; +import java.security.SecureRandom; + import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.engines.Salsa20Engine; @@ -153,6 +155,7 @@ public class Salsa20Test salsa20Test2(new ParametersWithIV(new KeyParameter(Hex.decode("0558ABFE51A4F74A9DF04396E93C8FE23588DB2E81D4277ACD2073C6196CBF12")), Hex.decode("167DE44BB21980E7")), set6v1_0, set6v1_65472, set6v1_65536); reinitBug(); + skipTest(); } private void salsa20Test1(int rounds, CipherParameters params, String v0, String v192, String v256, String v448) @@ -262,6 +265,100 @@ public class Salsa20Test } } + private boolean areEqual(byte[] a, int aOff, byte[] b, int bOff) + { + for (int i = bOff; i != b.length; i++) + { + if (a[aOff + i - bOff] != b[i]) + { + return false; + } + } + + return true; + } + + private void skipTest() + { + SecureRandom rand = new SecureRandom(); + byte[] plain = new byte[5000]; + byte[] cipher = new byte[5000]; + + rand.nextBytes(plain); + + CipherParameters params = new ParametersWithIV(new KeyParameter(Hex.decode("0053A6F94C9FF24598EB3E91E4378ADD3083D6297CCF2275C81B6EC11467BA0D")), Hex.decode("0D74DB42A91077DE")); + Salsa20Engine engine = new Salsa20Engine(); + + engine.init(true, params); + + engine.processBytes(plain, 0, plain.length, cipher, 0); + + byte[] fragment = new byte[20]; + + engine.init(true, params); + + engine.skip(10); + + engine.processBytes(plain, 10, fragment.length, fragment, 0); + + if (!areEqual(cipher, 10, fragment, 0)) + { + fail("skip forward 10 failed"); + } + + engine.skip(1000); + + engine.processBytes(plain, 1010 + fragment.length, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010 + fragment.length, fragment, 0)) + { + fail("skip forward 1000 failed"); + } + + engine.skip(-10); + + engine.processBytes(plain, 1010 + 2 * fragment.length - 10, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010 + 2 * fragment.length - 10, fragment, 0)) + { + fail("skip back 10 failed"); + } + + engine.skip(-1000); + + engine.processBytes(plain, 60, fragment.length, fragment, 0); + + if (!areEqual(cipher, 60, fragment, 0)) + { + fail("skip back 1000 failed"); + } + + engine.reset(); + + for (int i = 0; i != 1000; i++) + { + engine.skip(i); + + engine.processBytes(plain, i, fragment.length, fragment, 0); + + if (!areEqual(cipher, i, fragment, 0)) + { + fail("skip forward i failed: " + i); + } + + engine.skip(-fragment.length); + + engine.processBytes(plain, i, fragment.length, fragment, 0); + + if (!areEqual(cipher, i, fragment, 0)) + { + fail("skip back i failed: " + i); + } + + engine.reset(); + } + } + public static void main( String[] args) { |