Skip to content

Commit 0fb2d26

Browse files
committed
added EncodableService interface, added Memoable/EncodableService to KMAC.
1 parent 1e1649f commit 0fb2d26

File tree

4 files changed

+216
-6
lines changed

4 files changed

+216
-6
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.bouncycastle.crypto;
2+
3+
/**
4+
* Encodable services allow you to download an encoded copy of their internal state. This is useful for the situation where
5+
* you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the
6+
* internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the
7+
* entire message.
8+
*/
9+
public interface EncodableService
10+
{
11+
/**
12+
* Return an encoded byte array for the services's internal state
13+
*
14+
* @return an encoding of the services internal state.
15+
*/
16+
byte[] getEncodedState();
17+
}

core/src/main/java/org/bouncycastle/crypto/digests/EncodableDigest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.bouncycastle.crypto.digests;
22

3+
import org.bouncycastle.crypto.EncodableService;
4+
35
/**
46
* Encodable digests allow you to download an encoded copy of their internal state. This is useful for the situation where
57
* you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the
68
* internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the
79
* entire message.
810
*/
911
public interface EncodableDigest
12+
extends EncodableService
1013
{
1114
/**
1215
* Return an encoded byte array for the digest's internal state

core/src/main/java/org/bouncycastle/crypto/macs/KMAC.java

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import org.bouncycastle.crypto.Mac;
66
import org.bouncycastle.crypto.Xof;
77
import org.bouncycastle.crypto.digests.CSHAKEDigest;
8+
import org.bouncycastle.crypto.digests.EncodableDigest;
89
import org.bouncycastle.crypto.digests.XofUtils;
910
import org.bouncycastle.crypto.params.KeyParameter;
1011
import org.bouncycastle.util.Arrays;
12+
import org.bouncycastle.util.Memoable;
13+
import org.bouncycastle.util.Pack;
1114
import org.bouncycastle.util.Strings;
1215

1316
/**
@@ -17,13 +20,14 @@
1720
* </p>
1821
*/
1922
public class KMAC
20-
implements Mac, Xof
23+
implements Mac, Xof, Memoable, EncodableDigest
2124
{
2225
private static final byte[] padding = new byte[100];
2326

2427
private final CSHAKEDigest cshake;
25-
private final int bitLength;
26-
private final int outputLength;
28+
29+
private int bitLength;
30+
private int outputLength;
2731

2832
private byte[] key;
2933
private boolean initialised;
@@ -42,12 +46,48 @@ public KMAC(int bitLength, byte[] S)
4246
this.outputLength = bitLength * 2 / 8;
4347
}
4448

49+
public KMAC(KMAC original)
50+
{
51+
this.cshake = new CSHAKEDigest(original.cshake);
52+
this.bitLength = original.bitLength;
53+
this.outputLength = original.outputLength;
54+
this.key = original.key;
55+
this.initialised = original.initialised;
56+
this.firstOutput = original.firstOutput;
57+
}
58+
59+
public KMAC(byte[] state)
60+
{
61+
this.key = new byte[state[0] & 0xff];
62+
System.arraycopy(state, 1, key, 0, key.length);
63+
this.cshake = new CSHAKEDigest(Arrays.copyOfRange(state, 1 + key.length, state.length - 10));
64+
65+
this.bitLength = Pack.bigEndianToInt(state, state.length - 10);
66+
this.outputLength = Pack.bigEndianToInt(state, state.length - 6);
67+
this.initialised = state[state.length - 2] != 0;
68+
this.firstOutput = state[state.length - 1] != 0;
69+
}
70+
71+
private void copyIn(KMAC original)
72+
{
73+
this.cshake.reset(original.cshake);
74+
this.bitLength = original.bitLength;
75+
this.outputLength = original.outputLength;
76+
this.initialised = original.initialised;
77+
this.firstOutput = original.firstOutput;
78+
}
79+
4580
public void init(CipherParameters params)
4681
throws IllegalArgumentException
4782
{
4883
KeyParameter kParam = (KeyParameter)params;
4984

5085
this.key = Arrays.clone(kParam.getKey());
86+
if (this.key.length > 255) // 2^2040
87+
{
88+
throw new IllegalArgumentException("key length must be between 0 and 2040 bits");
89+
}
90+
5191
this.initialised = true;
5292

5393
reset();
@@ -201,4 +241,39 @@ private static byte[] encode(byte[] X)
201241
{
202242
return Arrays.concatenate(XofUtils.leftEncode(X.length * 8), X);
203243
}
244+
245+
public byte[] getEncodedState()
246+
{
247+
if (!this.initialised)
248+
{
249+
throw new IllegalStateException("KMAC not initialised");
250+
}
251+
252+
byte[] cshakeState = this.cshake.getEncodedState();
253+
byte[] extraState = new byte[4 + 4 + 2];
254+
255+
Pack.intToBigEndian(this.bitLength, extraState, 0);
256+
Pack.intToBigEndian(this.outputLength, extraState, 4);
257+
extraState[8] = this.initialised ? (byte)1 : (byte)0;
258+
extraState[9] = this.firstOutput ? (byte)1 : (byte)0;
259+
260+
byte[] enc = new byte[1 + key.length + cshakeState.length + extraState.length];
261+
262+
enc[0] = (byte)key.length; // key capped at 255 bytes.
263+
System.arraycopy(key, 0, enc, 1, key.length);
264+
System.arraycopy(cshakeState, 0, enc, 1 + key.length, cshakeState.length);
265+
System.arraycopy(extraState, 0, enc, 1 + key.length + cshakeState.length, extraState.length);
266+
267+
return enc;
268+
}
269+
270+
public Memoable copy()
271+
{
272+
return new KMAC(this);
273+
}
274+
275+
public void reset(Memoable other)
276+
{
277+
copyIn((KMAC)other);
278+
}
204279
}

core/src/test/java/org/bouncycastle/crypto/test/KMACTest.java

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.bouncycastle.crypto.test;
22

3+
import org.bouncycastle.crypto.Digest;
4+
import org.bouncycastle.crypto.EncodableService;
35
import org.bouncycastle.crypto.macs.KMAC;
46
import org.bouncycastle.crypto.params.KeyParameter;
57
import org.bouncycastle.util.Arrays;
8+
import org.bouncycastle.util.Memoable;
69
import org.bouncycastle.util.Strings;
710
import org.bouncycastle.util.encoders.Hex;
811
import org.bouncycastle.util.test.SimpleTest;
@@ -137,7 +140,15 @@ public void performTest()
137140
checkKMAC(256, new KMAC(256, null), Hex.decode("eeaabeef"));
138141
checkKMAC(128, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
139142
checkKMAC(128, new KMAC(128, null), Hex.decode("eeaabeef"));
140-
checkKMAC(256, new KMAC(256, null), Hex.decode("eeaabeef"));
143+
checkKMAC(256, new KMAC(256, null), Hex.decode("eeaabeef"));
144+
145+
byte[] resBuf = new byte[32];
146+
byte[] message = Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F");
147+
byte[] expected = Hex.decode("059a2eb4961b482ff5bb6a0278d3ad2117b20aafb2f0df33e7748176648c8192");
148+
149+
testClone(resBuf, message, expected, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
150+
testMemo(resBuf, message, expected, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
151+
testEncodedState(resBuf, message, expected, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
141152
}
142153

143154
private void doFinalTest()
@@ -146,7 +157,7 @@ private void doFinalTest()
146157

147158
kmac.init(new KeyParameter(Hex.decode(
148159
"404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
149-
160+
150161
kmac.update(Hex.decode("00010203"), 0, 4);
151162

152163
byte[] res = new byte[32];
@@ -236,7 +247,7 @@ private void checkKMAC(int bitSize, KMAC kmac, byte[] msg)
236247

237248
ref.init(new KeyParameter(new byte[0]));
238249
kmac.init(new KeyParameter(new byte[0]));
239-
250+
240251
ref.update(msg, 0, msg.length);
241252
kmac.update(msg, 0, msg.length);
242253

@@ -249,6 +260,110 @@ private void checkKMAC(int bitSize, KMAC kmac, byte[] msg)
249260
isTrue(Arrays.areEqual(res1, res2));
250261
}
251262

263+
private void testEncodedState(byte[] resBuf, byte[] input, byte[] expected, KMAC kmac, byte[] key)
264+
{
265+
kmac.init(new KeyParameter(key));
266+
267+
// test state encoding;
268+
kmac.update(input, 0, input.length / 2);
269+
270+
// copy the Digest
271+
Digest copy1 = new KMAC(((EncodableService)kmac).getEncodedState());
272+
Digest copy2 = new KMAC(((EncodableService)copy1).getEncodedState());
273+
274+
kmac.update(input, input.length / 2, input.length - input.length / 2);
275+
276+
kmac.doFinal(resBuf, 0);
277+
278+
if (!areEqual(expected, resBuf))
279+
{
280+
fail("failing state vector test", expected, new String(Hex.encode(resBuf)));
281+
}
282+
283+
copy1.update(input, input.length / 2, input.length - input.length / 2);
284+
copy1.doFinal(resBuf, 0);
285+
286+
if (!areEqual(expected, resBuf))
287+
{
288+
fail("failing state copy1 vector test", expected, new String(Hex.encode(resBuf)));
289+
}
290+
291+
copy2.update(input, input.length / 2, input.length - input.length / 2);
292+
copy2.doFinal(resBuf, 0);
293+
294+
if (!areEqual(expected, resBuf))
295+
{
296+
fail("failing state copy2 vector test", expected, new String(Hex.encode(resBuf)));
297+
}
298+
}
299+
300+
private void testMemo(byte[] resBuf, byte[] input, byte[] expected, KMAC kmac, byte[] key)
301+
{
302+
kmac.init(new KeyParameter(key));
303+
304+
Memoable m = (Memoable)kmac;
305+
306+
kmac.update(input, 0, input.length / 2);
307+
308+
// copy the Digest
309+
Memoable copy1 = m.copy();
310+
Memoable copy2 = copy1.copy();
311+
312+
kmac.update(input, input.length / 2, input.length - input.length / 2);
313+
kmac.doFinal(resBuf, 0);
314+
315+
if (!areEqual(expected, resBuf))
316+
{
317+
fail("failing memo vector test", Hex.toHexString(expected), Hex.toHexString(resBuf));
318+
}
319+
320+
m.reset(copy1);
321+
322+
kmac.update(input, input.length / 2, input.length - input.length / 2);
323+
kmac.doFinal(resBuf, 0);
324+
325+
if (!areEqual(expected, resBuf))
326+
{
327+
fail("failing memo reset vector test", Hex.toHexString(expected), Hex.toHexString(resBuf));
328+
}
329+
330+
KMAC md = (KMAC)copy2;
331+
332+
md.update(input, input.length / 2, input.length - input.length / 2);
333+
md.doFinal(resBuf, 0);
334+
335+
if (!areEqual(expected, resBuf))
336+
{
337+
fail("failing memo copy vector test", Hex.toHexString(expected), Hex.toHexString(resBuf));
338+
}
339+
}
340+
341+
private void testClone(byte[] resBuf, byte[] input, byte[] expected, KMAC kmac, byte[] key)
342+
{
343+
kmac.init(new KeyParameter(key));
344+
345+
kmac.update(input, 0, input.length / 2);
346+
347+
// clone the Digest
348+
KMAC d = new KMAC(kmac);
349+
350+
kmac.update(input, input.length / 2, input.length - input.length / 2);
351+
kmac.doFinal(resBuf, 0);
352+
353+
if (!areEqual(expected, resBuf))
354+
{
355+
fail("failing clone vector test", Hex.toHexString(expected), Hex.toHexString(resBuf));
356+
}
357+
358+
d.update(input, input.length / 2, input.length - input.length / 2);
359+
d.doFinal(resBuf, 0);
360+
361+
if (!areEqual(expected, resBuf))
362+
{
363+
fail("failing second clone vector test", Hex.toHexString(expected), Hex.toHexString(resBuf));
364+
}
365+
}
366+
252367
public static void main(
253368
String[] args)
254369
{

0 commit comments

Comments
 (0)