-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bouncy Castle 1.80: Encryption / Decryption not working anymore with NoIvGenerator #1985
Comments
I can confirm the behaviour described by Wolfgang. We too have existing encrypted strings that cannot be decrypted with the 1.80 version. Everything worked as expected with 1.79. I would appreciate any support on this issue. Thanks! |
Thank you for raising this issue. This issue is triggered by an improperly set IV. In earlier versions, if no IV was provided, a backup IV was generated internally. Starting from version 1.80, a validation check was introduced to ensure the IV size is correct. If an IV is missing, this check triggers an exception. To prevent this, please ensure that a valid IV is explicitly set when using Jasypt. I have added the following code to ensure the program runs properly:
We are currently discussing whether to introduce a mechanism that automatically uses a backup IV when none is provided. |
Thank you for your feedback and for looking into this. How can a message that was encrypted with BC 1.79 without explicitly setting an IV be decrypted with BC 1.80? I have not been able to do so even when I set an IV before performing the decryption. I can provide a minimal example if required. |
Yes, thank you @ligefeiBouncycastle for looking into this. I understand the issue and that it works when a RandomIvGenerator is used for encryption and decryption. However, just as @uszeiss has explained, we have messages, that have been encrypted with BC 1.79 and without the RandomIvGenerator. We need to be able to decrypt these messages with BC 1.80, otherwise we can't upgrade BC. How can we do this? Can we use the "internal backup Iv generator" that was added in 1.79? Thanks again! |
@wolfgangs-px @uszeiss Thank you for raising this critical compatibility concern. BC 1.80 introduced breaking changes for NoIvGenerator scenarios as part of our security hardening efforts in issue #1846. This change was implemented because no external IV overwrite was provided. To address this, we will be releasing a beta version soon for testing. Please note that the internal backup IV generator is not purely random. Instead, it derives a pseudo-random IV from the password and salt based on PKCS12 (RFC 7292) guidelines, using PBE.makePBEParameters. For further details, you can refer to the following: Thank you again for your feedback. |
Thank you for these additional information, @ligefeiBouncycastle . Based on your input, I was able to reconstruct the IV that was generated internally and I successfully used version 1.80 to decrypt a message encrypted with version 1.79. Is the beta version you mentioned going to provide full compatibility with 1.79 (in that we won't need to adjust our code)? |
@uszeiss Sounds good! Could you (only if easily possible) maybe share how you reconstructed the IV (by source code or explanation). It would help us and probably other developers facing this issue 😉 Thank you! |
Here's a JUnit test that decrypts a ciphertext that was encrypted using Jasypt and BC 1.79: package com.example.bc;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jcajce.provider.symmetric.util.PBE;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.contrib.org.apache.commons.codec_1_3.binary.Base64;
import org.jasypt.normalization.Normalizer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class DecryptionTest {
@Test
void decrypt_with_computed_salt_and_iv() throws Exception {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
// given
int iterationCount = 1000;
String algorithm = "PBEWithMD5AndDES";
String password = "5oddwa3FeNwvXY9y";
String encodedCiphertext = "Ky/9NyLdkJMlwWR4KcY9BIat/jzwbqBK3GWuBiPkQdY=";
String expectedPlaintext = "d1IYhc2vCFLLDfL2";
byte[] decodedCiphertext = Base64.decodeBase64(encodedCiphertext.getBytes(StandardCharsets.US_ASCII));
byte[] salt = Arrays.copyOfRange(decodedCiphertext, 0, 8); // {43, 47, -3, 55, 34, -35, -112, -109};
byte[] encryptedMessage = Arrays.copyOfRange(decodedCiphertext, 8, decodedCiphertext.length);
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
char[] normalizedPassword = Normalizer.normalizeToNfc(password.toCharArray());
PBEKeySpec pbeKeySpec = new PBEKeySpec(normalizedPassword);
SecretKey key = factory.generateSecret(pbeKeySpec);
PBEParameterSpec initialParameterSpecWithoutIv = new PBEParameterSpec(salt, iterationCount, new IvParameterSpec(new byte[0]));
ParametersWithIV ivParameters = (ParametersWithIV) PBE.Util.makePBEParameters(key.getEncoded(),
0,
0,
64,
64,
initialParameterSpecWithoutIv,
"DES/CBC");
PBEParameterSpec parameterSpecWithIv = new PBEParameterSpec(salt, iterationCount, new IvParameterSpec(ivParameters.getIV()));
Cipher decryptCipher = Cipher.getInstance(algorithm);
decryptCipher.init(Cipher.DECRYPT_MODE, key, parameterSpecWithIv);
// when
byte[] plaintextBytes = decryptCipher.doFinal(encryptedMessage);
// then
String plaintext = new String(plaintextBytes, StandardCharsets.US_ASCII);
Assertions.assertEquals(expectedPlaintext, plaintext);
}
} Note that the inputs (plaintext, password) were chosen randomly for this example and the parameters like If you want to use the Jasypt API, derive the |
@uszeiss Many thanks! |
We invite you to try BC 1.81 beta by downloading it from Bouncy Castle Betas. BC 1.81 beta is designed with backward compatibility in mind, so in theory, your existing code should work seamlessly with messages encrypted using 1.79. We’d greatly appreciate any feedback you have and any details you can share about your process. |
@ligefeiBouncycastle Thanks a lot for this beta version! I can confirm it allows us to decrypt the messages encrypted with BC 1.79 via Jasypt without any changes of our code. From this point of view, 1.81 is compatible with 1.79. Very good news! I have one test case, however, that works with 1.79 but fails with 1.81. It is not directly related to Jasypt: package com.example.bc;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class CompatibilityTest {
@Test
void minimal_example_for_incompatibility() throws Exception {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
// given
String algorithm = "PBEWithMD5AndDES";
char[] password = new char[]{'5', 'o', 'd', 'd', 'w', 'a', '3', 'F', 'e', 'N', 'w', 'v', 'X', 'Y', '9', 'y'};
byte[] salt = new byte[]{43, 47, -3, 55, 34, -35, -112, -109};
byte[] iv = new byte[]{-14, -53, 122, 86, 6, -37, 31, -93};
byte[] emptyIv = new byte[8];
byte[] message = {100, 49, 73, 89, 104, 99, 50, 118, 67, 70, 76, 76, 68, 102, 76, 50};
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
SecretKey key = factory.generateSecret(new PBEKeySpec(password));
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 1000, new IvParameterSpec(iv));
Cipher encryptCipher = Cipher.getInstance(algorithm);
encryptCipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
Cipher decryptCipher = Cipher.getInstance(algorithm);
decryptCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, 1000, new IvParameterSpec(emptyIv)));
// when
byte[] encryptedMessage = encryptCipher.doFinal(message);
byte[] decryptedMessage = decryptCipher.doFinal(encryptedMessage);
// then (works with BC 1.79, fails with BC 1.80+)
Assertions.assertEquals(new String(message), new String(decryptedMessage));
}
} This test succeeds if BC 1.79 is on the classpath, but fails if BC 1.80 or 1.8.1-SNAPSHOT is on the classpath. As you can see, with 1.79, we could use an empty IV of the expected length to decrypt a message encrypted with a non-empty IV. This is not possible in 1.80+. I'm not sure if this is an undesired behaviour in 1.79 or if this is something that is expected to still work in 1.80+. Thank you again and keep up the great work! |
Thank you for the detailed report and test case. In earlier versions, allowing an empty IV to be used for decryption—even when a non-empty IV was used during encryption—was an oversight, as the IV was overwritten by an internally generated one. With BC 1.80 and later, we enforce the use of the user’s predefined IV, except when an IV of length 0 is provided. The issue in your code arises because the IVs used during encryption and decryption do not match. To fix the code, please update the decryption initialization as follows: If the encryption was performed using the legacy behaviour (BC 1.79), you can resolve the incompatibility by either:
Thank you for your understanding and for helping us improve the library. Please let us know if you have any further questions or need additional guidance on adapting your code. |
Good to know, thanks @ligefeiBouncycastle. I adjusted my code to use an IV of length 0 for decryption and it works as expected. Looking forward to the official release of 1.81. |
Hello, I can confirm, that with the 1.81-SNAPSHOT version, the decryption of messages encrypted with 1.79 is working again in our setup. So we'll just skip 1.80 and wait for 1.81 to be officially released. Thank you very much for your quick reaction and the great support! |
Hi @ligefeiBouncycastle , when do you expect to release 1.81? |
Since bouncy castle version 1.80 the encryption and decryption for Ciphers like PBEWITHSHA256AND256BITAES-CBC-BC fail in our setup.
Example to reproduce the issue with Jasypt ( http://www.jasypt.org/bouncy-castle.html ) in Java:
The underlying exception is:
java.security.InvalidAlgorithmParameterException: IV must be 16 bytes long
We use Java 21, Jasypt 1.9.3 and org.bouncycastle:bcprov-jdk18on 1.80. Everything worked fine with bouncy castle 1.79, the error occurs since the 1.80 update.
It seems the issue is related to the IvGenerator. By default, a
NoIvGenerator
is added by Jasypt, if not specified differently.If in the above code example you add a
RandomIvGenerator
, encrpytion and decryption work fine again.But: We can not just switch to using a RandomIvGenerator, because we have stored the encrypted strings in a database and these can not be correctly decrpyted with a different IvGenerator than the NoIvGenerator used for encryption.
Why did the behaviour change and for the same algorithm there are now new requirements for the IvGenerator? Could this be fixed? Thanks!
The text was updated successfully, but these errors were encountered: