Skip to content

Commit 4546454

Browse files
fixed findings from code review
[deploy]
1 parent afd2cd2 commit 4546454

File tree

9 files changed

+112
-43
lines changed

9 files changed

+112
-43
lines changed

CHANGELOG.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased](https://github.com/cryptomator/siv-mode/compare/1.6.1...HEAD)
99

1010
### Added
11-
- new lowlevel API:
12-
* `new SivEngine(key).encrypt(plaintext, associatedData...)`
13-
* `new SivEngine(key).decrypt(plaintext, associatedData...)`
11+
- new low-level API:
12+
* `new SivEngine(key).encrypt(plaintext, associatedData...)`
13+
* `new SivEngine(key).decrypt(plaintext, associatedData...)`
1414
- implement JCA `Cipher` SPI:
15-
```
16-
Cipher siv = Cipher.getInstance("AES/SIV/NoPadding");
17-
siv.init(Cipher.ENCRYPT_MODE, key);
18-
siv.updateAAD(aad1);
19-
siv.updateAAD(aad2);
20-
byte[] ciphertext = siv.doFinal(plaintext);
21-
```
22-
15+
```java
16+
Cipher siv = Cipher.getInstance("AES/SIV/NoPadding");
17+
siv.init(Cipher.ENCRYPT_MODE, key);
18+
siv.updateAAD(aad1);
19+
siv.updateAAD(aad2);
20+
byte[] ciphertext = siv.doFinal(plaintext);
21+
```
22+
2323
### Changed
2424
- remove dependencies on BouncyCastle and Jetbrains Annotations
2525
- simplify build by removing `maven-shade-plugin`
2626
- update test dependencies
2727
- update build plugins
2828

2929
### Deprecated
30-
- old lowlevel API:
31-
* `new SivMode().encrypt(key, plaintext, associatedData...)`
32-
* `new SivMode().encrypt(ctrKey, macKey, plaintext, associatedData...)`
33-
* `new SivMode().decrypt(key, ciphertext, associatedData...)`
34-
* `new SivMode().decrypt(ctrKey, macKey, ciphertext, associatedData...)`
35-
36-
30+
- old low-level API:
31+
* `new SivMode().encrypt(key, plaintext, associatedData...)`
32+
* `new SivMode().encrypt(ctrKey, macKey, plaintext, associatedData...)`
33+
* `new SivMode().decrypt(key, ciphertext, associatedData...)`
34+
* `new SivMode().decrypt(ctrKey, macKey, ciphertext, associatedData...)`
3735

3836
## [1.6.1](https://github.com/cryptomator/siv-mode/compare/1.6.0...1.6.1)
3937

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
- No dependencies
1212
- Passes official RFC 5297 test vectors
1313
- Constant time authentication
14-
- Thread-safe
1514
- [Fast](https://github.com/cryptomator/siv-mode/issues/15)
1615
- Requires JDK 8+ or Android API Level 24+ (since version 1.4.0)
1716

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,14 @@ protected void engineInit(Key key, AlgorithmParameterSpec params) throws Invalid
5858
try {
5959
// L = AES_encrypt(K, const_Zero)
6060
encryptBlock(cipher, L, L);
61-
this.k1 = dbl(L).clone();
62-
this.k2 = dbl(L).clone();
61+
this.k1 = dbl(L.clone());
62+
this.k2 = dbl(k1.clone());
6363
} finally {
6464
Arrays.fill(L, (byte) 0);
6565
}
66+
67+
// reset state
68+
engineReset();
6669
}
6770

6871
@Override

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,34 +123,54 @@ protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] o
123123

124124
@Override
125125
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException {
126-
byte[] output = new byte[engineGetOutputSize(inputBuffer.length + inputLen)];
126+
int outputSize = engineGetOutputSize(inputBuffer.length + inputLen);
127+
if (outputSize < 0) {
128+
throw new IllegalBlockSizeException("Ciphertext too short (must be at least 16 bytes including SIV tag)");
129+
}
130+
byte[] output = new byte[outputSize];
127131
try {
128132
engineDoFinal(input, inputOffset, inputLen, output, 0);
129133
} catch (ShortBufferException e) {
134+
// outputSize was calculated before, so this should never happen
130135
throw new IllegalStateException(e);
131136
}
132137
return output;
133138
}
134139

135140
@Override
136141
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
142+
int outputSize = engineGetOutputSize(inputBuffer.length + inputLen);
143+
if (outputSize < 0) {
144+
throw new IllegalBlockSizeException("Ciphertext too short (must be at least 16 bytes including SIV tag)");
145+
}
137146
int availableSpace = output.length - outputOffset;
138-
if (availableSpace < engineGetOutputSize(inputBuffer.length + inputLen)) {
147+
if (availableSpace < outputSize) {
139148
throw new ShortBufferException();
140149
}
141150
engineUpdate(input, inputOffset, inputLen);
142151

152+
int resultLength;
143153
SivEngine siv = new SivEngine(this.key);
144154
byte[][] aad = this.aad.toArray(new byte[this.aad.size()][]);
145155
if (this.opmode == Cipher.ENCRYPT_MODE || this.opmode == Cipher.WRAP_MODE) {
146-
return siv.encrypt(inputBuffer, output, outputOffset, aad);
156+
resultLength = siv.encrypt(inputBuffer, output, outputOffset, aad);
147157
} else if (this.opmode == Cipher.DECRYPT_MODE || this.opmode == Cipher.UNWRAP_MODE) {
148158
// for security reasons we can't write into output directly before checking integrity:
149-
byte[] plaintext = siv.decrypt(inputBuffer, aad);
150-
System.arraycopy(plaintext, 0, output, outputOffset, plaintext.length);
151-
return plaintext.length;
159+
byte[] plaintext = new byte[0];
160+
try {
161+
plaintext = siv.decrypt(inputBuffer, aad);
162+
System.arraycopy(plaintext, 0, output, outputOffset, plaintext.length);
163+
resultLength = plaintext.length;
164+
} finally {
165+
Arrays.fill(plaintext, (byte) 0x00);
166+
}
152167
} else {
153168
throw new IllegalStateException("Invalid opmode " + this.opmode);
154169
}
170+
171+
// reset internal state:
172+
this.inputBuffer = EMPTY;
173+
this.aad.clear();
174+
return resultLength;
155175
}
156176
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ public int encrypt(byte[] plaintext, byte[] output, int outputOffset, byte[]...
113113
}
114114
byte[] iv = s2v(plaintext, associatedData);
115115
assert iv.length == IV_LENGTH;
116-
System.arraycopy(iv, 0, output, 0, IV_LENGTH);
117-
return IV_LENGTH + computeCtr(plaintext, 0, plaintext.length, iv, 0, IV_LENGTH, output, IV_LENGTH);
116+
System.arraycopy(iv, 0, output, outputOffset, IV_LENGTH);
117+
return IV_LENGTH + computeCtr(plaintext, 0, plaintext.length, iv, 0, IV_LENGTH, output, outputOffset + IV_LENGTH);
118118
}
119119

120120
/**
@@ -213,7 +213,7 @@ byte[] s2v(byte[] plaintext, byte[]... associatedData) throws IllegalArgumentExc
213213
return cmac.doFinal(end);
214214
} else {
215215
// T = dbl(D) xor pad(Sn)
216-
byte[] t = xor(dbl(d), pad(plaintext));
216+
byte[] t = xor(dbl(d), pad(plaintext, 16));
217217
return cmac.doFinal(t);
218218
}
219219
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public class Utils {
1212
private static final byte DOUBLING_CONST = (byte) 0x87;
1313

1414
// First bit 1, following bits 0.
15-
static byte[] pad(byte[] in) {
16-
final byte[] result = Arrays.copyOf(in, 16);
15+
static byte[] pad(byte[] in, int desiredLength) {
16+
final byte[] result = Arrays.copyOf(in, desiredLength);
1717
result[in.length] = (byte) 0x80;
1818
return result;
1919
}

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

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package org.cryptomator.siv;
22

3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
35
import org.junit.jupiter.params.ParameterizedTest;
46
import org.junit.jupiter.params.converter.ArgumentConversionException;
57
import org.junit.jupiter.params.converter.ConvertWith;
68
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
79
import org.junit.jupiter.params.provider.CsvSource;
810

11+
import java.nio.charset.StandardCharsets;
12+
913
import static org.junit.jupiter.api.Assertions.assertEquals;
1014

1115
class CMacTest {
@@ -22,19 +26,26 @@ protected Object convert(Object source, Class<?> targetType) throws ArgumentConv
2226
throw new ArgumentConversionException("Source must be a String");
2327
}
2428
String hex = (String) source;
25-
if (hex.isEmpty()) {
26-
return new byte[0];
27-
}
28-
int len = hex.length();
29-
byte[] data = new byte[len / 2];
30-
for (int i = 0; i < len; i += 2) {
31-
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
32-
+ Character.digit(hex.charAt(i + 1), 16));
33-
}
34-
return data;
29+
return hexToBytes(hex);
3530
}
3631
}
3732

33+
/**
34+
* Convert hex string to byte array
35+
*/
36+
private static byte[] hexToBytes(String hex) {
37+
if (hex.isEmpty()) {
38+
return new byte[0];
39+
}
40+
int len = hex.length();
41+
byte[] data = new byte[len / 2];
42+
for (int i = 0; i < len; i += 2) {
43+
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
44+
+ Character.digit(hex.charAt(i + 1), 16));
45+
}
46+
return data;
47+
}
48+
3849
/**
3950
* Convert byte array to hex string
4051
*/
@@ -84,4 +95,19 @@ void testNistVectors(String testName,
8495
assertEquals(expectedHex, bytesToHex(result), testName + " failed");
8596
}
8697

98+
@Test
99+
public void testReset() {
100+
byte[] key = hexToBytes("2b7e151628aed2a6abf7158809cf4f3c");
101+
byte[] incorrectInput = "oops, never ment to update with this data!".getBytes(StandardCharsets.UTF_8);
102+
byte[] correctInput = hexToBytes("6bc1bee22e409f96e93d7e117393172a");
103+
104+
CMac cmac = CMac.create(key);
105+
cmac.engineUpdate(incorrectInput, 0, incorrectInput.length);
106+
cmac.engineReset();
107+
cmac.engineUpdate(correctInput, 0, correctInput.length);
108+
byte[] tag = cmac.engineDoFinal();
109+
110+
assertEquals("070a16b46b4d4144f79bdd9dd04a287c", bytesToHex(tag));
111+
}
112+
87113
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void testEngineInitWithValidKeySize(int keysize) {
7878
@ValueSource(ints = {16, 24, 1337})
7979
public void testEngineInitWithInvalidKeySize(int keysize) {
8080
SecretKeySpec key = new SecretKeySpec(new byte[keysize], "AES");
81-
Assertions.assertThrows(InvalidKeyException.class,() -> cipher.engineInit(Cipher.ENCRYPT_MODE, key, null));
81+
Assertions.assertThrows(InvalidKeyException.class, () -> cipher.engineInit(Cipher.ENCRYPT_MODE, key, null));
8282
}
8383

8484
@Test
@@ -132,4 +132,19 @@ public void testWrapAndUnwrap() throws InvalidKeyException, ShortBufferException
132132
// compare:
133133
Assertions.assertEquals("helloworld!", new String(unwrapped, StandardCharsets.UTF_8));
134134
}
135+
136+
@Test
137+
public void testReuseCipher() throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
138+
byte[] plaintext = "helloworld".getBytes(StandardCharsets.UTF_8);
139+
cipher.engineInit(Cipher.ENCRYPT_MODE, key, null);
140+
byte[] encrypted1 = cipher.engineDoFinal(plaintext, 0, plaintext.length);
141+
byte[] encrypted2 = cipher.engineDoFinal(plaintext, 0, plaintext.length);
142+
cipher.engineInit(Cipher.DECRYPT_MODE, key, null);
143+
byte[] decrypted1 = cipher.engineDoFinal(encrypted1, 0, encrypted1.length);
144+
byte[] decrypted2 = cipher.engineDoFinal(encrypted2, 0, encrypted2.length);
145+
146+
Assertions.assertArrayEquals(encrypted1, encrypted2);
147+
Assertions.assertArrayEquals(plaintext, decrypted1);
148+
Assertions.assertArrayEquals(plaintext, decrypted2);
149+
}
135150
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ public void testSivEncrypt() {
169169
Assertions.assertArrayEquals(ciphertext, result);
170170
}
171171

172+
@Test
173+
public void testSivEncryptWithOffset() throws ShortBufferException {
174+
final byte[] result = new byte[ciphertext.length + 10];
175+
int encrypted = new SivEngine(key).encrypt(plaintext, result, 10, ad);
176+
Assertions.assertEquals(ciphertext.length, encrypted);
177+
Assertions.assertArrayEquals(ciphertext, Arrays.copyOfRange(result, 10, result.length));
178+
}
179+
172180
@Test
173181
public void testSivDecrypt() throws AEADBadTagException, IllegalBlockSizeException {
174182
final byte[] result = new SivEngine(key).decrypt(ciphertext, ad);

0 commit comments

Comments
 (0)