Skip to content

Commit

Permalink
convenience method for sole 256, 384, 512 bit key
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Dec 5, 2024
1 parent 5529c38 commit 59a2551
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 3 deletions.
57 changes: 57 additions & 0 deletions src/main/java/org/cryptomator/siv/SivMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@ interface CtrComputer {
byte[] computeCtr(byte[] input, byte[] key, final byte[] iv);
}

/**
* Convenience method using a single 256, 384, or 512 bits key. This is just a wrapper for {@link #encrypt(byte[], byte[], byte[], byte[]...)}.
* @param key Combined key, which is split in half.
* @param plaintext Your plaintext, which shall be encrypted.
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
* @return IV + Ciphertext as a concatenated byte array.
*/
public byte[] encrypt(SecretKey key, byte[] plaintext, byte[]... associatedData) {
final byte[] keyBytes = key.getEncoded();
if (keyBytes.length != 64 && keyBytes.length != 48 && keyBytes.length != 32) {
throw new IllegalArgumentException("Key length must be 256, 384, or 512 bits.");
}
final int subkeyLen = keyBytes.length / 2;
assert subkeyLen == 32 || subkeyLen == 24 || subkeyLen == 16;
final byte[] macKey = new byte[subkeyLen];
final byte[] ctrKey = new byte[subkeyLen];
try {
System.arraycopy(keyBytes, 0, macKey, 0, macKey.length); // K1 = leftmost(K, len(K)/2);
System.arraycopy(keyBytes, macKey.length, ctrKey, 0, ctrKey.length); // K2 = rightmost(K, len(K)/2);
return encrypt(ctrKey, macKey, plaintext, associatedData);
} finally {
Arrays.fill(macKey, (byte) 0);
Arrays.fill(ctrKey, (byte) 0);
Arrays.fill(keyBytes, (byte) 0);
}
}

/**
* Convenience method, if you are using the javax.crypto API. This is just a wrapper for {@link #encrypt(byte[], byte[], byte[], byte[]...)}.
*
Expand Down Expand Up @@ -150,6 +177,36 @@ public byte[] encrypt(byte[] ctrKey, byte[] macKey, byte[] plaintext, byte[]...
return result;
}

/**
* Convenience method using a single 256, 384, or 512 bits key. This is just a wrapper for {@link #decrypt(byte[], byte[], byte[], byte[]...)}.
* @param key Combined key, which is split in half.
* @param ciphertext Your cipehrtext, which shall be decrypted.
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
* @return Plaintext byte array.
* @throws IllegalArgumentException If keys are invalid.
* @throws UnauthenticCiphertextException If the authentication failed, e.g. because ciphertext and/or associatedData are corrupted.
* @throws IllegalBlockSizeException If the provided ciphertext is of invalid length.
*/
public byte[] decrypt(SecretKey key, byte[] ciphertext, byte[]... associatedData) throws UnauthenticCiphertextException, IllegalBlockSizeException {
final byte[] keyBytes = key.getEncoded();
if (keyBytes.length != 64 && keyBytes.length != 48 && keyBytes.length != 32) {
throw new IllegalArgumentException("Key length must be 256, 384, or 512 bits.");
}
final int subkeyLen = keyBytes.length / 2;
assert subkeyLen == 32 || subkeyLen == 24 || subkeyLen == 16;
final byte[] macKey = new byte[subkeyLen];
final byte[] ctrKey = new byte[subkeyLen];
try {
System.arraycopy(keyBytes, 0, macKey, 0, macKey.length); // K1 = leftmost(K, len(K)/2);
System.arraycopy(keyBytes, macKey.length, ctrKey, 0, ctrKey.length); // K2 = rightmost(K, len(K)/2);
return decrypt(ctrKey, macKey, ciphertext, associatedData);
} finally {
Arrays.fill(macKey, (byte) 0);
Arrays.fill(ctrKey, (byte) 0);
Arrays.fill(keyBytes, (byte) 0);
}
}

/**
* Convenience method, if you are using the javax.crypto API. This is just a wrapper for {@link #decrypt(byte[], byte[], byte[], byte[]...)}.
*
Expand Down
43 changes: 40 additions & 3 deletions src/test/java/org/cryptomator/siv/SivModeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;

import javax.crypto.IllegalBlockSizeException;
Expand Down Expand Up @@ -66,6 +68,17 @@ public void testEncryptWithInvalidKey2() {
});
}

@Test
public void testEncryptWithInvalidKey3() {
SecretKey key = Mockito.mock(SecretKey.class);
Mockito.when(key.getEncoded()).thenReturn(new byte[13]);

SivMode sivMode = new SivMode();
Assertions.assertThrows(IllegalArgumentException.class, () -> {
sivMode.encrypt(key, new byte[10]);
});
}

@Test
public void testInvalidCipher1() {
BlockCipherFactory factory = () -> null;
Expand Down Expand Up @@ -111,6 +124,17 @@ public void testDecryptWithInvalidKey2() {
});
}

@Test
public void testDecryptWithInvalidKey3() {
SecretKey key = Mockito.mock(SecretKey.class);
Mockito.when(key.getEncoded()).thenReturn(new byte[13]);

SivMode sivMode = new SivMode();
Assertions.assertThrows(IllegalArgumentException.class, () -> {
sivMode.decrypt(key, new byte[10]);
});
}

@Test
public void testDecryptWithInvalidBlockSize() {
final byte[] dummyKey = new byte[16];
Expand Down Expand Up @@ -437,9 +461,10 @@ public void testNonceBasedAuthenticatedEncryption() {
Assertions.assertArrayEquals(expected, result);
}

@Test
public void testEncryptionAndDecryptionUsingJavaxCryptoApi() throws UnauthenticCiphertextException, IllegalBlockSizeException {
final byte[] dummyKey = new byte[16];
@ParameterizedTest
@ValueSource(ints = {16, 24, 32})
public void testEncryptionAndDecryptionUsingJavaxCryptoApi(int keylen) throws UnauthenticCiphertextException, IllegalBlockSizeException {
final byte[] dummyKey = new byte[keylen];
final SecretKey ctrKey = new SecretKeySpec(dummyKey, "AES");
final SecretKey macKey = new SecretKeySpec(dummyKey, "AES");
final SivMode sivMode = new SivMode();
Expand All @@ -449,6 +474,18 @@ public void testEncryptionAndDecryptionUsingJavaxCryptoApi() throws UnauthenticC
Assertions.assertArrayEquals(cleartext, decrypted);
}

@ParameterizedTest
@ValueSource(ints = {32, 48, 64})
public void testEncryptionAndDecryptionUsingSingleJavaxCryptoApi(int keylen) throws UnauthenticCiphertextException, IllegalBlockSizeException {
final byte[] dummyKey = new byte[keylen];
final SecretKey key = new SecretKeySpec(dummyKey, "AES");
final SivMode sivMode = new SivMode();
final byte[] cleartext = "hello world".getBytes();
final byte[] ciphertext = sivMode.encrypt(key, cleartext);
final byte[] decrypted = sivMode.decrypt(key, ciphertext);
Assertions.assertArrayEquals(cleartext, decrypted);
}

@Test
public void testShiftLeft() {
final byte[] output = new byte[4];
Expand Down

0 comments on commit 59a2551

Please sign in to comment.