Skip to content

Commit 59a2551

Browse files
convenience method for sole 256, 384, 512 bit key
1 parent 5529c38 commit 59a2551

File tree

2 files changed

+97
-3
lines changed

2 files changed

+97
-3
lines changed

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,33 @@ interface CtrComputer {
100100
byte[] computeCtr(byte[] input, byte[] key, final byte[] iv);
101101
}
102102

103+
/**
104+
* Convenience method using a single 256, 384, or 512 bits key. This is just a wrapper for {@link #encrypt(byte[], byte[], byte[], byte[]...)}.
105+
* @param key Combined key, which is split in half.
106+
* @param plaintext Your plaintext, which shall be encrypted.
107+
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
108+
* @return IV + Ciphertext as a concatenated byte array.
109+
*/
110+
public byte[] encrypt(SecretKey key, byte[] plaintext, byte[]... associatedData) {
111+
final byte[] keyBytes = key.getEncoded();
112+
if (keyBytes.length != 64 && keyBytes.length != 48 && keyBytes.length != 32) {
113+
throw new IllegalArgumentException("Key length must be 256, 384, or 512 bits.");
114+
}
115+
final int subkeyLen = keyBytes.length / 2;
116+
assert subkeyLen == 32 || subkeyLen == 24 || subkeyLen == 16;
117+
final byte[] macKey = new byte[subkeyLen];
118+
final byte[] ctrKey = new byte[subkeyLen];
119+
try {
120+
System.arraycopy(keyBytes, 0, macKey, 0, macKey.length); // K1 = leftmost(K, len(K)/2);
121+
System.arraycopy(keyBytes, macKey.length, ctrKey, 0, ctrKey.length); // K2 = rightmost(K, len(K)/2);
122+
return encrypt(ctrKey, macKey, plaintext, associatedData);
123+
} finally {
124+
Arrays.fill(macKey, (byte) 0);
125+
Arrays.fill(ctrKey, (byte) 0);
126+
Arrays.fill(keyBytes, (byte) 0);
127+
}
128+
}
129+
103130
/**
104131
* Convenience method, if you are using the javax.crypto API. This is just a wrapper for {@link #encrypt(byte[], byte[], byte[], byte[]...)}.
105132
*
@@ -150,6 +177,36 @@ public byte[] encrypt(byte[] ctrKey, byte[] macKey, byte[] plaintext, byte[]...
150177
return result;
151178
}
152179

180+
/**
181+
* Convenience method using a single 256, 384, or 512 bits key. This is just a wrapper for {@link #decrypt(byte[], byte[], byte[], byte[]...)}.
182+
* @param key Combined key, which is split in half.
183+
* @param ciphertext Your cipehrtext, which shall be decrypted.
184+
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
185+
* @return Plaintext byte array.
186+
* @throws IllegalArgumentException If keys are invalid.
187+
* @throws UnauthenticCiphertextException If the authentication failed, e.g. because ciphertext and/or associatedData are corrupted.
188+
* @throws IllegalBlockSizeException If the provided ciphertext is of invalid length.
189+
*/
190+
public byte[] decrypt(SecretKey key, byte[] ciphertext, byte[]... associatedData) throws UnauthenticCiphertextException, IllegalBlockSizeException {
191+
final byte[] keyBytes = key.getEncoded();
192+
if (keyBytes.length != 64 && keyBytes.length != 48 && keyBytes.length != 32) {
193+
throw new IllegalArgumentException("Key length must be 256, 384, or 512 bits.");
194+
}
195+
final int subkeyLen = keyBytes.length / 2;
196+
assert subkeyLen == 32 || subkeyLen == 24 || subkeyLen == 16;
197+
final byte[] macKey = new byte[subkeyLen];
198+
final byte[] ctrKey = new byte[subkeyLen];
199+
try {
200+
System.arraycopy(keyBytes, 0, macKey, 0, macKey.length); // K1 = leftmost(K, len(K)/2);
201+
System.arraycopy(keyBytes, macKey.length, ctrKey, 0, ctrKey.length); // K2 = rightmost(K, len(K)/2);
202+
return decrypt(ctrKey, macKey, ciphertext, associatedData);
203+
} finally {
204+
Arrays.fill(macKey, (byte) 0);
205+
Arrays.fill(ctrKey, (byte) 0);
206+
Arrays.fill(keyBytes, (byte) 0);
207+
}
208+
}
209+
153210
/**
154211
* Convenience method, if you are using the javax.crypto API. This is just a wrapper for {@link #decrypt(byte[], byte[], byte[], byte[]...)}.
155212
*

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import org.junit.jupiter.api.DynamicTest;
1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.TestFactory;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
2123
import org.mockito.Mockito;
2224

2325
import javax.crypto.IllegalBlockSizeException;
@@ -66,6 +68,17 @@ public void testEncryptWithInvalidKey2() {
6668
});
6769
}
6870

71+
@Test
72+
public void testEncryptWithInvalidKey3() {
73+
SecretKey key = Mockito.mock(SecretKey.class);
74+
Mockito.when(key.getEncoded()).thenReturn(new byte[13]);
75+
76+
SivMode sivMode = new SivMode();
77+
Assertions.assertThrows(IllegalArgumentException.class, () -> {
78+
sivMode.encrypt(key, new byte[10]);
79+
});
80+
}
81+
6982
@Test
7083
public void testInvalidCipher1() {
7184
BlockCipherFactory factory = () -> null;
@@ -111,6 +124,17 @@ public void testDecryptWithInvalidKey2() {
111124
});
112125
}
113126

127+
@Test
128+
public void testDecryptWithInvalidKey3() {
129+
SecretKey key = Mockito.mock(SecretKey.class);
130+
Mockito.when(key.getEncoded()).thenReturn(new byte[13]);
131+
132+
SivMode sivMode = new SivMode();
133+
Assertions.assertThrows(IllegalArgumentException.class, () -> {
134+
sivMode.decrypt(key, new byte[10]);
135+
});
136+
}
137+
114138
@Test
115139
public void testDecryptWithInvalidBlockSize() {
116140
final byte[] dummyKey = new byte[16];
@@ -437,9 +461,10 @@ public void testNonceBasedAuthenticatedEncryption() {
437461
Assertions.assertArrayEquals(expected, result);
438462
}
439463

440-
@Test
441-
public void testEncryptionAndDecryptionUsingJavaxCryptoApi() throws UnauthenticCiphertextException, IllegalBlockSizeException {
442-
final byte[] dummyKey = new byte[16];
464+
@ParameterizedTest
465+
@ValueSource(ints = {16, 24, 32})
466+
public void testEncryptionAndDecryptionUsingJavaxCryptoApi(int keylen) throws UnauthenticCiphertextException, IllegalBlockSizeException {
467+
final byte[] dummyKey = new byte[keylen];
443468
final SecretKey ctrKey = new SecretKeySpec(dummyKey, "AES");
444469
final SecretKey macKey = new SecretKeySpec(dummyKey, "AES");
445470
final SivMode sivMode = new SivMode();
@@ -449,6 +474,18 @@ public void testEncryptionAndDecryptionUsingJavaxCryptoApi() throws UnauthenticC
449474
Assertions.assertArrayEquals(cleartext, decrypted);
450475
}
451476

477+
@ParameterizedTest
478+
@ValueSource(ints = {32, 48, 64})
479+
public void testEncryptionAndDecryptionUsingSingleJavaxCryptoApi(int keylen) throws UnauthenticCiphertextException, IllegalBlockSizeException {
480+
final byte[] dummyKey = new byte[keylen];
481+
final SecretKey key = new SecretKeySpec(dummyKey, "AES");
482+
final SivMode sivMode = new SivMode();
483+
final byte[] cleartext = "hello world".getBytes();
484+
final byte[] ciphertext = sivMode.encrypt(key, cleartext);
485+
final byte[] decrypted = sivMode.decrypt(key, ciphertext);
486+
Assertions.assertArrayEquals(cleartext, decrypted);
487+
}
488+
452489
@Test
453490
public void testShiftLeft() {
454491
final byte[] output = new byte[4];

0 commit comments

Comments
 (0)