I was trying to switch security provider from SunJCE to Bouncy Castle (BC) and stumbled upon this peculiar behaviour in the Cipher object. To my knowledge the encrypted text returned by SunJCE's cipher.update(bytes) includes the subsequent initialization vector (IV) as the last block. With BC, I need to call cipher.doFinal() and take the first block to get the IV. The algorithm I'm using is AES/CBC/PKCS5Padding
Why is this happening and what's the best way to handle this?
Here's my code
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.xml.bind.DatatypeConverter.printHexBinary;
public class CipherDebug {
private final String algorithm;
private final String provider;
private final String cryptographicAlgorithm;
public CipherDebug(String algorithm,
String cipherMode,
String paddingMode,
String provider) {
this.algorithm = algorithm;
this.provider = provider;
this.cryptographicAlgorithm = algorithm + "/" + cipherMode + "/" + paddingMode;
}
private Cipher createCipher(int encryptDecryptMode,
byte[] keyValue,
byte[] initializationVector) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(keyValue, algorithm);
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
Cipher cipher = Cipher.getInstance(cryptographicAlgorithm, provider);
cipher.init(encryptDecryptMode, keySpec, ivSpec);
return cipher;
}
#Override
public String toString() {
return "CipherDebug{" +
"provider=\"" + provider + '\"' +
", cryptographicAlgorithm=\"" + cryptographicAlgorithm + '\"' +
'}';
}
private static String generateData(int length) {
char[] chars = new char[length];
Arrays.fill(chars, '0');
return new String(chars);
}
public static void main(String[] args) throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
int numberOfChunks = 3;
byte[] keyValue = Base64.getDecoder()
.decode("yY7flqEdx95dojF/yY7flqEdx95dojF/".getBytes(StandardCharsets.UTF_8));
byte[] initializationVector = "pjts4PzQIr9Pd2yb".getBytes(StandardCharsets.UTF_8);
CipherDebug bouncyCastle = new CipherDebug("AES", "CBC", "PKCS5Padding", "BC");
CipherDebug sunJCE = new CipherDebug("AES", "CBC", "PKCS5Padding", "SunJCE");
Cipher bouncyCastleCipher = bouncyCastle.createCipher(Cipher.ENCRYPT_MODE,
keyValue, initializationVector);
Cipher sunJCECipher = sunJCE.createCipher(Cipher.ENCRYPT_MODE,
keyValue, initializationVector);
assert bouncyCastleCipher.getBlockSize() == sunJCECipher.getBlockSize();
// blockSize = 16
int blockSize = bouncyCastleCipher.getBlockSize();
byte[] data = generateData(blockSize * numberOfChunks).getBytes(UTF_8);
byte[] bouncyCastleUpdate = bouncyCastleCipher.update(data);
byte[] sunJCEUpdate = sunJCECipher.update(data);
//303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030
System.out.println(printHexBinary(data));
// CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
// 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924
// 0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341
System.out.println(bouncyCastle.toString());
System.out.println(printHexBinary(bouncyCastleUpdate));
System.out.println(printHexBinary(bouncyCastleCipher.doFinal()));
System.out.println();
// CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
// 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF
// 24D2E6E7F06184A78559793B23A9A341
System.out.println(sunJCE.toString());
System.out.println(printHexBinary(sunJCEUpdate));
System.out.println(printHexBinary(sunJCECipher.doFinal()));
// assertion fails
assert Arrays.equals(bouncyCastleUpdate, sunJCEUpdate);
}
}
The output:
// data
03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030
// Bouncy Castle
CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924
0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341
// SunJCE
CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF
24D2E6E7F06184A78559793B23A9A341
The extra data in the cipher text at the end is the padding, but you need to call Cipher.doFinal() in both cases - the cipher needs to know it has all the input data before it can add or remove padding.
Cipher.getIV() will return the IV. While the IV might be generated on encryption it is never part of the actual stream and is normally passed around as a parameter or generated.
In case it's the way that the output is getting chunked up that is causing confusion, there's no "standard" for this - in the case of BC it always holds onto a block until doFinal() arrives, for some ciphers the SunJCE provider doesn't, for HSMs the input may be buffered to first to make better use of the HSM, so a succession of updates may produce nothing, then suddenly a large chunk of processed data may appear. You need to rely on the return values from the updates and doFinals to tell handle the processed data correctly.
Related
I implemented a straight simple Java utility class to encrypt and decrypt using AES/GCM/NoPadding. I use this piece of code:
public byte[] encrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.ENCRYPT_MODE);
return cipher.doFinal(input);
}
public byte[] decrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.DECRYPT_MODE);
return cipher.doFinal(input);
}
private Cipher initAES256GCMCipher(byte[] key, byte[] iv, int encryptionMode) throws Exception{
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(encryptionMode, secretKey, gcmParameterSpec);
return cipher;
}
IV is always a 12-byte array, key is 32-byte array generated with SecureRandom taking a seed. I knowthat on different OS SecureRandom is different, but encryption and decryption are performed on the same OS, so there should be no problem.
Is it quite linear, right? It works perfectly on Windows, ciphering and deciphering return the same text. However, on a Docker image, the same JAR doesn not work: encryption works fine, but decryption throws "AEADBadTagException".
Can you help me, please?
Do not use SecureRandom to derive a key. You'd use a Key Derivation Function (KDF) such as HKDF to do that. But honestly, you have to think of a way to communicate the key - without using SecureRandom.
The problem is that the - relatively secure - SHA1PRNG algorithm is not well defined. The SUN provider does accept a seed that is then used as an only seed iff you seed it before retrieving any random data from it. However, it makes sense that other providers will just mix in the seed to the state of the underlying CSPRNG. This is also the default for most other SecureRandom implementations.
Few SecureRandom implementations fully specify the way that random bits are returned even if the underlying algorithm is specified (with the DRBG). That is usually not a problem if you are just expecting random values. However, if you are using it as a deterministic algorithm such as a KDF (or hash function, for instance) then this becomes a problem and you may get different keys for the same input.
Nowadays you should be able to store AES secret keys within a key store. Not sure if that is a solution for your use case, but it should solve your current issue. Unfortunately Java doesn't contain any official KDF's other than PBKDF1 and PBKDF2 which takes a password rather than a key. Just using a HMAC-SHA256 over some key identifying data using a master key is commonly a good "poor mans KDF".
Here is a quick, initial (but undocumented) implementation that I just created. It mimics the Java JCA without actually going into implementation (as no specific KDF class is defined for it, yet).
You can ask many keys from it with any algorithm specification, as long as the size is below 256 bits (the output of SHA-256) and the label is different for each key that you request. Call getEncoded() on the key if you need data for, for instance, an IV.
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import hex.Hex;
public class PoorMansKDF {
public interface KeyDerivationParameters extends AlgorithmParameterSpec {
String getDerivedKeyAlgorithm();
int getDerivedKeySize();
byte[] getCanonicalInfo();
}
public static class KeyDerivationParametersWithLabel implements KeyDerivationParameters {
private final String algorithm;
private final int keySize;
private final String label;
public KeyDerivationParametersWithLabel(String algorithm, int keySize, String label) {
this.algorithm = algorithm;
this.keySize = keySize;
this.label = label;
}
#Override
public byte[] getCanonicalInfo() {
if (label == null) {
// array without elements
return new byte[0];
}
return label.getBytes(StandardCharsets.UTF_8);
}
#Override
public String getDerivedKeyAlgorithm() {
return algorithm;
}
#Override
public int getDerivedKeySize() {
return keySize;
}
}
private enum State {
CONFIGURED,
INITIALIZED;
}
public static PoorMansKDF getInstance() throws NoSuchAlgorithmException {
return new PoorMansKDF();
}
private final Mac hmac;
private State state;
private PoorMansKDF() throws NoSuchAlgorithmException {
this.hmac = Mac.getInstance("HMACSHA256");
this.state = State.CONFIGURED;
}
public void init(Key key) throws InvalidKeyException {
if (key.getAlgorithm().equalsIgnoreCase("HMAC")) {
key = new SecretKeySpec(key.getEncoded(), "HMAC");
}
hmac.init(key);
this.state = State.INITIALIZED;
}
public Key deriveKey(KeyDerivationParameters info) {
if (state != State.INITIALIZED) {
throw new IllegalStateException("Not initialized");
}
final int keySize = info.getDerivedKeySize();
if (keySize < 0 || keySize % Byte.SIZE != 0 || keySize > hmac.getMacLength() * Byte.SIZE) {
throw new IllegalArgumentException("Required key incompatible with this KDF");
}
final int keySizeBytes = keySize / Byte.SIZE;
// we'll directly encode the info to bytes
byte[] infoData = info.getCanonicalInfo();
final byte[] fullHMAC = hmac.doFinal(infoData);
final byte[] derivedKeyData;
if (fullHMAC.length == keySizeBytes) {
derivedKeyData = fullHMAC;
} else {
derivedKeyData = Arrays.copyOf(fullHMAC, keySizeBytes);
}
SecretKey derivedKey = new SecretKeySpec(derivedKeyData, info.getDerivedKeyAlgorithm());
Arrays.fill(derivedKeyData, (byte) 0x00);
if (fullHMAC != derivedKeyData) {
Arrays.fill(fullHMAC, (byte) 0x00);
}
return derivedKey;
}
// test only
public static void main(String[] args) throws Exception {
var kdf = PoorMansKDF.getInstance();
// input key (zero byte key for testing purposes, use your own 16-32 byte key)
// do not use a password here!
var masterKey = new SecretKeySpec(new byte[32], "HMAC");
kdf.init(masterKey);
// here "enc" is a label, in this case for a derived key for ENCryption
var labeledParameters = new KeyDerivationParametersWithLabel("AES", 256, "enc");
var derivedKey = kdf.deriveKey(labeledParameters);
// use your own hex decoder, e.g. from Apache Commons
System.out.println(Hex.encode(derivedKey.getEncoded()));
var aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
var gcmParams = new GCMParameterSpec(128, new byte[12]);
aesCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmParams);
var ct = aesCipher.doFinal();
System.out.println(Hex.encode(ct));
}
}
In my Java code, I'm trying to encrypt a String using RSA, with a public key. The String is a Base64 encoded String that represents an Image (Image was converted to String). It will be decrypted using a private key.
During the Encryption, I first got an exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes". So, I processed the String (plaintext) in blocks of 189 which then resolved it.
During the Decryption, I got another exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes". So, I processed the byte[] (ciphertext), by converting it to a String first, in blocks of 256 which then resolved it as well.
Again, during my decryption process, I end up getting a "javax.crypto.BadPaddingException: Decryption error" Exception, which I have been unable to resolve.
Upon the recommendation of experts on this site, I used "OAEPWithSHA-256AndMGF1Padding". I even tried using No Padding, after other padding methods, to see if the Exception would go away, but it did not work. What have I done wrong?
I was able to identify that the Exception was thrown at the line - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
- which is in the decryption portion of the main method.
Please bear with me if my coding practices are poor. I'd really prefer to just find out the error behind the exception for now.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
public class Tester
{
public KeyPair buildKeyPair() throws NoSuchAlgorithmException
{
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public byte[] encrypt(PublicKey publicKey, String message) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes());
}
public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(encrypted));
}
public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
{
byte[] cipherText = encrypt(publicKey, watermarkMsg);
return cipherText;
}
public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
{
String plainText = decrypt(privateKey, cipherText);
return plainText;
}
public static void main(String args[]) throws NoSuchAlgorithmException
{
Tester t = new Tester();
String inputImageFilePath = "<file_path_here";
String stringOfImage = null;
byte[] encryptedImage = null;
byte[] encryptedImagePartial = null;
KeyPair keyPair = t.buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate()
//-----------IMAGE TO STRING CONVERSION----------------
//The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String
try
{
stringOfImage = t.imagetostring(inputImageFilePath);
}
catch(Exception e)
{
System.out.println(e.toString());
}
//-----------ENCRYPTION OF STRING----------------
//The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
try
{
String plaintext = stringOfImage;
String plaintextTrimmed = "";
System.out.println(stringOfImage);
encryptedImage = new byte[15512]; //The size is given as 15512 because the length of the particular string was found to be 15512
while(plaintext!="")
{
if(plaintext.length()>189)
{
plaintextTrimmed = plaintext.substring(0, 189);
plaintext = plaintext.substring(189);
}
else
{
plaintextTrimmed = plaintext;
plaintext = "";
}
encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
System.out.println(encryptedImage.length);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
t.byteDigest(encryptedImage);
//-----------DECRYPTION OF STRING--------------
//The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
try
{
// The ciphertext is located in the variable encryptedImage which is a byte[]
String stringRepOfCipherText = new String(encryptedImage); String cipherTextTrimmed = "";
String decryptedImagePartial;
String decryptedImage = "";
while(stringRepOfCipherText!="")
{
if(stringRepOfCipherText.length()>189)
{
cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
stringRepOfCipherText = stringRepOfCipherText.substring(189);
}
else
{
cipherTextTrimmed = stringRepOfCipherText;
stringRepOfCipherText = "";
}
decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
decryptedImage = decryptedImage + decryptedImagePartial;
}
}
catch(BadPaddingException e)
{
System.out.println(e.toString());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
Also, I noticed a few other examples where KeyFactory was used to generate the keys. Could anyone also tell me the difference between using KeyFactory and what I have used?
You can not cut the ciphertext into arbitrary chunks!
Since you specifically asked for plain RSA without symmetric algorithms involved (which I strongly recommend against!), this is what you need to do:
Find out the maximum payload size for your RSA configuration.
Split your plaintext into chunks of this size
Encrypt each chunk individually and do not simply concatenate them and discard chunk boundaries!
During decryption:
Pass each ciphertext chunk to the decrypt function using the original size it has after encryption. Do not append any data and do not create "substrings".
Concatenate the resulting plaintexts.
Ideally you should use a hybrid encryption scheme:
generate an encryption key (encKey)
encrypt your image using a symmetric algorithm with encKey
encrypt encKey using pubKey with RSA
Symmetric ciphers can be used in different modes of operation, that avoid such length limitations.
First of all, it makes absolutely no sense to first encode the image to base 64. The input of modern ciphers consist of bytes, and images are already bytes. You may want to base 64 encode the ciphertext if you want to store that a string.
The input block size is indeed 190 bytes. You can see a table for RSA / OAEP here (don't forget to upvote!). I'm not sure why you would want to use 189 in that case; my code is however generalized. The output block size is simply the key size for RSA as it is explicitly converted to the key size in bytes (even if it could be smaller).
During decryption you convert the ciphertext to a string. However, string decoding in Java is lossy; if the decoder finds a byte that doesn't represent a character then it is dropped silently. So this won't (always work), resulting for instance in a BadPaddingException. That's OK though, we can keep to binary ciphertext.
So without further ado, some code for you to look at. Note the expansion of the ciphertext with the 66 bytes per block and the poor performance of - mainly - the decryption. Using AES with RSA in a hybrid cryptosystem is highly recommended (and not for the first time for this question).
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import javax.crypto.Cipher;
public class Tester {
private static final int KEY_SIZE = 2048;
private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static void main(String args[]) throws Exception {
KeyPair keyPair = Tester.buildKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// assumes the bitLength is a multiple of 8 (check first!)
int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;
byte[] image = new byte[1000];
Arrays.fill(image, (byte) 'm');
// --- encryption
final Cipher enc;
try {
enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
}
enc.init(Cipher.ENCRYPT_MODE, pubKey);
int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;
ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
int off = 0;
while (off < image.length) {
int toCrypt = Math.min(fragmentsize, image.length - off);
byte[] partialCT = enc.doFinal(image, off, toCrypt);
ctStream.write(partialCT);
off += toCrypt;
}
byte[] ct = ctStream.toByteArray();
// --- decryption
Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
dec.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
off = 0;
while (off < ct.length) {
int toCrypt = Math.min(keySizeBytes, ct.length - off);
byte[] partialPT = dec.doFinal(ct, off, toCrypt);
ptStream.write(partialPT);
off += toCrypt;
}
byte[] pt = ptStream.toByteArray();
// mmmm...
System.out.println(new String(pt, StandardCharsets.US_ASCII));
}
}
I'd like to use the PBEWITHHMACSHA256ANDAES_256 algorithm but it's not supported. I've added the bouncy castle provider to my test in the hopes that it will work but to no avail. Can anyone please tell me how to fix the test below such that PBEWITHHMACSHA256ANDAES is added to the Supported list?
import java.security.Security;
import java.util.Set;
import java.util.TreeSet;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import org.jasypt.registry.AlgorithmRegistry;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class EncryptionTest {
#BeforeClass
public static void beforeClass() {
Security.addProvider(new BouncyCastleProvider());
}
#Test
public void test() {
Set<String> supported = new TreeSet<>();
Set<String> unsupported = new TreeSet<>();
for (Object oAlgorithm : AlgorithmRegistry.getAllPBEAlgorithms()) {
String algorithm = (String) oAlgorithm;
try {
SimpleStringPBEConfig pbeConfig = new SimpleStringPBEConfig();
pbeConfig.setAlgorithm(algorithm);
pbeConfig.setPassword("changeme");
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setConfig(pbeConfig);
String encrypted = encryptor.encrypt("foo");
String decrypted = encryptor.decrypt(encrypted);
Assert.assertEquals("foo", decrypted);
supported.add(algorithm);
} catch (EncryptionOperationNotPossibleException e) {
unsupported.add(algorithm);
}
}
System.out.println("Supported");
supported.forEach((String alg) -> System.out.println(" " + alg));
System.out.println("Unsupported");
unsupported.forEach((String alg) -> System.out.println(" " + alg));
}
}
Output:
Supported
PBEWITHMD2ANDDES
PBEWITHMD5AND128BITAES-CBC-OPENSSL
PBEWITHMD5AND192BITAES-CBC-OPENSSL
PBEWITHMD5AND256BITAES-CBC-OPENSSL
PBEWITHMD5ANDDES
PBEWITHMD5ANDRC2
PBEWITHSHA1ANDDES
PBEWITHSHA1ANDDESEDE
PBEWITHSHA1ANDRC2
PBEWITHSHA1ANDRC2_128
PBEWITHSHA1ANDRC2_40
PBEWITHSHA1ANDRC4_128
PBEWITHSHA1ANDRC4_40
Unsupported
PBEWITHHMACSHA1ANDAES_128
PBEWITHHMACSHA1ANDAES_256
PBEWITHHMACSHA224ANDAES_128
PBEWITHHMACSHA224ANDAES_256
PBEWITHHMACSHA256ANDAES_128
PBEWITHHMACSHA256ANDAES_256
PBEWITHHMACSHA384ANDAES_128
PBEWITHHMACSHA384ANDAES_256
PBEWITHHMACSHA512ANDAES_128
PBEWITHHMACSHA512ANDAES_256
PBEWITHMD5ANDTRIPLEDES
PBEWITHSHA256AND128BITAES-CBC-BC
PBEWITHSHA256AND192BITAES-CBC-BC
PBEWITHSHA256AND256BITAES-CBC-BC
PBEWITHSHAAND128BITAES-CBC-BC
PBEWITHSHAAND128BITRC2-CBC
PBEWITHSHAAND128BITRC4
PBEWITHSHAAND192BITAES-CBC-BC
PBEWITHSHAAND2-KEYTRIPLEDES-CBC
PBEWITHSHAAND256BITAES-CBC-BC
PBEWITHSHAAND3-KEYTRIPLEDES-CBC
PBEWITHSHAAND40BITRC2-CBC
PBEWITHSHAAND40BITRC4
PBEWITHSHAANDIDEA-CBC
PBEWITHSHAANDTWOFISH-CBC
* Edit *
#EbbeMPedersen suggested that this algorithm is provided by SunJCE but I can see that SunJCE provider is enabled using the following code
for (Provider provider : Security.getProviders()) {
System.out.println(provider.getName() + " " + provider.getClass().getName());
}
Output
SUN sun.security.provider.Sun
SunRsaSign sun.security.rsa.SunRsaSign
SunEC sun.security.ec.SunEC
SunJSSE com.sun.net.ssl.internal.ssl.Provider
SunJCE com.sun.crypto.provider.SunJCE
SunJGSS sun.security.jgss.SunProvider
SunSASL com.sun.security.sasl.Provider
XMLDSig org.jcp.xml.dsig.internal.dom.XMLDSigRI
SunPCSC sun.security.smartcardio.SunPCSC
SunMSCAPI sun.security.mscapi.SunMSCAPI
BC org.bouncycastle.jce.provider.BouncyCastleProvider
I think the issue is that Jasypt is not able to retain the derived AlgorithmParameters and re-use them when switching the cipher between ENCRYPT and DECRYPT. The underlying exception that Jasypt is obscuring is java.security.InvalidAlgorithmParameterException: Missing parameter type: IV expected, but if you provide the salt (or SaltGenerator in this case) and key obtention iterations (i.e. the number of times to invoke the HMAC/SHA-256 "key digest function"), Jasypt still complains because it doesn't understand how to pass the calculated parameters to the decrypt cipher. (You can verify this by debugging your test and putting a breakpoint in StandardPBEByteEncryptor.java at line 1055 (Jasypt 1.9.2)) -- here you can catch the underlying exception and use the expression window to verify that:
encryptCipher.getParameters().getEncoded() -> 306206092a864886f70d01050d3055303406092a864886f70d01050c30270410f5a439e8dc12642972dbbf3e1867edaf020203e8020120300c06082a864886f70d02090500301d060960864801650304012a0410caacd97ae953ae257b1b4a0bb70ccc2e
but
decryptCipher.getParameters().getEncoded() -> java.security.ProviderException: Could not construct CipherSpi instance
Here is a (Groovy) test that demonstrates the failure of Jasypt and the successful method of using your desired algorithm (note: 1000 iterations is not sufficient for robust security against modern hardware, but for demonstration purposes only):
#Test
void testShouldUseHS256andAES256() {
// Arrange
String algorithm = "PBEwithHMACSHA256andAES_256";
SimpleStringPBEConfig pbeConfig = new SimpleStringPBEConfig();
pbeConfig.setAlgorithm(algorithm);
pbeConfig.setPassword("changeme");
// Need an IV (derived from salt and iteration count)
// pbeConfig.setKeyObtentionIterations(1000);
// pbeConfig.setSaltGenerator(new RandomSaltGenerator());
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setConfig(pbeConfig);
// Act
def msg = shouldFail(Exception) {
String encrypted = encryptor.encrypt("foo");
// Assert
String decrypted = encryptor.decrypt(encrypted);
Assert.assertEquals("foo", decrypted);
}
logger.info("Expected: ${msg}")
// Required way
Cipher rawCipher = Cipher.getInstance(algorithm)
PBEKeySpec pbeKeySpec = new PBEKeySpec("changeme" as char[])
final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
SecretKey tempKey = factory.generateSecret(pbeKeySpec);
PBEParameterSpec saltParameterSpec = new PBEParameterSpec(Hex.decodeHex("0123456789ABCDEF" as char[]), 1000)
rawCipher.init(Cipher.ENCRYPT_MODE, tempKey, saltParameterSpec)
// Save the generated ASN.1-encoded parameters
byte[] algorithmParameterBytes = rawCipher.getParameters().encoded
byte[] cipherBytes = rawCipher.doFinal("foo".getBytes(StandardCharsets.UTF_8))
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm)
algorithmParameters.init(algorithmParameterBytes)
rawCipher.init(Cipher.DECRYPT_MODE, tempKey, algorithmParameters)
byte[] plainBytes = rawCipher.doFinal(cipherBytes)
String recovered = new String(plainBytes, StandardCharsets.UTF_8)
assert recovered == "foo"
}
I'm trying to implement a function that receives a string and returns the encoded values of the String in CAST-256. The following code is what i implement following the example on BoncyCastle official web page (http://www.bouncycastle.org/specifications.html , point 4.1).
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.CAST6Engine;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
public class Test {
static{
Security.addProvider(new BouncyCastleProvider());
}
public static final String UTF8 = "utf-8";
public static final String KEY = "CLp4j13gADa9AmRsqsXGJ";
public static byte[] encrypt(String inputString) throws UnsupportedEncodingException {
final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CAST6Engine());
byte[] key = KEY.getBytes(UTF8);
byte[] input = inputString.getBytes(UTF8);
cipher.init(true, new KeyParameter(key));
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int outputLen = cipher.processBytes(input, 0, input.length, cipherText, 0);
try {
cipher.doFinal(cipherText, outputLen);
} catch (CryptoException ce) {
System.err.println(ce);
System.exit(1);
}
return cipherText;
}
public static void main(String[] args) throws UnsupportedEncodingException {
final String toEncrypt = "hola";
final String encrypted = new String(Base64.encode(test(toEncrypt)),UTF8);
System.out.println(encrypted);
}
}
But , when i run my code i get
QUrYzMVlbx3OK6IKXWq1ng==
and if you encode hola in CAST-256 with the same key ( try here if you want http://www.tools4noobs.com/online_tools/encrypt/) i should get
w5nZSYEyA8HuPL5V0J29Yg==.
What is happening? Why im getting a wront encrypted string?
I'm tired of find that on internet and didnt find a answer.
Bouncy Castle uses PKCS #7 padding by default, while PHP's mcrypt (and the web site you linked) uses zero padding by default. This causes the different ciphertexts.
Please note that the ECB mode used here is not secure for almost any use. Additionally, I hope the secret key you posted is not the real key, because now that it's not secret anymore, all this encryption is useless.
This doesn't really answer your question, but it does provide some pointers.
You need to do a little digging to ensure you are decrypting in exactly the same way as PHP's mcrypt(). You need to make sure your key generation, encoding/decoding and cipher algorithm match exactly.
Keys
"CLp4j13gADa9AmRsqsXGJ".getBytes("UTF-8");
is probably not the right way to create the key source bytes. The docs seem to indicate that mcrypt() pads the key and data with \0 if it isn't the right size. Note that your method produces a 168 bit key, which is not a valid key size and I'm not sure what java is going to do about it.
Algorithm
Make sure the cipher mode and padding are the same. Does mcrypt() use ECB, CBC, something else?
Encoding
Ciphers work on bytes, not Strings. Make sure your conversion between the two is the same in java and PHP.
Here is a reference test for CAST6 using test vectors from https://www.rfc-editor.org/rfc/rfc2612#page-10. Note the key, ciphertext and plaintext are hex encoded.
import java.security.Provider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Cast6 {
static final String KEY_ALGO = "CAST6";
static final String CIPHER_ALGO = "CAST6/ECB/NOPADDING";
static String keytext = "2342bb9efa38542c0af75647f29f615d";
static String plaintext = "00000000000000000000000000000000";
static String ciphertext = "c842a08972b43d20836c91d1b7530f6b";
static Provider bc = new BouncyCastleProvider();
public static void main(String[] args) throws Exception {
System.out.println("encrypting");
String actual = encrypt();
System.out.println("actual: " + actual);
System.out.println("expect: " + ciphertext);
System.out.println("decrypting");
actual = decrypt();
System.out.println("actual: " + actual);
System.out.println("expect: " + plaintext);
}
static String encrypt() throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_ALGO, bc);
byte[] keyBytes = Hex.decodeHex(keytext.toCharArray());
SecretKeySpec key = new SecretKeySpec(keyBytes, KEY_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] input = Hex.decodeHex(plaintext.toCharArray());
byte[] output = cipher.doFinal(input);
String actual = Hex.encodeHexString(output);
return actual;
}
static String decrypt() throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_ALGO, bc);
byte[] keyBytes = Hex.decodeHex(keytext.toCharArray());
SecretKeySpec key = new SecretKeySpec(keyBytes, KEY_ALGO);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] output = cipher.doFinal(Hex.decodeHex(ciphertext.toCharArray()));
String actual = Hex.encodeHexString(output);
return actual;
}
}
Edit
Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.
Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE api is kind of misleading depending on the chosen algorithm
Original post
As cryptography in Java is quite... cryptographic, im struggling to do some optimizations. In the functional aspect, this class works quite well and i hope it serves as an example of AES encryption usage
I have a performance issue when encrypting and decrypting data using AES implementation of BouncyCastle (im not comparing, thats the only one implementation I tested). Actually this problem is generic to any cipher I decide to use.
The main issue is: can i avoid the two ciphers whole re-initialization per encrypt/decrypt call? They are too expensive
For the sake of simplicity, keep in mind that the following code had its exception handling removed and a lot of simplification was made to keep the focus on the problem. The synchronized blocks are there to guarantee thread safety
By the way, feedbacks on any part of the code are welcome
Thx
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AES {
private static final int ITERATIONS = 120000;
private static final int SALT_SIZE_IN_BYTES = 8;
private static final String algorithm = "PBEWithSHA256And128BitAES-CBC-BC";
private static final byte[] KEY_SALT = "a fixed key salt".getBytes(Charset.forName("UTF-8"));
private Cipher encryptCipher;
private Cipher decryptCipher;
private SecretKey key;
private RandomGenerator randomGenerator = new RandomGenerator();
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
Security.addProvider(new BouncyCastleProvider());
}
public AES(String passphrase) throws Exception {
encryptCipher = Cipher.getInstance(algorithm);
decryptCipher = Cipher.getInstance(algorithm);
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), KEY_SALT, ITERATIONS);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
key = keyFactory.generateSecret(keySpec);
}
public byte[] encrypt(byte[] data) throws Exception {
byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
data = DataUtil.append(data, salt);
byte[] encrypted;
synchronized (encryptCipher) {
// as a security constrain, it is necessary to use different salts per encryption
// core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
encryptCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, parameterSpec);
encrypted = encryptCipher.doFinal(data);
}
return DataUtil.append(encrypted, salt);
}
public byte[] decrypt(byte[] data) throws Exception {
byte[] salt = extractSaltPart(data);
data = extractDataPart(data);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
byte[] decrypted;
synchronized (decryptCipher) {
// as a security constrain, it is necessary to use different salts per encryption
// core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
decryptCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, parameterSpec);
decrypted = decryptCipher.doFinal(data);
}
byte[] decryptedSalt = extractSaltPart(decrypted);
if (Arrays.equals(salt, decryptedSalt))
return extractDataPart(decrypted);
else
throw new IllegalArgumentException("Encrypted data is corrupted: Bad Salt");
}
protected byte[] extractDataPart(byte[] bytes) {
return DataUtil.extract(bytes, 0, bytes.length - SALT_SIZE_IN_BYTES);
}
protected byte[] extractSaltPart(byte[] bytes) {
return DataUtil.extract(bytes, bytes.length - SALT_SIZE_IN_BYTES, SALT_SIZE_IN_BYTES);
}
// main method to basic check the code execution
public static void main(String[] args) throws Exception {
String plainText = "some plain text, have fun!";
String passphrase = "this is a secret";
byte[] data = plainText.getBytes(Charset.forName("UTF-8"));
AES cipher = new AES(passphrase);
byte[] encrypted = cipher.encrypt(data);
byte[] decrypted = cipher.decrypt(encrypted);
System.out.println("expected: true, actual: " + Arrays.equals(data, decrypted));
}
}
// Utility class
class RandomGenerator {
private SecureRandom random = new SecureRandom();
public RandomGenerator() {
random = new SecureRandom();
random.nextBoolean();
}
public synchronized byte[] generateRandom(int length) {
byte[] data = new byte[length];
random.nextBytes(data);
return data;
}
}
// Utility class
class DataUtil {
public static byte[] append(byte[] data, byte[] append) {
byte[] merged = new byte[data.length + append.length];
System.arraycopy(data, 0, merged, 0, data.length);
System.arraycopy(append, 0, merged, data.length, append.length);
return merged;
}
public static byte[] extract(byte[] data, int start, int length) {
if (start + length > data.length)
throw new IllegalArgumentException("Cannot extract " + length + " bytes starting from index " + start + " from data with length " + data.length);
byte[] extracted = new byte[length];
System.arraycopy(data, start, extracted, 0, length);
return extracted;
}
}
You're out of luck. If you're picking a new salt each time, that means you're using a new key for encryption/decryption each time, which means you need to call init each time.
If you want something faster, just salt your message:
byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
encryptCipher.update(salt);
encrypted = encryptCipher.doFinal(data);
That way, you use the same key every time so you don't need to reinitialize it. (Don't use PBE, just use 128 bit AES/CBC). It's hard to know if that is adequate for your needs without knowing how you plan to apply this encryption in the real world.
p.s. ITERATIONS == 120000? No wonder it is so slow.
Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.
Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE API is kind of misleading depending on the chosen algorithm
About the salt: Adding a salt to the plain message is completely unnecessary. What I really should use to achieve randomness in each encryption is using a random Initialization Vector, that can be appended or prepended to the ciphertext after the encryption just like the salt.
If you're just transmitting data and need it to be encrypted in flight, use TLS/SSL. Its way faster and won't break.
Make Sure you use some authentication on your cipher-text. Either use a MAC or better use AES in GCM or CCM modes. Otherwise you encryption is insecure.
As to your question : yes its fixable.
Just generate the key once and reuse it/ . AES is safe to use to send multiple messages with the same key. So just derive the key/Cipher once and keep using it.
Just make sure you use a fresh IV for each message.