diff options
author | David Hook <dgh@cryptoworkshop.com> | 2014-05-16 09:49:13 +0400 |
---|---|---|
committer | David Hook <dgh@cryptoworkshop.com> | 2014-05-16 09:49:13 +0400 |
commit | 8b2aeaea94a1f9c3450b5bd8841c37ac8e78efd5 (patch) | |
tree | 75a8543081a363ff93aba5cfae9538f9a85d8e42 /core/src | |
parent | 13f8d8f5309cdb26317e9578a54ccdcd635fe706 (diff) |
further work on SkippingCipher, added StreamCipher interface to SICBlockCipher.
Diffstat (limited to 'core/src')
5 files changed, 270 insertions, 31 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java b/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java index bf2595fd..477be0c0 100644 --- a/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java +++ b/core/src/main/java/org/bouncycastle/crypto/SkippingCipher.java @@ -6,12 +6,19 @@ package org.bouncycastle.crypto; public interface SkippingCipher { /** - * Skip numberOfBlocks forwards, or backwards. If the cipher is a stream cipher a block - * size of 1 is assumed. + * Skip numberOfBytes forwards, or backwards. * - * @param numberOfBlocks the number of blocks to skip (positive forward, negative backwards). - * @return the number of blocks actually skipped. - * @throws java.lang.IllegalArgumentException if numberOfBlocks is an invalid value. + * @param numberOfBytes the number of bytes to skip (positive forward, negative backwards). + * @return the number of bytes actually skipped. + * @throws java.lang.IllegalArgumentException if numberOfBytes is an invalid value. */ - long skip(long numberOfBlocks); + long skip(long numberOfBytes); + + /** + * Reset the cipher and then skip forward to a given position. + * + * @param position the number of bytes in to set the cipher state to. + * @return the byte position moved to. + */ + long seekTo(long position); } 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 b4e89900..a2837dd8 100644 --- a/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java +++ b/core/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java @@ -216,15 +216,15 @@ public class Salsa20Engine } } - public long skip(long numberOfBlocks) + public long skip(long numberOfBytes) { - if (numberOfBlocks >= 0) + if (numberOfBytes >= 0) { - for (long i = 0; i < numberOfBlocks; i++) + for (long i = 0; i < numberOfBytes; i++) { if (index == 0) { - if (numberOfBlocks - i < 64) + if (numberOfBytes - i < 64) { generateKeyStream(keyStream); } @@ -237,14 +237,14 @@ public class Salsa20Engine } else { - for (long i = 0; i > numberOfBlocks; i--) + for (long i = 0; i > numberOfBytes; i--) { index = (index - 1) & 63; if (index == 0) { retreatCounter(); - if (i - numberOfBlocks < 63) + if (i - numberOfBytes < 63) { if (isCounterAtZero()) { @@ -261,7 +261,14 @@ public class Salsa20Engine } } - return numberOfBlocks; + return numberOfBytes; + } + + public long seekTo(long position) + { + reset(); + + return skip(position); } public void reset() diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java index da8c4ae1..5da541ee 100644 --- a/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java +++ b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java @@ -3,6 +3,8 @@ package org.bouncycastle.crypto.modes; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.SkippingCipher; +import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.params.ParametersWithIV; /** @@ -10,7 +12,7 @@ import org.bouncycastle.crypto.params.ParametersWithIV; * block cipher. This mode is also known as CTR mode. */ public class SICBlockCipher - implements BlockCipher + implements BlockCipher, StreamCipher, SkippingCipher { private final BlockCipher cipher; private final int blockSize; @@ -18,7 +20,7 @@ public class SICBlockCipher private byte[] IV; private byte[] counter; private byte[] counterOut; - + private int byteCount; /** * Basic constructor. @@ -32,6 +34,7 @@ public class SICBlockCipher this.IV = new byte[blockSize]; this.counter = new byte[blockSize]; this.counterOut = new byte[blockSize]; + this.byteCount = 0; } @@ -53,17 +56,17 @@ public class SICBlockCipher { if (params instanceof ParametersWithIV) { - ParametersWithIV ivParam = (ParametersWithIV)params; - byte[] iv = ivParam.getIV(); - System.arraycopy(iv, 0, IV, 0, IV.length); + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + System.arraycopy(iv, 0, IV, 0, IV.length); - reset(); + // if null it's an IV changed only. + if (ivParam.getParameters() != null) + { + cipher.init(true, ivParam.getParameters()); + } - // if null it's an IV changed only. - if (ivParam.getParameters() != null) - { - cipher.init(true, ivParam.getParameters()); - } + reset(); } else { @@ -76,6 +79,34 @@ public class SICBlockCipher return cipher.getAlgorithmName() + "/SIC"; } + public byte returnByte(byte in) + { + return processByte(in); + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException + { + if (outOff + len > out.length) + { + throw new DataLengthException("output buffer too small"); + } + + if (inOff + len > in.length) + { + throw new DataLengthException("input buffer too small for len"); + } + + int inStart = inOff; + int inEnd = inOff + len; + int outStart = outOff; + + while (inStart < inEnd) + { + out[outStart++] = processByte(in[inStart++]); + } + } + public int getBlockSize() { return cipher.getBlockSize(); @@ -85,29 +116,106 @@ public class SICBlockCipher public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException { - cipher.processBlock(counter, 0, counterOut, 0); + processBytes(in, inOff, blockSize, out, outOff); - // - // XOR the counterOut with the plaintext producing the cipher text - // - for (int i = 0; i < counterOut.length; i++) + return blockSize; + } + + private byte processByte(byte in) + throws DataLengthException, IllegalStateException + { + if (byteCount == 0) { - out[outOff + i] = (byte)(counterOut[i] ^ in[inOff + i]); + cipher.processBlock(counter, 0, counterOut, 0); } + byte rv = (byte)(counterOut[byteCount++] ^ in); + if (byteCount == counter.length) + { + byteCount = 0; + + incrementCounter(); + } + + return rv; + } + + private void incrementCounter() + { // increment counter by 1. for (int i = counter.length - 1; i >= 0 && ++counter[i] == 0; i--) { ; // do nothing - pre-increment and test for 0 in counter does the job. } + } - return counter.length; + private void decrementCounter() + { + // increment counter by 1. + for (int i = counter.length - 1; i >= 0 && --counter[i] == Integer.MIN_VALUE; i--) + { + ; // do nothing - pre-increment and test for 0 in counter does the job. + } } + private void adjustCounter(long n) + { + if (n >= 0) + { + long numBlocks = (n + byteCount) / blockSize; + + for (long i = 0; i != numBlocks; i++) + { + incrementCounter(); + } + + byteCount = (int)((n + byteCount) - (blockSize * numBlocks)); + } + else + { + long numBlocks = (-n - byteCount) / blockSize; + + for (long i = 0; i != numBlocks; i++) + { + decrementCounter(); + } + + int gap = (int)(byteCount + n + (blockSize * numBlocks)); + + if (gap >= 0) + { + byteCount = 0; + } + else + { + decrementCounter(); + byteCount = blockSize + gap; + } + } + } public void reset() { System.arraycopy(IV, 0, counter, 0, counter.length); cipher.reset(); + this.byteCount = 0; + } + + public long skip(long numberOfBytes) + { + adjustCounter(numberOfBytes); + + cipher.processBlock(counter, 0, counterOut, 0); + + return numberOfBytes; + } + + public long seekTo(long position) + { + reset(); + + skip(position); + + return 0; } } diff --git a/core/src/test/java/org/bouncycastle/crypto/test/AESTest.java b/core/src/test/java/org/bouncycastle/crypto/test/AESTest.java index 8c9637f7..791e47b9 100644 --- a/core/src/test/java/org/bouncycastle/crypto/test/AESTest.java +++ b/core/src/test/java/org/bouncycastle/crypto/test/AESTest.java @@ -1,7 +1,10 @@ package org.bouncycastle.crypto.test; +import java.security.SecureRandom; + import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; @@ -243,6 +246,109 @@ public class AESTest } } + 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("5F060D3716B345C253F6749ABAC10917")), Hex.decode("00000000000000000000000000000000")); + SICBlockCipher engine = new SICBlockCipher(new AESEngine()); + + 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.seekTo(1010); + + engine.processBytes(plain, 1010, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010, fragment, 0)) + { + fail("seek to 1010 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 void performTest() throws Exception { @@ -285,6 +391,8 @@ public class AESTest testNullSIC(); testNullOFB(); testNullCFB(); + + skipTest(); } public static void main( 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 8e1de2c9..e36b679e 100644 --- a/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java +++ b/core/src/test/java/org/bouncycastle/crypto/test/Salsa20Test.java @@ -333,6 +333,15 @@ public class Salsa20Test fail("skip back 1000 failed"); } + engine.seekTo(1010); + + engine.processBytes(plain, 1010, fragment.length, fragment, 0); + + if (!areEqual(cipher, 1010, fragment, 0)) + { + fail("seek to 1010 failed"); + } + engine.reset(); for (int i = 0; i != 1000; i++) |