Skip to content

Commit

Permalink
Merge branch 'release/1.6.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Dec 5, 2024
2 parents 7fc744b + a9f31ab commit 01fde51
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 39 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Build
on:
[push]
push:
pull_request_target:
types: [labeled]
jobs:
build:
name: Build and Test
Expand Down Expand Up @@ -68,4 +70,4 @@ jobs:
```
See [README.md](https://github.com/cryptomator/siv-mode/#reproducible-builds) section regarding reproducing this build.
generate_release_notes: true
generate_release_notes: true
5 changes: 2 additions & 3 deletions .github/workflows/publish-central.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jobs:
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
server-username: MAVEN_USERNAME # env variable for username in deploy
server-password: MAVEN_PASSWORD # env variable for token in deploy
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
- name: Verify project version = ${{ github.event.inputs.tag }}
run: |
PROJECT_VERSION=$(./mvnw help:evaluate "-Dexpression=project.version" -q -DforceStdout)
Expand All @@ -32,4 +30,5 @@ jobs:
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
3 changes: 1 addition & 2 deletions .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ jobs:
java-version: 21
distribution: 'zulu'
cache: 'maven'
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
- name: Verify project version = ${{ github.event.release.tag_name }}
run: |
PROJECT_VERSION=$(./mvnw help:evaluate "-Dexpression=project.version" -q -DforceStdout)
Expand All @@ -24,6 +22,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

### Added

- This CHANGELOG file
- `encrypt(SecretKey key, byte[] plaintext, byte[]... associatedData)` and `decrypt(SecretKey key, byte[] ciphertext, byte[]... associatedData)` using a single 256, 384, or 512 bit key

### Changed

- use `maven-gpg-plugin`'s bc-based signer
40 changes: 19 additions & 21 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>siv-mode</artifactId>
<version>1.5.2</version>
<version>1.6.0</version>

<name>SIV Mode</name>
<description>RFC 5297 SIV mode: deterministic authenticated encryption</description>
Expand Down Expand Up @@ -38,17 +38,17 @@
<project.build.outputTimestamp>2024-04-16T12:32:12Z</project.build.outputTimestamp>

<!-- dependencies -->
<bouncycastle.version>1.78</bouncycastle.version>
<bouncycastle.version>1.78.1</bouncycastle.version>

<!-- test dependencies -->
<junit.version>5.10.2</junit.version>
<mockito.version>5.11.0</mockito.version>
<junit.version>5.11.0</junit.version>
<mockito.version>5.13.0</mockito.version>
<jmh.version>1.37</jmh.version>
<hamcrest.version>2.2</hamcrest.version>
<guava.version>33.1.0-jre</guava.version>
<hamcrest.version>3.0</hamcrest.version>
<guava.version>33.3.0-jre</guava.version>

<!-- maven plugins -->
<dependency-check.version>9.1.0</dependency-check.version>
<dependency-check.version>11.1.1</dependency-check.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -110,12 +110,12 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.16.2</version>
<version>2.18.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<version>3.5.0</version>
<executions>
<execution>
<id>enforce-java</id>
Expand Down Expand Up @@ -162,11 +162,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<version>3.5.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.2</version>
<configuration>
<archive>
<manifestEntries>
Expand All @@ -178,7 +178,7 @@
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -190,7 +190,7 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<version>3.11.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand All @@ -210,7 +210,7 @@
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
Expand Down Expand Up @@ -285,7 +285,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
Expand All @@ -311,7 +311,7 @@
<plugins>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.2</version>
<version>3.2.7</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand All @@ -320,10 +320,8 @@
<goal>sign</goal>
</goals>
<configuration>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
<signer>bc</signer>
<keyFingerprint>58117AFA1F85B3EEC154677D615D449FE6E6A235</keyFingerprint>
</configuration>
</execution>
</executions>
Expand All @@ -346,7 +344,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
Expand Down
73 changes: 65 additions & 8 deletions src/main/java/org/cryptomator/siv/SivMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,38 @@ 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[]...)}.
*
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param macKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param macKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @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.
Expand All @@ -127,8 +154,8 @@ public byte[] encrypt(SecretKey ctrKey, SecretKey macKey, byte[] plaintext, byte
/**
* Encrypts plaintext using SIV mode. A block cipher defined by the constructor is being used.<br>
*
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param macKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param macKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @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.
Expand All @@ -150,11 +177,41 @@ 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[]...)}.
*
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param macKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param macKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param ciphertext Your cipehrtext, which shall be decrypted.
* @param associatedData Optional associated data, which needs to be authenticated during decryption.
* @return Plaintext byte array.
Expand All @@ -179,8 +236,8 @@ public byte[] decrypt(SecretKey ctrKey, SecretKey macKey, byte[] ciphertext, byt
/**
* Decrypts ciphertext using SIV mode. A block cipher defined by the constructor is being used.<br>
*
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param macKey SIV mode requires two separate keys. You can use one long key, which is splitted in half. See https://tools.ietf.org/html/rfc5297#section-2.2
* @param ctrKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param macKey SIV mode requires two separate keys. You can use one long key, which is split in half. See <a href="https://tools.ietf.org/html/rfc5297#section-2.2">RFC 5297 Section 2.2</a>
* @param ciphertext Your ciphertext, which shall be encrypted.
* @param associatedData Optional associated data, which needs to be authenticated during decryption.
* @return Plaintext byte array.
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 01fde51

Please sign in to comment.