Skip to content

Commit 3947c0b

Browse files
Using JCE implementation as a default instead of BouncyCastle's AESFastEngine
1 parent 3f2a279 commit 3947c0b

File tree

4 files changed

+281
-175
lines changed

4 files changed

+281
-175
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.cryptomator.siv;
2+
/*******************************************************************************
3+
* Copyright (c) 2016 Sebastian Stenzel
4+
* This file is licensed under the terms of the MIT license.
5+
* See the LICENSE.txt file for more info.
6+
*
7+
* Contributors:
8+
* Sebastian Stenzel - initial API and implementation
9+
******************************************************************************/
10+
11+
import java.nio.ByteBuffer;
12+
import java.security.InvalidKeyException;
13+
import java.security.Key;
14+
import java.security.NoSuchAlgorithmException;
15+
16+
import javax.crypto.Cipher;
17+
import javax.crypto.NoSuchPaddingException;
18+
import javax.crypto.ShortBufferException;
19+
import javax.crypto.spec.SecretKeySpec;
20+
21+
import org.bouncycastle.crypto.BlockCipher;
22+
import org.bouncycastle.crypto.CipherParameters;
23+
import org.bouncycastle.crypto.DataLengthException;
24+
import org.bouncycastle.crypto.params.KeyParameter;
25+
26+
/**
27+
* Adapter class between BouncyCastle's {@link BlockCipher} and JCE's {@link Cipher} API.
28+
*/
29+
class JceAesBlockCipher implements BlockCipher {
30+
31+
private static final String ALG_NAME = "AES";
32+
private static final String KEY_DESIGNATION = "AES";
33+
private static final String JCE_CIPHER_NAME = "AES/ECB/NoPadding";
34+
35+
private final Cipher cipher;
36+
private Key key;
37+
private int opmode;
38+
39+
public JceAesBlockCipher() {
40+
try {
41+
this.cipher = Cipher.getInstance(JCE_CIPHER_NAME); // defaults to SunJCE but allows to configure different providers
42+
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
43+
throw new IllegalStateException("Every implementation of the Java platform is required to support AES/ECB/NoPadding.");
44+
}
45+
}
46+
47+
@Override
48+
public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException {
49+
if (params instanceof KeyParameter) {
50+
init(forEncryption, (KeyParameter) params);
51+
} else {
52+
throw new IllegalArgumentException("Invalid or missing parameter of type KeyParameter.");
53+
}
54+
}
55+
56+
private void init(boolean forEncryption, KeyParameter keyParam) throws IllegalArgumentException {
57+
this.key = new SecretKeySpec(keyParam.getKey(), KEY_DESIGNATION);
58+
this.opmode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
59+
try {
60+
cipher.init(opmode, key);
61+
} catch (InvalidKeyException e) {
62+
throw new IllegalArgumentException("Invalid key.", e);
63+
}
64+
}
65+
66+
@Override
67+
public String getAlgorithmName() {
68+
return ALG_NAME;
69+
}
70+
71+
@Override
72+
public int getBlockSize() {
73+
return cipher.getBlockSize();
74+
}
75+
76+
@Override
77+
public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException {
78+
if (in.length - inOff < getBlockSize()) {
79+
throw new DataLengthException("Insufficient data in 'in'.");
80+
}
81+
ByteBuffer inBuf = ByteBuffer.wrap(in, inOff, getBlockSize());
82+
ByteBuffer outBuf = ByteBuffer.wrap(out, outOff, out.length - outOff);
83+
try {
84+
return cipher.update(inBuf, outBuf);
85+
} catch (ShortBufferException e) {
86+
throw new DataLengthException("Insufficient space in 'out'.");
87+
}
88+
}
89+
90+
@Override
91+
public void reset() {
92+
if (key == null) {
93+
return; // no-op if init has not been called yet.
94+
}
95+
try {
96+
cipher.init(opmode, key);
97+
} catch (InvalidKeyException e) {
98+
throw new IllegalStateException("cipher.init(...) already invoked successfully earlier with same parameters.");
99+
}
100+
}
101+
102+
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.bouncycastle.crypto.BlockCipher;
1919
import org.bouncycastle.crypto.CipherParameters;
2020
import org.bouncycastle.crypto.Mac;
21-
import org.bouncycastle.crypto.engines.AESFastEngine;
2221
import org.bouncycastle.crypto.engines.AESLightEngine;
2322
import org.bouncycastle.crypto.macs.CMac;
2423
import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
@@ -35,7 +34,7 @@ public final class SivMode {
3534
private final BlockCipherFactory cipherFactory;
3635

3736
/**
38-
* Creates an AES-SIV instance using BouncyCastle's {@link AESFastEngine}, which should normally be the best choice.<br>
37+
* Creates an AES-SIV instance using JCE's cipher implementation, which should normally be the best choice.<br>
3938
*
4039
* For embedded systems, you might want to consider using {@link #SivMode(BlockCipherFactory)} with {@link AESLightEngine} instead.
4140
*
@@ -46,7 +45,7 @@ public SivMode() {
4645

4746
@Override
4847
public BlockCipher create() {
49-
return new AESFastEngine();
48+
return new JceAesBlockCipher();
5049
}
5150

5251
});
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package org.cryptomator.siv;
2+
/*******************************************************************************
3+
* Copyright (c) 2016 Sebastian Stenzel
4+
* This file is licensed under the terms of the MIT license.
5+
* See the LICENSE.txt file for more info.
6+
*
7+
* Contributors:
8+
* Sebastian Stenzel - initial API and implementation
9+
******************************************************************************/
10+
11+
import org.bouncycastle.crypto.DataLengthException;
12+
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
13+
import org.bouncycastle.crypto.params.KeyParameter;
14+
import org.junit.Assert;
15+
import org.junit.Rule;
16+
import org.junit.Test;
17+
import org.junit.rules.ExpectedException;
18+
19+
public class JceAesBlockCipherTest {
20+
21+
@Rule
22+
public final ExpectedException thrown = ExpectedException.none();
23+
24+
@Test
25+
public void testInitWithNullParam() {
26+
JceAesBlockCipher cipher = new JceAesBlockCipher();
27+
thrown.expect(IllegalArgumentException.class);
28+
thrown.expectMessage("missing parameter of type KeyParameter");
29+
cipher.init(true, null);
30+
}
31+
32+
@Test
33+
public void testInitWithMissingKey() {
34+
JceAesBlockCipher cipher = new JceAesBlockCipher();
35+
thrown.expect(IllegalArgumentException.class);
36+
thrown.expectMessage("missing parameter of type KeyParameter");
37+
cipher.init(true, new AsymmetricKeyParameter(true));
38+
}
39+
40+
@Test
41+
public void testInitWithInvalidKey() {
42+
JceAesBlockCipher cipher = new JceAesBlockCipher();
43+
thrown.expect(IllegalArgumentException.class);
44+
thrown.expectMessage("Invalid key");
45+
cipher.init(true, new KeyParameter(new byte[7]));
46+
}
47+
48+
@Test
49+
public void testInitForEncryption() {
50+
JceAesBlockCipher cipher = new JceAesBlockCipher();
51+
cipher.init(true, new KeyParameter(new byte[16]));
52+
}
53+
54+
@Test
55+
public void testInitForDecryption() {
56+
JceAesBlockCipher cipher = new JceAesBlockCipher();
57+
cipher.init(false, new KeyParameter(new byte[16]));
58+
}
59+
60+
@Test
61+
public void testGetAlgorithmName() {
62+
JceAesBlockCipher cipher = new JceAesBlockCipher();
63+
Assert.assertEquals("AES", cipher.getAlgorithmName());
64+
}
65+
66+
@Test
67+
public void testGetBlockSize() {
68+
JceAesBlockCipher cipher = new JceAesBlockCipher();
69+
Assert.assertEquals(16, cipher.getBlockSize());
70+
}
71+
72+
@Test
73+
public void testProcessBlockWithUninitializedCipher() {
74+
JceAesBlockCipher cipher = new JceAesBlockCipher();
75+
thrown.expect(IllegalStateException.class);
76+
cipher.processBlock(new byte[16], 0, new byte[16], 0);
77+
}
78+
79+
@Test
80+
public void testProcessBlockWithUnsufficientInput() {
81+
JceAesBlockCipher cipher = new JceAesBlockCipher();
82+
cipher.init(true, new KeyParameter(new byte[16]));
83+
thrown.expect(DataLengthException.class);
84+
thrown.expectMessage("Insufficient data in 'in'");
85+
cipher.processBlock(new byte[16], 1, new byte[16], 0);
86+
}
87+
88+
@Test
89+
public void testProcessBlockWithUnsufficientOutput() {
90+
JceAesBlockCipher cipher = new JceAesBlockCipher();
91+
cipher.init(true, new KeyParameter(new byte[16]));
92+
thrown.expect(DataLengthException.class);
93+
thrown.expectMessage("Insufficient space in 'out'");
94+
cipher.processBlock(new byte[16], 0, new byte[16], 1);
95+
}
96+
97+
@Test
98+
public void testProcessBlock() {
99+
JceAesBlockCipher cipher = new JceAesBlockCipher();
100+
cipher.init(true, new KeyParameter(new byte[16]));
101+
byte[] ciphertext = new byte[16];
102+
int encrypted = cipher.processBlock(new byte[20], 0, ciphertext, 0);
103+
Assert.assertEquals(16, encrypted);
104+
105+
cipher.init(false, new KeyParameter(new byte[16]));
106+
byte[] cleartext = new byte[16];
107+
int decrypted = cipher.processBlock(ciphertext, 0, cleartext, 0);
108+
Assert.assertEquals(16, decrypted);
109+
Assert.assertArrayEquals(new byte[16], cleartext);
110+
}
111+
112+
@Test
113+
public void testResetBeforeInitDoesNotThrowExceptions() {
114+
JceAesBlockCipher cipher = new JceAesBlockCipher();
115+
cipher.reset();
116+
}
117+
118+
@Test
119+
public void testResetAfterInitDoesNotThrowExceptions() {
120+
JceAesBlockCipher cipher = new JceAesBlockCipher();
121+
cipher.init(true, new KeyParameter(new byte[16]));
122+
cipher.reset();
123+
}
124+
125+
}

0 commit comments

Comments
 (0)