Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 9 additions & 102 deletions src/main/java/com/ibm/crypto/plus/provider/ChaCha20Cipher.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright IBM Corp. 2023
* Copyright IBM Corp. 2023, 2025
*
* This code is free software; you can redistribute it and/or modify it
* under the terms provided by IBM in the LICENSE file that accompanied
Expand Down Expand Up @@ -40,26 +40,6 @@ public final class ChaCha20Cipher extends CipherSpi implements ChaCha20Constants
private boolean encrypting = false;
private boolean initialized = false;

// Java 8 Cipher.class documentation does not require that a cipher.init is
// called between successive encryption or decryption. However, it requires
// prior IV+ Key cannot be used. Since it is not feasible to maintain a history
// of previously called IV+Key combination, this implementation checks the
// previous encryption. The exception to this requirement is when
// an SBE was encountered, the Cipher class allows same IV and Key but with a
// larger buffer.
// Calling encryption/decryption after a sbe is allowed which allows applications
// to call failed operation with a larger buffer
// This implementation deviates from Sun's implementation.

private byte[] lastEncKey = null; //last encryption Key
private byte[] lastEncNonce = null; // last encryption Nonce

// Keeps track if a shortBufferException was experienced in last call.
private boolean sbeInLastFinalEncrypt = false;

private boolean initCalledInEncSeq = false;
private boolean generateIV = false;
SecureRandom random = null;

public ChaCha20Cipher(OpenJCEPlusProvider provider) {
if (!OpenJCEPlusProvider.verifySelfIntegrity(this.getClass())) {
Expand All @@ -74,30 +54,9 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)

checkCipherInitialized();

// Generate IV only when Init was not called in this seq and an init was called
// during prior encryption without specifying params.
if ((!initCalledInEncSeq) && (!sbeInLastFinalEncrypt) && (generateIV) && (encrypting)) {
this.nonceBytes = generateRandomNonce(random).clone();
this.counter = this.counter + 1;
}

// The checks are performed only for successive encryption, and when Init
// operation was not called
// since iv must not be changed between encryption and decryption.
// performing two successive decryption with the same IV + Key is allowed.
if ((!initCalledInEncSeq) && (!sbeInLastFinalEncrypt) && (encrypting)) {
boolean sameKeyIv = checkKeyAndNonce(keyBytes, nonceBytes, lastEncKey, lastEncNonce);
if (sameKeyIv) {
resetVarsAfterException();
throw new IllegalStateException("Cannot reuse iv for ChaCha20Poly1305 encryption");
}
}

try {
byte[] output = new byte[engineGetOutputSize(inputLen)];

int outputLen = symmetricCipher.doFinal(input, inputOffset, inputLen, output, 0);
initCalledInEncSeq = false;
if (outputLen < output.length) {
byte[] out = Arrays.copyOfRange(output, 0, outputLen);
if (!encrypting) {
Expand All @@ -108,23 +67,17 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
return output;
}
} catch (BadPaddingException ock_bpe) {
resetVarsAfterException();
BadPaddingException bpe = new BadPaddingException(ock_bpe.getMessage());
provider.setOCKExceptionCause(bpe, ock_bpe);
throw bpe;
} catch (IllegalBlockSizeException ock_ibse) {
resetVarsAfterException();
IllegalBlockSizeException ibse = new IllegalBlockSizeException(ock_ibse.getMessage());
provider.setOCKExceptionCause(ibse, ock_ibse);
throw ibse;
} catch (Exception e) {
resetVarsAfterException();
throw provider.providerException("Failure in engineDoFinal", e);
} finally {
if (encrypting) {
lastEncKey = keyBytes.clone();
lastEncNonce = nonceBytes.clone();
}
resetVars();
}
}

Expand All @@ -135,59 +88,26 @@ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[]

checkCipherInitialized();

// Generate IV only when Init was not called in this seq and an init was called during prior encryption
// without specifying params.
if ((!initCalledInEncSeq) && (!sbeInLastFinalEncrypt) && (generateIV) && (encrypting)) {
this.nonceBytes = generateRandomNonce(random).clone();
this.counter = this.counter + 1;
}

// The checks are performed only for successive encryption, and when Init operation was not called
// since iv must not be changed between encryption and decryption.
// performing two successive decryption with the same IV + Key is allowed.
if ((!initCalledInEncSeq) && (!sbeInLastFinalEncrypt) && (encrypting)) {
boolean sameKeyIv = checkKeyAndNonce(keyBytes, nonceBytes, lastEncKey, lastEncNonce);
if (sameKeyIv) {
resetVarsAfterException();
throw new IllegalStateException("Cannot reuse iv for ChaCha20Poly1305 encryption");
}
}

try {
int ret = symmetricCipher.doFinal(input, inputOffset, inputLen, output, outputOffset);
sbeInLastFinalEncrypt = false;
this.initCalledInEncSeq = false;
return ret;
} catch (BadPaddingException ock_bpe) {
resetVarsAfterException();
BadPaddingException bpe = new BadPaddingException(ock_bpe.getMessage());
provider.setOCKExceptionCause(bpe, ock_bpe);
throw bpe;
} catch (IllegalBlockSizeException ock_ibse) {
resetVarsAfterException();

IllegalBlockSizeException ibse = new IllegalBlockSizeException(ock_ibse.getMessage());
provider.setOCKExceptionCause(ibse, ock_ibse);
throw ibse;
} catch (ShortBufferException ock_sbe) {
ShortBufferException sbe = new ShortBufferException(ock_sbe.getMessage());
provider.setOCKExceptionCause(sbe, ock_sbe);
sbeInLastFinalEncrypt = encrypting;
throw sbe;
} catch (Exception e) {

resetVarsAfterException();
throw provider.providerException("Failure in engineDoFinal", e);
} finally {
// Do not reset this.initialized in final block
// Calling applications can decrypt or encrypt after a successful completion.
// Only IV need to change for Encryption
// Save Keys and Nonce only for encryption. applications must be able to call decrypt after
// an encrypt with the same key and iv
if (encrypting) {
lastEncKey = keyBytes.clone();
lastEncNonce = nonceBytes.clone();
}
resetVars();
}
}

Expand Down Expand Up @@ -230,14 +150,11 @@ protected AlgorithmParameters engineGetParameters() {

@Override
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
this.initialized = false;

if (opmode == Cipher.DECRYPT_MODE) {
throw new InvalidKeyException("Parameters missing");
}
this.random = random;
this.initialized = false;
this.sbeInLastFinalEncrypt = false;
generateIV = (opmode == Cipher.ENCRYPT_MODE);

internalInit(opmode, key, generateRandomNonce(random), 1);
}
Expand All @@ -246,13 +163,10 @@ protected void engineInit(int opmode, Key key, SecureRandom random) throws Inval
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
this.initialized = false;
this.sbeInLastFinalEncrypt = false;

if (params == null) {
engineInit(opmode, key, random);
} else {
generateIV = false; // Use IV from params
this.random = random;

if (params instanceof ChaCha20ParameterSpec) {
byte[] nonce = ((ChaCha20ParameterSpec) params).getNonce();
if (nonce.length != ChaCha20_NONCE_SIZE) {
Expand All @@ -271,6 +185,7 @@ protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
this.initialized = false;

if (params == null) {
engineInit(opmode, key, random);
Expand All @@ -281,8 +196,6 @@ protected void engineInit(int opmode, Key key, AlgorithmParameters params, Secur

private void internalInit(int opmode, Key newKey, byte[] newNonceBytes, int newCounter)
throws InvalidKeyException {

this.initialized = false;
if ((opmode == Cipher.WRAP_MODE) || (opmode == Cipher.UNWRAP_MODE)) {
throw new UnsupportedOperationException("WRAP_MODE and UNWRAP_MODE are not supported");
} else if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
Expand Down Expand Up @@ -336,10 +249,7 @@ private void internalInit(int opmode, Key newKey, byte[] newNonceBytes, int newC
this.counter = newCounter;
this.ivBytes = newIvBytes;
this.initialized = true;
this.initCalledInEncSeq = isEncrypt;
} catch (Exception e) {
this.initialized = false;
this.initCalledInEncSeq = false;
throw provider.providerException("Failed to init cipher", e);
}

Expand Down Expand Up @@ -456,12 +366,9 @@ private void checkCipherInitialized() throws IllegalStateException {
}
}

// Reset class variables after an exception
private void resetVarsAfterException() {
// force re-initialization presumably with different nonce and key
this.initialized = false;
this.sbeInLastFinalEncrypt = false;
this.initCalledInEncSeq = false;
// Reset class variables.
private void resetVars() {
this.initialized = (!this.encrypting); // force re-initialization only when encrypting
}

private byte[] generateRandomNonce(SecureRandom random) {
Expand Down
Loading
Loading