Skip to content

Commit 620a9ad

Browse files
Simplify algorithm by no longer generating keystream with custom implementation. Use CTR mode implementation of BouncyCastle instead.
1 parent 63ce40b commit 620a9ad

File tree

5 files changed

+202
-40
lines changed

5 files changed

+202
-40
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.cryptomator.siv;
2+
3+
import org.bouncycastle.crypto.BlockCipher;
4+
import org.bouncycastle.crypto.CipherParameters;
5+
import org.bouncycastle.crypto.OutputLengthException;
6+
import org.bouncycastle.crypto.modes.SICBlockCipher;
7+
import org.bouncycastle.crypto.params.KeyParameter;
8+
import org.bouncycastle.crypto.params.ParametersWithIV;
9+
10+
import java.util.function.Supplier;
11+
12+
/**
13+
* Performs CTR Mode computations facilitating BouncyCastle's {@link SICBlockCipher}.
14+
*/
15+
class CustomCtrComputer implements SivMode.CtrComputer {
16+
17+
private final Supplier<BlockCipher> blockCipherSupplier;
18+
19+
public CustomCtrComputer(Supplier<BlockCipher> blockCipherSupplier) {
20+
this.blockCipherSupplier = blockCipherSupplier;
21+
}
22+
23+
@Override
24+
public byte[] computeCtr(byte[] input, byte[] key, byte[] iv) {
25+
SICBlockCipher cipher = new SICBlockCipher(blockCipherSupplier.get());
26+
CipherParameters params = new ParametersWithIV(new KeyParameter(key), iv);
27+
cipher.init(true, params);
28+
try {
29+
byte[] output = new byte[input.length];
30+
cipher.processBytes(input, 0, input.length, output, 0);
31+
return output;
32+
} catch (OutputLengthException e) {
33+
throw new IllegalStateException("In CTR mode output length must be equal to input length", e);
34+
}
35+
}
36+
}

src/main/java/org/cryptomator/siv/SivMode.java

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public final class SivMode {
3030
private static final byte DOUBLING_CONST = (byte) 0x87;
3131

3232
private final ThreadLocal<BlockCipher> threadLocalCipher;
33+
private final CtrComputer ctrComputer;
3334

3435
/**
3536
* Creates an AES-SIV instance using JCE's cipher implementation, which should normally be the best choice.<br>
@@ -67,29 +68,40 @@ public BlockCipher create() {
6768
* @param cipherFactory A factory method creating a Blockcipher.get(). Must use a block size of 128 bits (16 bytes).
6869
*/
6970
public SivMode(final BlockCipherFactory cipherFactory) {
71+
this(ThreadLocal.withInitial(() -> cipherFactory.create()));
72+
}
73+
74+
private SivMode(final ThreadLocal<BlockCipher> threadLocalCipher) {
75+
this(threadLocalCipher, new CustomCtrComputer(threadLocalCipher::get));
76+
}
77+
78+
private SivMode(final ThreadLocal<BlockCipher> threadLocalCipher, final CtrComputer ctrComputer) {
7079
// Try using cipherFactory to check that the block size is valid.
7180
// We assume here that the block size will not vary across calls to .create().
72-
if (cipherFactory.create().getBlockSize() != 16) {
81+
if (threadLocalCipher.get().getBlockSize() != 16) {
7382
throw new IllegalArgumentException("cipherFactory must create BlockCipher objects with a 16-byte block size");
7483
}
7584

76-
this.threadLocalCipher = new ThreadLocal<BlockCipher>() {
77-
78-
@Override
79-
protected BlockCipher initialValue() {
80-
return cipherFactory.create();
81-
}
82-
83-
};
85+
this.threadLocalCipher = threadLocalCipher;
86+
this.ctrComputer = ctrComputer;
8487
}
8588

8689
/**
8790
* Creates {@link BlockCipher}s.
8891
*/
92+
@FunctionalInterface
8993
public interface BlockCipherFactory {
9094
BlockCipher create();
9195
}
9296

97+
/**
98+
* Performs CTR computations.
99+
*/
100+
@FunctionalInterface
101+
interface CtrComputer {
102+
byte[] computeCtr(byte[] input, byte[] key, final byte[] iv);
103+
}
104+
93105
/**
94106
* Convenience method, if you are using the javax.crypto API. This is just a wrapper for {@link #encrypt(byte[], byte[], byte[], byte[]...)}.
95107
*
@@ -131,10 +143,8 @@ public byte[] encrypt(byte[] ctrKey, byte[] macKey, byte[] plaintext, byte[]...
131143
}
132144

133145
assert plaintext.length + 15 < Integer.MAX_VALUE;
134-
final int numBlocks = (plaintext.length + 15) / 16;
135146
final byte[] iv = s2v(macKey, plaintext, associatedData);
136-
final byte[] keystream = generateKeyStream(ctrKey, iv, numBlocks);
137-
final byte[] ciphertext = xor(plaintext, keystream);
147+
final byte[] ciphertext = computeCtr(plaintext, ctrKey, iv);
138148

139149
// concat IV + ciphertext:
140150
final byte[] result = new byte[iv.length + ciphertext.length];
@@ -191,9 +201,7 @@ public byte[] decrypt(byte[] ctrKey, byte[] macKey, byte[] ciphertext, byte[]...
191201

192202
assert actualCiphertext.length == ciphertext.length - 16;
193203
assert actualCiphertext.length + 15 < Integer.MAX_VALUE;
194-
final int numBlocks = (actualCiphertext.length + 15) / 16;
195-
final byte[] keystream = generateKeyStream(ctrKey, iv, numBlocks);
196-
final byte[] plaintext = xor(actualCiphertext, keystream);
204+
final byte[] plaintext = computeCtr(actualCiphertext, ctrKey, iv);
197205
final byte[] control = s2v(macKey, plaintext, associatedData);
198206

199207
// time-constant comparison (taken from MessageDigest.isEqual in JDK8)
@@ -209,26 +217,14 @@ public byte[] decrypt(byte[] ctrKey, byte[] macKey, byte[] ciphertext, byte[]...
209217
throw new UnauthenticCiphertextException("authentication in SIV decryption failed");
210218
}
211219
}
212-
213-
byte[] generateKeyStream(byte[] ctrKey, byte[] iv, int numBlocks) {
214-
final byte[] keystream = new byte[numBlocks * 16];
215-
220+
221+
byte[] computeCtr(byte[] input, byte[] key, final byte[] iv) {
216222
// clear out the 31st and 63rd (rightmost) bit:
217-
final byte[] ctr = Arrays.copyOf(iv, 16);
218-
ctr[8] = (byte) (ctr[8] & 0x7F);
219-
ctr[12] = (byte) (ctr[12] & 0x7F);
220-
final ByteBuffer ctrBuf = ByteBuffer.wrap(ctr);
221-
final long initialCtrVal = ctrBuf.getLong(8);
222-
223-
final BlockCipher cipher = threadLocalCipher.get();
224-
cipher.init(true, new KeyParameter(ctrKey));
225-
for (int i = 0; i < numBlocks; i++) {
226-
ctrBuf.putLong(8, initialCtrVal + i);
227-
cipher.processBlock(ctr, 0, keystream, i * 16);
228-
cipher.reset();
229-
}
230-
231-
return keystream;
223+
final byte[] adjustedIv = Arrays.copyOf(iv, 16);
224+
adjustedIv[8] = (byte) (adjustedIv[8] & 0x7F);
225+
adjustedIv[12] = (byte) (adjustedIv[12] & 0x7F);
226+
227+
return ctrComputer.computeCtr(input, key, adjustedIv);
232228
}
233229

234230
// Visible for testing, throws IllegalArgumentException if key is not accepted by CMac#init(CipherParameters)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.cryptomator.siv;
2+
3+
import org.bouncycastle.crypto.BlockCipher;
4+
import org.bouncycastle.crypto.engines.AESLightEngine;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class CustomCtrComputerTest {
9+
10+
private BlockCipher supplyBlockCipher() {
11+
return new AESLightEngine();
12+
}
13+
14+
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.1
15+
@Test
16+
public void testComputeCtr1() {
17+
byte[] ctrKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
18+
(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
19+
(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
20+
(byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff};
21+
22+
byte[] ctr = {(byte) 0x85, (byte) 0x63, (byte) 0x2d, (byte) 0x07, //
23+
(byte) 0xc6, (byte) 0xe8, (byte) 0xf3, (byte) 0x7f, //
24+
(byte) 0x15, (byte) 0x0a, (byte) 0xcd, (byte) 0x32, //
25+
(byte) 0x0a, (byte) 0x2e, (byte) 0xcc, (byte) 0x93};
26+
27+
byte[] expected = {(byte) 0x51, (byte) 0xe2, (byte) 0x18, (byte) 0xd2, //
28+
(byte) 0xc5, (byte) 0xa2, (byte) 0xab, (byte) 0x8c, //
29+
(byte) 0x43, (byte) 0x45, (byte) 0xc4, (byte) 0xa6, //
30+
(byte) 0x23, (byte) 0xb2, (byte) 0xf0, (byte) 0x8f};
31+
32+
byte[] result = new CustomCtrComputer(this::supplyBlockCipher).computeCtr(new byte[16], ctrKey, ctr);
33+
Assertions.assertArrayEquals(expected, result);
34+
}
35+
36+
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.2
37+
@Test
38+
public void testComputeCtr2() {
39+
final byte[] ctrKey = {(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, //
40+
(byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, //
41+
(byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, //
42+
(byte) 0x4c, (byte) 0x4d, (byte) 0x4e, (byte) 0x4f};
43+
44+
final byte[] ctr = {(byte) 0x7b, (byte) 0xdb, (byte) 0x6e, (byte) 0x3b, //
45+
(byte) 0x43, (byte) 0x26, (byte) 0x67, (byte) 0xeb, //
46+
(byte) 0x06, (byte) 0xf4, (byte) 0xd1, (byte) 0x4b, //
47+
(byte) 0x7f, (byte) 0x2f, (byte) 0xbd, (byte) 0x0f};
48+
49+
final byte[] expected = {(byte) 0xbf, (byte) 0xf8, (byte) 0x66, (byte) 0x5c, //
50+
(byte) 0xfd, (byte) 0xd7, (byte) 0x33, (byte) 0x63, //
51+
(byte) 0x55, (byte) 0x0f, (byte) 0x74, (byte) 0x00, //
52+
(byte) 0xe8, (byte) 0xf9, (byte) 0xd3, (byte) 0x76, //
53+
(byte) 0xb2, (byte) 0xc9, (byte) 0x08, (byte) 0x8e, //
54+
(byte) 0x71, (byte) 0x3b, (byte) 0x86, (byte) 0x17, //
55+
(byte) 0xd8, (byte) 0x83, (byte) 0x92, (byte) 0x26, //
56+
(byte) 0xd9, (byte) 0xf8, (byte) 0x81, (byte) 0x59, //
57+
(byte) 0x9e, (byte) 0x44, (byte) 0xd8, (byte) 0x27, //
58+
(byte) 0x23, (byte) 0x49, (byte) 0x49, (byte) 0xbc, //
59+
(byte) 0x1b, (byte) 0x12, (byte) 0x34, (byte) 0x8e, //
60+
(byte) 0xbc, (byte) 0x19, (byte) 0x5e, (byte) 0xc7};
61+
62+
byte[] result = new CustomCtrComputer(this::supplyBlockCipher).computeCtr(new byte[48], ctrKey, ctr);
63+
Assertions.assertArrayEquals(expected, result);
64+
}
65+
66+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.cryptomator.siv;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
6+
public class JceAesCtrComputerTest {
7+
8+
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.1
9+
@Test
10+
public void testComputeCtr1() {
11+
byte[] ctrKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
12+
(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
13+
(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
14+
(byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff};
15+
16+
byte[] ctr = {(byte) 0x85, (byte) 0x63, (byte) 0x2d, (byte) 0x07, //
17+
(byte) 0xc6, (byte) 0xe8, (byte) 0xf3, (byte) 0x7f, //
18+
(byte) 0x15, (byte) 0x0a, (byte) 0xcd, (byte) 0x32, //
19+
(byte) 0x0a, (byte) 0x2e, (byte) 0xcc, (byte) 0x93};
20+
21+
byte[] expected = {(byte) 0x51, (byte) 0xe2, (byte) 0x18, (byte) 0xd2, //
22+
(byte) 0xc5, (byte) 0xa2, (byte) 0xab, (byte) 0x8c, //
23+
(byte) 0x43, (byte) 0x45, (byte) 0xc4, (byte) 0xa6, //
24+
(byte) 0x23, (byte) 0xb2, (byte) 0xf0, (byte) 0x8f};
25+
26+
byte[] result = new JceAesCtrComputer(null).computeCtr(new byte[16], ctrKey, ctr);
27+
Assertions.assertArrayEquals(expected, result);
28+
}
29+
30+
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.2
31+
@Test
32+
public void testComputeCtr2() {
33+
final byte[] ctrKey = {(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, //
34+
(byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, //
35+
(byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, //
36+
(byte) 0x4c, (byte) 0x4d, (byte) 0x4e, (byte) 0x4f};
37+
38+
final byte[] ctr = {(byte) 0x7b, (byte) 0xdb, (byte) 0x6e, (byte) 0x3b, //
39+
(byte) 0x43, (byte) 0x26, (byte) 0x67, (byte) 0xeb, //
40+
(byte) 0x06, (byte) 0xf4, (byte) 0xd1, (byte) 0x4b, //
41+
(byte) 0x7f, (byte) 0x2f, (byte) 0xbd, (byte) 0x0f};
42+
43+
final byte[] expected = {(byte) 0xbf, (byte) 0xf8, (byte) 0x66, (byte) 0x5c, //
44+
(byte) 0xfd, (byte) 0xd7, (byte) 0x33, (byte) 0x63, //
45+
(byte) 0x55, (byte) 0x0f, (byte) 0x74, (byte) 0x00, //
46+
(byte) 0xe8, (byte) 0xf9, (byte) 0xd3, (byte) 0x76, //
47+
(byte) 0xb2, (byte) 0xc9, (byte) 0x08, (byte) 0x8e, //
48+
(byte) 0x71, (byte) 0x3b, (byte) 0x86, (byte) 0x17, //
49+
(byte) 0xd8, (byte) 0x83, (byte) 0x92, (byte) 0x26, //
50+
(byte) 0xd9, (byte) 0xf8, (byte) 0x81, (byte) 0x59, //
51+
(byte) 0x9e, (byte) 0x44, (byte) 0xd8, (byte) 0x27, //
52+
(byte) 0x23, (byte) 0x49, (byte) 0x49, (byte) 0xbc, //
53+
(byte) 0x1b, (byte) 0x12, (byte) 0x34, (byte) 0x8e, //
54+
(byte) 0xbc, (byte) 0x19, (byte) 0x5e, (byte) 0xc7};
55+
56+
byte[] result = new JceAesCtrComputer(null).computeCtr(new byte[48], ctrKey, ctr);
57+
Assertions.assertArrayEquals(expected, result);
58+
}
59+
60+
}

src/test/java/org/cryptomator/siv/SivModeTest.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Sebastian Stenzel - initial API and implementation
99
******************************************************************************/
1010

11+
import org.bouncycastle.crypto.engines.AESLightEngine;
1112
import org.bouncycastle.crypto.engines.DESEngine;
1213
import org.cryptomator.siv.SivMode.BlockCipherFactory;
1314
import org.hamcrest.CoreMatchers;
@@ -148,7 +149,7 @@ public void testDecryptAssociatedDataLimit() {
148149

149150
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.1
150151
@Test
151-
public void testGenerateKeyStream1() {
152+
public void testComputeCtr1() {
152153
final byte[] ctrKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
153154
(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
154155
(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
@@ -164,16 +165,19 @@ public void testGenerateKeyStream1() {
164165
(byte) 0x43, (byte) 0x45, (byte) 0xc4, (byte) 0xa6, //
165166
(byte) 0x23, (byte) 0xb2, (byte) 0xf0, (byte) 0x8f};
166167

167-
final byte[] result = new SivMode().generateKeyStream(ctrKey, ctr, 1);
168+
final byte[] result = new SivMode().computeCtr(new byte[16], ctrKey, ctr);
168169
Assertions.assertArrayEquals(expected, result);
169170

170-
final byte[] resultProvider = new SivMode(getSunJceProvider()).generateKeyStream(ctrKey, ctr, 1);
171-
Assertions.assertArrayEquals(expected, resultProvider);
171+
final byte[] sunJceResult = new SivMode(getSunJceProvider()).computeCtr(new byte[16], ctrKey, ctr);
172+
Assertions.assertArrayEquals(expected, sunJceResult);
173+
174+
final byte[] bcResult = new SivMode(AESLightEngine::new).computeCtr(new byte[16], ctrKey, ctr);
175+
Assertions.assertArrayEquals(expected, bcResult);
172176
}
173177

174178
// CTR-AES https://tools.ietf.org/html/rfc5297#appendix-A.2
175179
@Test
176-
public void testGenerateKeyStream2() {
180+
public void testComputeCtr2() {
177181
final byte[] ctrKey = {(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, //
178182
(byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, //
179183
(byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, //
@@ -197,7 +201,7 @@ public void testGenerateKeyStream2() {
197201
(byte) 0x1b, (byte) 0x12, (byte) 0x34, (byte) 0x8e, //
198202
(byte) 0xbc, (byte) 0x19, (byte) 0x5e, (byte) 0xc7};
199203

200-
final byte[] result = new SivMode().generateKeyStream(ctrKey, ctr, 3);
204+
final byte[] result = new SivMode().computeCtr(new byte[48], ctrKey, ctr);
201205
Assertions.assertArrayEquals(expected, result);
202206
}
203207

0 commit comments

Comments
 (0)