Skip to content

Commit 01fde51

Browse files
Merge branch 'release/1.6.0'
2 parents 7fc744b + a9f31ab commit 01fde51

File tree

7 files changed

+148
-39
lines changed

7 files changed

+148
-39
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: Build
22
on:
3-
[push]
3+
push:
4+
pull_request_target:
5+
types: [labeled]
46
jobs:
57
build:
68
name: Build and Test
@@ -68,4 +70,4 @@ jobs:
6870
```
6971
7072
See [README.md](https://github.com/cryptomator/siv-mode/#reproducible-builds) section regarding reproducing this build.
71-
generate_release_notes: true
73+
generate_release_notes: true

.github/workflows/publish-central.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ jobs:
2121
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
2222
server-username: MAVEN_USERNAME # env variable for username in deploy
2323
server-password: MAVEN_PASSWORD # env variable for token in deploy
24-
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
25-
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
2624
- name: Verify project version = ${{ github.event.inputs.tag }}
2725
run: |
2826
PROJECT_VERSION=$(./mvnw help:evaluate "-Dexpression=project.version" -q -DforceStdout)
@@ -32,4 +30,5 @@ jobs:
3230
env:
3331
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
3432
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
35-
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
33+
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
34+
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}

.github/workflows/publish-github.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ jobs:
1313
java-version: 21
1414
distribution: 'zulu'
1515
cache: 'maven'
16-
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
17-
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
1816
- name: Verify project version = ${{ github.event.release.tag_name }}
1917
run: |
2018
PROJECT_VERSION=$(./mvnw help:evaluate "-Dexpression=project.version" -q -DforceStdout)
@@ -24,6 +22,7 @@ jobs:
2422
env:
2523
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2624
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
25+
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
2726
- name: Slack Notification
2827
uses: rtCamp/action-slack-notify@v2
2928
env:

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.6.0](https://github.com/cryptomator/siv-mode/compare/1.5.2...1.6.0)
9+
10+
### Added
11+
12+
- This CHANGELOG file
13+
- `encrypt(SecretKey key, byte[] plaintext, byte[]... associatedData)` and `decrypt(SecretKey key, byte[] ciphertext, byte[]... associatedData)` using a single 256, 384, or 512 bit key
14+
15+
### Changed
16+
17+
- use `maven-gpg-plugin`'s bc-based signer

pom.xml

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>org.cryptomator</groupId>
55
<artifactId>siv-mode</artifactId>
6-
<version>1.5.2</version>
6+
<version>1.6.0</version>
77

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

4040
<!-- dependencies -->
41-
<bouncycastle.version>1.78</bouncycastle.version>
41+
<bouncycastle.version>1.78.1</bouncycastle.version>
4242

4343
<!-- test dependencies -->
44-
<junit.version>5.10.2</junit.version>
45-
<mockito.version>5.11.0</mockito.version>
44+
<junit.version>5.11.0</junit.version>
45+
<mockito.version>5.13.0</mockito.version>
4646
<jmh.version>1.37</jmh.version>
47-
<hamcrest.version>2.2</hamcrest.version>
48-
<guava.version>33.1.0-jre</guava.version>
47+
<hamcrest.version>3.0</hamcrest.version>
48+
<guava.version>33.3.0-jre</guava.version>
4949

5050
<!-- maven plugins -->
51-
<dependency-check.version>9.1.0</dependency-check.version>
51+
<dependency-check.version>11.1.1</dependency-check.version>
5252
</properties>
5353

5454
<dependencies>
@@ -110,12 +110,12 @@
110110
<plugin>
111111
<groupId>org.codehaus.mojo</groupId>
112112
<artifactId>versions-maven-plugin</artifactId>
113-
<version>2.16.2</version>
113+
<version>2.18.0</version>
114114
</plugin>
115115
<plugin>
116116
<groupId>org.apache.maven.plugins</groupId>
117117
<artifactId>maven-enforcer-plugin</artifactId>
118-
<version>3.4.1</version>
118+
<version>3.5.0</version>
119119
<executions>
120120
<execution>
121121
<id>enforce-java</id>
@@ -162,11 +162,11 @@
162162
<plugin>
163163
<groupId>org.apache.maven.plugins</groupId>
164164
<artifactId>maven-surefire-plugin</artifactId>
165-
<version>3.2.5</version>
165+
<version>3.5.2</version>
166166
</plugin>
167167
<plugin>
168168
<artifactId>maven-jar-plugin</artifactId>
169-
<version>3.3.0</version>
169+
<version>3.4.2</version>
170170
<configuration>
171171
<archive>
172172
<manifestEntries>
@@ -178,7 +178,7 @@
178178
</plugin>
179179
<plugin>
180180
<artifactId>maven-source-plugin</artifactId>
181-
<version>3.3.0</version>
181+
<version>3.3.1</version>
182182
<executions>
183183
<execution>
184184
<id>attach-sources</id>
@@ -190,7 +190,7 @@
190190
</plugin>
191191
<plugin>
192192
<artifactId>maven-javadoc-plugin</artifactId>
193-
<version>3.6.3</version>
193+
<version>3.11.1</version>
194194
<executions>
195195
<execution>
196196
<id>attach-javadocs</id>
@@ -210,7 +210,7 @@
210210
</plugin>
211211
<plugin>
212212
<artifactId>maven-shade-plugin</artifactId>
213-
<version>3.5.2</version>
213+
<version>3.6.0</version>
214214
<executions>
215215
<execution>
216216
<phase>package</phase>
@@ -285,7 +285,7 @@
285285
<plugin>
286286
<groupId>org.jacoco</groupId>
287287
<artifactId>jacoco-maven-plugin</artifactId>
288-
<version>0.8.11</version>
288+
<version>0.8.12</version>
289289
<executions>
290290
<execution>
291291
<id>prepare-agent</id>
@@ -311,7 +311,7 @@
311311
<plugins>
312312
<plugin>
313313
<artifactId>maven-gpg-plugin</artifactId>
314-
<version>3.2.2</version>
314+
<version>3.2.7</version>
315315
<executions>
316316
<execution>
317317
<id>sign-artifacts</id>
@@ -320,10 +320,8 @@
320320
<goal>sign</goal>
321321
</goals>
322322
<configuration>
323-
<gpgArguments>
324-
<arg>--pinentry-mode</arg>
325-
<arg>loopback</arg>
326-
</gpgArguments>
323+
<signer>bc</signer>
324+
<keyFingerprint>58117AFA1F85B3EEC154677D615D449FE6E6A235</keyFingerprint>
327325
</configuration>
328326
</execution>
329327
</executions>
@@ -346,7 +344,7 @@
346344
<plugin>
347345
<groupId>org.sonatype.plugins</groupId>
348346
<artifactId>nexus-staging-maven-plugin</artifactId>
349-
<version>1.6.13</version>
347+
<version>1.7.0</version>
350348
<extensions>true</extensions>
351349
<configuration>
352350
<serverId>ossrh</serverId>

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

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,38 @@ 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
*
106-
* @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
107-
* @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
133+
* @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>
134+
* @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>
108135
* @param plaintext Your plaintext, which shall be encrypted.
109136
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
110137
* @return IV + Ciphertext as a concatenated byte array.
@@ -127,8 +154,8 @@ public byte[] encrypt(SecretKey ctrKey, SecretKey macKey, byte[] plaintext, byte
127154
/**
128155
* Encrypts plaintext using SIV mode. A block cipher defined by the constructor is being used.<br>
129156
*
130-
* @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
131-
* @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
157+
* @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>
158+
* @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>
132159
* @param plaintext Your plaintext, which shall be encrypted.
133160
* @param associatedData Optional associated data, which gets authenticated but not encrypted.
134161
* @return IV + Ciphertext as a concatenated byte array.
@@ -150,11 +177,41 @@ 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
*
156-
* @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
157-
* @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
213+
* @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>
214+
* @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>
158215
* @param ciphertext Your cipehrtext, which shall be decrypted.
159216
* @param associatedData Optional associated data, which needs to be authenticated during decryption.
160217
* @return Plaintext byte array.
@@ -179,8 +236,8 @@ public byte[] decrypt(SecretKey ctrKey, SecretKey macKey, byte[] ciphertext, byt
179236
/**
180237
* Decrypts ciphertext using SIV mode. A block cipher defined by the constructor is being used.<br>
181238
*
182-
* @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
183-
* @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
239+
* @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>
240+
* @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>
184241
* @param ciphertext Your ciphertext, which shall be encrypted.
185242
* @param associatedData Optional associated data, which needs to be authenticated during decryption.
186243
* @return Plaintext byte array.

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)