Skip to content

Commit c8f79ab

Browse files
remove dependency on bouncycastle
by internalizing CMAC implementation
1 parent 72e4be2 commit c8f79ab

File tree

18 files changed

+328
-661
lines changed

18 files changed

+328
-661
lines changed

pom.xml

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
34
<modelVersion>4.0.0</modelVersion>
45
<groupId>org.cryptomator</groupId>
56
<artifactId>siv-mode</artifactId>
@@ -37,28 +38,21 @@
3738
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3839
<project.build.outputTimestamp>2025-03-14T12:02:43Z</project.build.outputTimestamp>
3940

40-
<!-- dependencies -->
41-
<bouncycastle.version>1.80</bouncycastle.version>
42-
4341
<!-- test dependencies -->
4442
<junit.version>5.12.0</junit.version>
45-
<mockito.version>5.15.2</mockito.version>
43+
<mockito.version>5.20.0</mockito.version>
4644
<jmh.version>1.37</jmh.version>
4745
<hamcrest.version>3.0</hamcrest.version>
4846
<guava.version>33.4.0-jre</guava.version>
4947

5048
<!-- maven plugins -->
5149
<dependency-check.version>12.1.1</dependency-check.version>
50+
51+
<!-- Property used by surefire to determine jacoco engine -->
52+
<surefire.jacoco.args/>
5253
</properties>
5354

5455
<dependencies>
55-
<dependency>
56-
<groupId>org.bouncycastle</groupId>
57-
<artifactId>bcprov-jdk18on</artifactId>
58-
<version>${bouncycastle.version}</version>
59-
<!-- see maven-shade-plugin; we don't want this as a transitive dependency in other projects -->
60-
<optional>true</optional>
61-
</dependency>
6256
<dependency>
6357
<groupId>org.jetbrains</groupId>
6458
<artifactId>annotations</artifactId>
@@ -134,13 +128,33 @@
134128
</execution>
135129
</executions>
136130
</plugin>
131+
<plugin>
132+
<groupId>org.apache.maven.plugins</groupId>
133+
<artifactId>maven-dependency-plugin</artifactId>
134+
<executions>
135+
<execution>
136+
<id>jar-paths-to-properties</id>
137+
<phase>validate</phase>
138+
<goals>
139+
<goal>properties</goal>
140+
</goals>
141+
</execution>
142+
</executions>
143+
</plugin>
137144
<plugin>
138145
<artifactId>maven-compiler-plugin</artifactId>
139146
<version>3.14.0</version>
140147
<configuration>
141148
<release>8</release>
142149
<encoding>UTF-8</encoding>
143150
<showWarnings>true</showWarnings>
151+
<annotationProcessorPaths>
152+
<path>
153+
<groupId>org.openjdk.jmh</groupId>
154+
<artifactId>jmh-generator-annprocess</artifactId>
155+
<version>${jmh.version}</version>
156+
</path>
157+
</annotationProcessorPaths>
144158
</configuration>
145159
<executions>
146160
<execution>
@@ -163,6 +177,9 @@
163177
<groupId>org.apache.maven.plugins</groupId>
164178
<artifactId>maven-surefire-plugin</artifactId>
165179
<version>3.5.3</version>
180+
<configuration>
181+
<argLine>@{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar}</argLine>
182+
</configuration>
166183
</plugin>
167184
<plugin>
168185
<artifactId>maven-jar-plugin</artifactId>
@@ -208,43 +225,6 @@
208225
<release>8</release>
209226
</configuration>
210227
</plugin>
211-
<plugin>
212-
<artifactId>maven-shade-plugin</artifactId>
213-
<version>3.6.0</version>
214-
<executions>
215-
<execution>
216-
<phase>package</phase>
217-
<goals>
218-
<goal>shade</goal>
219-
</goals>
220-
<configuration>
221-
<minimizeJar>true</minimizeJar>
222-
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
223-
<createDependencyReducedPom>false</createDependencyReducedPom>
224-
<createSourcesJar>false</createSourcesJar>
225-
<artifactSet>
226-
<includes>
227-
<include>org.bouncycastle:bcprov-jdk18on</include>
228-
</includes>
229-
</artifactSet>
230-
<relocations>
231-
<relocation>
232-
<pattern>org.bouncycastle</pattern>
233-
<shadedPattern>org.cryptomator.siv.org.bouncycastle</shadedPattern>
234-
</relocation>
235-
</relocations>
236-
<filters>
237-
<filter>
238-
<artifact>org.bouncycastle:bcprov-jdk18on</artifact>
239-
<excludes>
240-
<exclude>META-INF/**</exclude>
241-
</excludes>
242-
</filter>
243-
</filters>
244-
</configuration>
245-
</execution>
246-
</executions>
247-
</plugin>
248228
</plugins>
249229
</build>
250230

@@ -292,6 +272,9 @@
292272
<goals>
293273
<goal>prepare-agent</goal>
294274
</goals>
275+
<configuration>
276+
<propertyName>surefire.jacoco.args</propertyName>
277+
</configuration>
295278
</execution>
296279
</executions>
297280
<!-- workaround for https://github.com/jacoco/jacoco/issues/407 -->
@@ -340,7 +323,7 @@
340323
<extensions>true</extensions>
341324
<configuration>
342325
<publishingServerId>central</publishingServerId>
343-
<autoPublish>true</autoPublish>
326+
<autoPublish>true</autoPublish>
344327
</configuration>
345328
</plugin>
346329
</plugins>
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.cryptomator.siv;
2+
3+
import javax.crypto.BadPaddingException;
4+
import javax.crypto.Cipher;
5+
import javax.crypto.IllegalBlockSizeException;
6+
import javax.crypto.MacSpi;
7+
import javax.crypto.NoSuchPaddingException;
8+
import javax.crypto.spec.SecretKeySpec;
9+
import java.security.InvalidKeyException;
10+
import java.security.Key;
11+
import java.security.MessageDigest;
12+
import java.security.NoSuchAlgorithmException;
13+
import java.security.spec.AlgorithmParameterSpec;
14+
import java.util.Arrays;
15+
16+
/**
17+
* AES-CMAC (Cipher-based Message Authentication Code).
18+
* Specs: <a href="https://www.rfc-editor.org/rfc/rfc4493.html">RFC 4493</a>.
19+
*/
20+
class CMac extends MacSpi {
21+
22+
private static final int BLOCK_SIZE = 16; // 128 bits for AES
23+
private static final String AES_ALGORITHM = "AES";
24+
private static final String AES_ECB_NO_PADDING = "AES/ECB/NoPadding";
25+
26+
// MAC keys:
27+
private Cipher cipher;
28+
private byte[] k1;
29+
private byte[] k2;
30+
31+
// MAC state:
32+
private int bufferPos = 0;
33+
private byte[] buffer = new byte[BLOCK_SIZE];
34+
private byte[] x = new byte[BLOCK_SIZE]; // X := const_Zero;
35+
private byte[] y = new byte[BLOCK_SIZE];
36+
private int msgLen = 0;
37+
38+
@Override
39+
protected int engineGetMacLength() {
40+
return BLOCK_SIZE;
41+
}
42+
43+
@Override
44+
protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException {
45+
try {
46+
this.cipher = Cipher.getInstance(AES_ECB_NO_PADDING);
47+
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
48+
throw new AssertionError("Every implementation of the Java platform is required to support [...] AES/ECB/NoPadding", e);
49+
}
50+
cipher.init(Cipher.ENCRYPT_MODE, key);
51+
52+
// init subkeys K1 and K2
53+
// see https://www.rfc-editor.org/rfc/rfc4493.html#section-2.3
54+
byte[] L = new byte[BLOCK_SIZE];
55+
try {
56+
// L = AES_encrypt(K, const_Zero)
57+
L = encryptBlock(cipher, L);
58+
this.k1 = SivMode.dbl(L);
59+
this.k2 = SivMode.dbl(k1);
60+
} finally {
61+
Arrays.fill(L, (byte) 0);
62+
}
63+
}
64+
65+
@Override
66+
protected void engineUpdate(byte input) {
67+
if (bufferPos == BLOCK_SIZE) { // buffer is full
68+
processBlock();
69+
}
70+
assert bufferPos < BLOCK_SIZE;
71+
buffer[bufferPos++] = input;
72+
msgLen++;
73+
}
74+
75+
@Override
76+
protected void engineUpdate(byte[] input, int offset, int len) {
77+
assert bufferPos < BLOCK_SIZE;
78+
for (int i = offset; i < offset + len; ) {
79+
if (bufferPos == BLOCK_SIZE) { // buffer is full
80+
processBlock();
81+
}
82+
int required = offset + len - i;
83+
int available = BLOCK_SIZE - bufferPos;
84+
int m = Math.min(required, available);
85+
System.arraycopy(input, i, buffer, bufferPos, m);
86+
bufferPos += m;
87+
i += m;
88+
}
89+
msgLen += len;
90+
}
91+
92+
// https://www.rfc-editor.org/rfc/rfc4493.html#section-2.4 Step 6
93+
private void processBlock() {
94+
y = SivMode.xor(x, buffer); // Y := X XOR M_i;
95+
x = encryptBlock(cipher, y); // X := AES-128(K,Y);
96+
bufferPos = 0;
97+
}
98+
99+
// https://www.rfc-editor.org/rfc/rfc4493.html#section-2.4
100+
@Override
101+
protected byte[] engineDoFinal() {
102+
// Step 3:
103+
boolean flag = msgLen > 0 && bufferPos % BLOCK_SIZE == 0; // denoting if last block is complete or not
104+
105+
// Step 4:
106+
byte[] m_last;
107+
if (flag) {
108+
// M_last := M_n XOR K1;
109+
m_last = SivMode.xor(buffer, k1);
110+
} else {
111+
// M_last := padding(M_n) XOR K2;
112+
//
113+
// [...] padding(x) is the concatenation of x and a single '1',
114+
// followed by the minimum number of '0's, so that the total length is
115+
// equal to 128 bits.
116+
buffer[bufferPos] = (byte) 0x80; // single '1' bit
117+
if (bufferPos + 1 < BLOCK_SIZE) {
118+
Arrays.fill(buffer, bufferPos + 1, BLOCK_SIZE, (byte) 0x00); // followed by '0' bits
119+
}
120+
m_last = SivMode.xor(buffer, k2);
121+
}
122+
123+
// Step 7:
124+
y = SivMode.xor(m_last, x); // Y := M_last XOR X;
125+
try {
126+
return encryptBlock(cipher, y); // T := AES-128(K,Y);
127+
} finally {
128+
engineReset();
129+
}
130+
}
131+
132+
@Override
133+
protected void engineReset() {
134+
bufferPos = 0;
135+
msgLen = 0;
136+
Arrays.fill(buffer, (byte) 0);
137+
Arrays.fill(x, (byte) 0);
138+
Arrays.fill(y, (byte) 0);
139+
}
140+
141+
// TODO make instance method, remove cipher param?
142+
private static byte[] encryptBlock(Cipher cipher, byte[] block) {
143+
try {
144+
return cipher.doFinal(block);
145+
} catch (IllegalBlockSizeException e) {
146+
throw new IllegalArgumentException(e);
147+
} catch (BadPaddingException e) {
148+
throw new AssertionError("Not in decrypt mode", e);
149+
}
150+
}
151+
152+
/**
153+
* Create a new CMAC instance for incremental message processing
154+
*/
155+
public static CMac create(byte[] key) {
156+
if (key.length != 16 && key.length != 24 && key.length != 32) {
157+
throw new IllegalArgumentException("Invalid key length. Must be 16, 24, or 32 bytes");
158+
}
159+
try {
160+
SecretKeySpec keySpec = new SecretKeySpec(key, AES_ALGORITHM);
161+
162+
CMac mac = new CMac();
163+
mac.engineInit(keySpec, null);
164+
return mac;
165+
} catch (InvalidKeyException e) {
166+
throw new IllegalArgumentException("Invalid key", e);
167+
}
168+
}
169+
170+
/**
171+
* One-shot CMAC computation
172+
*/
173+
public static byte[] tag(byte[] key, byte[] message) {
174+
CMac cmac = create(key);
175+
cmac.engineUpdate(message, 0, message.length);
176+
return cmac.engineDoFinal();
177+
}
178+
179+
/**
180+
* Verify CMAC tag
181+
*/
182+
public static boolean verify(byte[] key, byte[] message, byte[] tag) {
183+
byte[] computedTag = tag(key, message);
184+
return MessageDigest.isEqual(computedTag, tag);
185+
}
186+
}

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

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)