Skip to content

Commit bb0ff48

Browse files
author
Jamil Nimeh
committed
8305091: Change ChaCha20 cipher init behavior to match AES-GCM
Reviewed-by: djelinski, ascarpino
1 parent c0c4d77 commit bb0ff48

File tree

2 files changed

+63
-46
lines changed

2 files changed

+63
-46
lines changed

src/java.base/share/classes/com/sun/crypto/provider/ChaCha20Cipher.java

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -87,6 +87,7 @@ abstract class ChaCha20Cipher extends CipherSpi {
8787
// The counter
8888
private static final long MAX_UINT32 = 0x00000000FFFFFFFFL;
8989
private long finalCounterValue;
90+
private long initCounterValue;
9091
private long counter;
9192

9293
// The base state is created at initialization time as a 16-int array
@@ -336,7 +337,9 @@ protected void engineInit(int opmode, Key key,
336337
}
337338
ChaCha20ParameterSpec chaParams = (ChaCha20ParameterSpec)params;
338339
newNonce = chaParams.getNonce();
339-
counter = ((long)chaParams.getCounter()) & 0x00000000FFFFFFFFL;
340+
initCounterValue = ((long)chaParams.getCounter()) &
341+
0x00000000FFFFFFFFL;
342+
counter = initCounterValue;
340343
break;
341344
case MODE_AEAD:
342345
if (!(params instanceof IvParameterSpec)) {
@@ -545,9 +548,12 @@ private void init(int opmode, Key key, byte[] newNonce)
545548
}
546549

547550
// Make sure that the provided key and nonce are unique before
548-
// assigning them to the object.
551+
// assigning them to the object. Key and nonce uniqueness
552+
// protection is for encryption operations only.
549553
byte[] newKeyBytes = getEncodedKey(key);
550-
checkKeyAndNonce(newKeyBytes, newNonce);
554+
if (opmode == Cipher.ENCRYPT_MODE) {
555+
checkKeyAndNonce(newKeyBytes, newNonce);
556+
}
551557
if (this.keyBytes != null) {
552558
Arrays.fill(this.keyBytes, (byte)0);
553559
}
@@ -704,9 +710,8 @@ protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen)
704710
} catch (ShortBufferException | KeyException exc) {
705711
throw new RuntimeException(exc);
706712
} finally {
707-
// Regardless of what happens, the cipher cannot be used for
708-
// further processing until it has been freshly initialized.
709-
initialized = false;
713+
// Reset the cipher's state to post-init values.
714+
resetStartState();
710715
}
711716
return output;
712717
}
@@ -742,9 +747,8 @@ protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,
742747
} catch (KeyException ke) {
743748
throw new RuntimeException(ke);
744749
} finally {
745-
// Regardless of what happens, the cipher cannot be used for
746-
// further processing until it has been freshly initialized.
747-
initialized = false;
750+
// Reset the cipher's state to post-init values.
751+
resetStartState();
748752
}
749753
return bytesUpdated;
750754
}
@@ -1170,6 +1174,23 @@ private void authWriteLengths(long aLen, long dLen, byte[] buf) {
11701174
asLongLittleEndian.set(buf, Long.BYTES, dLen);
11711175
}
11721176

1177+
/**
1178+
* reset the Cipher's state to the values it had after
1179+
* the initial init() call.
1180+
*
1181+
* Note: The cipher's internal "initialized" field is set differently
1182+
* for ENCRYPT_MODE and DECRYPT_MODE in order to allow DECRYPT_MODE
1183+
* ciphers to reuse the key/nonce/counter values. This kind of reuse
1184+
* is disallowed in ENCRYPT_MODE.
1185+
*/
1186+
private void resetStartState() {
1187+
keyStrLimit = 0;
1188+
keyStrOffset = 0;
1189+
counter = initCounterValue;
1190+
aadDone = false;
1191+
initialized = (direction == Cipher.DECRYPT_MODE);
1192+
}
1193+
11731194
/**
11741195
* Interface for the underlying processing engines for ChaCha20
11751196
*/
@@ -1275,7 +1296,8 @@ public int getOutputSize(int inLength, boolean isFinal) {
12751296

12761297
private EngineAEADEnc() throws InvalidKeyException {
12771298
initAuthenticator();
1278-
counter = 1;
1299+
initCounterValue = 1;
1300+
counter = initCounterValue;
12791301
}
12801302

12811303
@Override
@@ -1347,7 +1369,8 @@ public int getOutputSize(int inLen, boolean isFinal) {
13471369

13481370
private EngineAEADDec() throws InvalidKeyException {
13491371
initAuthenticator();
1350-
counter = 1;
1372+
initCounterValue = 1;
1373+
counter = initCounterValue;
13511374
cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE);
13521375
tag = new byte[TAG_LENGTH];
13531376
}

test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java

+28-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
2323

2424
/**
2525
* @test
26-
* @bug 8153029
26+
* @bug 8153029 8305091
2727
* @library /test/lib
2828
* @run main ChaCha20NoReuse
2929
* @summary ChaCha20 Cipher Implementation (key/nonce reuse protection)
@@ -376,26 +376,20 @@ public boolean run(String algorithm) {
376376
}
377377
SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);
378378

379-
// Initialize and encrypt
379+
// Initialize and decrypt
380380
cipher.init(testData.direction, key, spec);
381381
if (algorithm.equals(ALG_CC20_P1305)) {
382382
cipher.updateAAD(testData.aad);
383383
}
384384
cipher.doFinal(testData.input);
385385
System.out.println("First decryption complete");
386386

387-
// Now attempt to encrypt again without changing the key/IV
388-
// This should fail.
389-
try {
390-
if (algorithm.equals(ALG_CC20_P1305)) {
391-
cipher.updateAAD(testData.aad);
392-
}
393-
cipher.doFinal(testData.input);
394-
throw new RuntimeException(
395-
"Expected IllegalStateException not thrown");
396-
} catch (IllegalStateException ise) {
397-
// Do nothing, this is what we expected to happen
387+
// Now attempt to decrypt again without changing the key/IV
388+
// We allow this scenario.
389+
if (algorithm.equals(ALG_CC20_P1305)) {
390+
cipher.updateAAD(testData.aad);
398391
}
392+
cipher.doFinal(testData.input);
399393
} catch (Exception exc) {
400394
System.out.println("Unexpected exception: " + exc);
401395
exc.printStackTrace();
@@ -408,7 +402,8 @@ public boolean run(String algorithm) {
408402

409403
/**
410404
* Perform an AEAD decryption with corrupted data so the tag does not
411-
* match. Then attempt to reuse the cipher without initialization.
405+
* match. Then use the uncorrupted test vector input and attempt to
406+
* reuse the cipher without initialization.
412407
*/
413408
public static final TestMethod decFailNoInit = new TestMethod() {
414409
@Override
@@ -441,16 +436,16 @@ public boolean run(String algorithm) {
441436
System.out.println("Expected decryption failure occurred");
442437
}
443438

444-
// Make sure that despite the exception, the Cipher object is
445-
// not in a state that would leave it initialized and able
446-
// to process future decryption operations without init.
447-
try {
448-
cipher.updateAAD(testData.aad);
449-
cipher.doFinal(testData.input);
450-
throw new RuntimeException(
451-
"Expected IllegalStateException not thrown");
452-
} catch (IllegalStateException ise) {
453-
// Do nothing, this is what we expected to happen
439+
// Even though an exception occurred during decryption, the
440+
// Cipher object should be returned to its post-init state.
441+
// Since this is a decryption operation, we should allow
442+
// key/nonce reuse. It should properly decrypt the uncorrupted
443+
// input.
444+
cipher.updateAAD(testData.aad);
445+
byte[] pText = cipher.doFinal(testData.input);
446+
if (!Arrays.equals(pText, testData.expOutput)) {
447+
throw new RuntimeException("FAIL: Attempted decryption " +
448+
"did not match expected plaintext");
454449
}
455450
} catch (Exception exc) {
456451
System.out.println("Unexpected exception: " + exc);
@@ -562,18 +557,17 @@ public boolean run(String algorithm) {
562557
if (algorithm.equals(ALG_CC20_P1305)) {
563558
cipher.updateAAD(testData.aad);
564559
}
565-
cipher.doFinal(testData.input);
560+
byte[] pText = cipher.doFinal(testData.input);
561+
if (!Arrays.equals(pText, testData.expOutput)) {
562+
throw new RuntimeException("FAIL: Attempted decryption " +
563+
"did not match expected plaintext");
564+
}
566565
System.out.println("First decryption complete");
567566

568567
// Initializing after the completed decryption with
569-
// the same key and nonce should fail.
570-
try {
571-
cipher.init(testData.direction, key, spec);
572-
throw new RuntimeException(
573-
"Expected InvalidKeyException not thrown");
574-
} catch (InvalidKeyException ike) {
575-
// Do nothing, this is what we expected to happen
576-
}
568+
// the same key and nonce is allowed.
569+
cipher.init(testData.direction, key, spec);
570+
System.out.println("Successful reinit in DECRYPT_MODE");
577571
} catch (Exception exc) {
578572
System.out.println("Unexpected exception: " + exc);
579573
exc.printStackTrace();

0 commit comments

Comments
 (0)