Using Java to Decrypt Openssl Aes-256-Cbc Using Provided Key and Iv

Java AES Decryption with keyFile using BouncyCastle SSL

When using a password, OpenSSL stores the ciphertext in a specific format, namely the ASCII encoding of Salted__, followed by the 8 bytes salt, then the actual ciphertext.

During decryption, the salt must not be randomly generated (as it is done in the posted code), otherwise the wrong key and IV will be derived. Instead, the salt must be determined from the metadata of the ciphertext. Also the use of the stream classes must be fixed:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;

...

String inputPath = "..."; // path to enc file
String outputPath = "..."; // path to dec file
String passwordStr = "...";

// Decrypt with AES-256, CBC using streams
try (FileInputStream fis = new FileInputStream(inputPath)){

// Determine salt from OpenSSL format
fis.readNBytes(8); // Skip prefix Salted__
byte[] salt = fis.readNBytes(8); // Read salt

// Derive 32 bytes key (AES_256) and 16 bytes IV via EVP_BytesToKey()
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits

// Decrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
FileOutputStream fos = new FileOutputStream(outputPath)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}

This is functionally equivalent to the OpenSSL statement:

openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>

Note that OpenSSL applied MD5 as digest by default in earlier versions and SHA256 as of v.1.1.0. Code and OpenSSL statement must use the same digest for compatibility.

In the code the digest is explicitly specified, in the OpenSSL statement it can be explicitly set via the -md option so that matching is possible on both sides.

Keep in mind that EVP_BytesToKey(), which is used by default by OpenSSL for key derivation, is deemed insecure nowadays.


Addition regarding Java 8: For Java 8, e.g. the following implementation can be applied for the determination of the salt:

int i = 0;
byte[] firstBlock = new byte[16];
while (i < firstBlock.length) {
i += fis.read(firstBlock, i, firstBlock.length - i);
}
byte[] salt = Arrays.copyOfRange(firstBlock, 8, 16);

The loop is necessary because read(byte[],int,int), unlike readNBytes(int), does not guarantee that the buffer is completely filled (considering here the non-EOF and non-error case).

If you omit the loop (which means using the equivalent read(byte[])), the code will still run for those JVMs which also fill the buffer completely. Since this applies to the most common JVMs for small buffer sizes the code will mostly work, see the comment by dave_thompson_085. However, this is not guaranteed for any JVM and is therefore less robust (though probably not by much).

Decrypt file encrypted using openssl with aes-cbc-256

You have several problems. The most obvious is that you are trying to read the IV from the file, but openssl enc in its default password-based mode derives both key and IV from password and salt -- even when using PBKDF2. However, both the standard (Sun/Oracle/OpenJDK) and BouncyCastle providers in Java implement PBKDF2 to derive only a key -- the way it is used in PBES2.

Even without that, your method of generating the 'password' as random bytes wouldn't work either. The PKCS5 standard actually defines PBKDF2 to take the password as

an octet
string of arbitrary length whose interpretation as a text string is
unspecified. In the interest of interoperability, however, it is
recommended that applications follow some common text encoding rules.
ASCII and UTF-8 [RFC3629] are two possibilities. (ASCII is a subset
of UTF-8.)

Many systems take interoperable encoding more seriously, and Java in particular (which was designed from its inception to be worldwide) defines PBEKeySpec to contain characters -- char[] in Java is UTF-16 -- which are encoded as UTF-8 when doing PBKDF2. In contrast openssl is a C program dating from before the turn of the century when C started admitting the existence of countries other than the USA, so it only knows about bytes -- bytes which might be ASCII, or some other single-byte code like EBCDIC, but maybe not characters at all and certainly not any of those weird foreign characters that don't fit in a byte. The probability of a sequence of 32 random bytes being valid UTF-8 is very low; it's too much work for me to figure analytically, but I ran a test of 100 million random values and got only one that would work with your scheme. (I was going to test a billion but got tired of waiting.)

Plus, since a password is supposed to be text, openssl reads -pass file: as a text file and treats it as a string. That means if any of the random bytes is a null byte or a byte corresponding to the newline character, the remainder of the data in the file is discarded and ignored for the key-and-IV derivation. This will occur on average about 1 in 4 times for random 32-byte values, and about 1 in 20 times it will occur early enough in the file to make the result cryptographically weak and breakable.

Which raises the point: why are you using password-based encryption at all? If your 'key' is 32 bytes from a decent secure RNG -- which openssl rand is -- you don't need to strengthen it, it's already valid as a key. You can use openssl enc to do key-based encryption, not password-based, and it's more efficient, more secure, AND much easier in Java -- a massive win. IF you use a new, random key for each encryption you don't even have to use a real IV, you can just use a zero IV as I did below. But if you are going to reuse the/any key, you need to use a unique and unpredictable -- normally random -- IV for each encryption, and convey it with the data, perhaps by just putting it at the front.

So anyway, here's a fairly simple Java program which can handle either case: the openssl form of pdbkf2 with a 'password' that isn't actually a password and isn't UTF-8, or the more sensible key-based form (but for this demo with zero IV):

//nopackage
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class SO61613286 {
static public void main (String[] args) throws Exception /* let JVM give error */{
// arguments: P/K, filename output from openssl enc, filename of text pw or binary key
byte[] file = Files.readAllBytes(Paths.get(args[1]));
byte[] fil2 = Files.readAllBytes(Paths.get(args[2]));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

if( args[0].startsWith("P") ){
// possibly truncate 'password' in fil2
int n = 0; for( ; n < fil2.length; n++ ) if( fil2[n]==0 || fil2[n]=='\n' ) break;
if( n < fil2.length ) fil2 = Arrays.copyOf(fil2, n);
// extract salt and derive ...
byte[] salt = Arrays.copyOfRange(file, 8, 16);
byte[] derive = PBKDF2 ("HmacSHA256", fil2, salt, 10000, 48);
// ... key is first 32, IV is last 16
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(derive,0,32,"AES"), new IvParameterSpec(derive,32,16));
// remainder of file is ciphertext
System.out.write( cipher.doFinal(file,16,file.length-16) );
}else{
// just use fil2 as key and zeros for IV ...
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(fil2,"AES"), new IvParameterSpec(new byte[16]));
// ... all of file is ciphertext
System.out.write( cipher.doFinal(file,0,file.length) );
// !!!if key will be reused, must use better IVs and transmit with the data!!!
}
}
public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
byte[] result = new byte[len];
Mac mac = Mac.getInstance(prf);
mac.init (new SecretKeySpec (pass,prf));
byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
while( /*remaining*/len>0 ){
for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break;
byte[] u = saltcnt, x = new byte[mac.getMacLength()];
for( int i = 1; i <= iter; i++ ){
u = mac.doFinal (u);
for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
}
int len2 = Math.min (len, x.length);
System.arraycopy (x,0, result,result.length-len, len2);
len -= len2;
}
return result;
}
public static void testutf8 (){
Random r = new Random();
byte[] t = new byte[32];
for( int i = 0; i < 1000000000; i++ ){
r.nextBytes(t);
if( Arrays.equals(new String (t, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), t) )
System.out.println(i+" "+Arrays.toString(t));
if( i % 1000000 == 999999 ) System.out.println (i);
}
}
}

and a demo:

$ openssl rand 32 >SO61613286.rnd   # repeated several times until I got this:
$ xxd SO61613286.rnd # notice the null byte
0000000: ab1a 1384 9238 0900 c947 6b9a c23d 5ee0 .....8...Gk..=^.
0000010: 32f0 6b2f 91ec 2dd9 a69d eb7d e00e 37ff 2.k/..-....}..7.
$
$ echo the name of the cat >SO61613286.in
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc1 -pass file:SO61613286.rnd -pbkdf2 -iter 10000
$ java8 -cp . SO61613286 P SO61613286.enc1 SO61613286.rnd
the name of the cat
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc2 -K $(xxd -p -c32 SO61613286.rnd) -iv 00
hex string is too short, padding with zero bytes to length
$ # that's a warning and can be ignored, as long as we don't need real IVs (see above)
$ java8 -cp . SO61613286 K SO61613286.enc2 SO61613286.rnd
the name of the cat
$

Looking for Java implementation for decrypting a message encrypted using openssl -aes-256-cbc -a -salt command?

Also got an improved solution from the following site. Got the code for both encryption and decryption for now...

http://qaru.site/questions/19874/java-equivalent-of-an-openssl-aes-cbc-encryption

import java.net.URLEncoder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import static java.nio.charset.StandardCharsets.*;

/**
* Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a
* shared key (aka password) with symetric ciphers.
*/

public class OpenSslAesQu {

/** OpenSSL magic initial bytes. */
private static final String SALTED_STR = "Salted__";
private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);

public static String encryptAndURLEncode(String password, String clearText) {

String encrypted = null;
try {
encrypted = URLEncoder.encode(encrypt(password, clearText),UTF_8.name());
} catch (Exception e) {e.printStackTrace();}
return encrypted;

}

/**
*
* @param password The password / key to encrypt with.
* @param data The data to encrypt
* @return A base64 encoded string containing the encrypted data.
*/

public static String encrypt(String password, String clearText) {

String encryptedMsg = null;
final byte[] pass = password.getBytes(US_ASCII);
final byte[] salt = (new SecureRandom()).generateSeed(8);
final byte[] inBytes = clearText.getBytes(UTF_8);

final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];

try {
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}

final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(inBytes);
data = array_concat(array_concat(SALTED_MAGIC, salt), data);
//return Base64.getEncoder().encodeToString( data );
encryptedMsg = org.apache.commons.codec.binary.Base64.encodeBase64String(data);
} catch(Exception e) {e.printStackTrace();}

return encryptedMsg;
}

/**
* @see http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption for what looks like a useful answer. The not-yet-commons-ssl also has an implementation
* @param password
* @param source The encrypted data
*/

public static String decrypt(String password, String source) {

String decryptedMsg = null;

final byte[] pass = password.getBytes(US_ASCII);

//final byte[] inBytes = Base64.getDecoder().decode(source);
final byte[] inBytes = Base64.decodeBase64(source);

final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}

final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);

final byte[] passAndSalt = array_concat(pass, salt);

byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
try {
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}

final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);

final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
decryptedMsg = new String(clear, UTF_8);
} catch (Exception e) {e.printStackTrace();}

return decryptedMsg;
}

private static byte[] array_concat(final byte[] a, final byte[] b) {
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}

public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {

String msg = "the decrypted message is this";
String password = "pass";

System.out.println(">> "+encrypt(password,msg));
//System.out.println("<< "+decrypt(encrypt(msg, password), password));

String encryptedMsg = "U2FsdGVkX190A5FsNTanwTKBdex29SpnH4zWkZN+Ld+MmbJgK4BH1whGIRRSpOJT";
String encryptedMsg2 = "U2FsdGVkX1/B6oOznz5+nd7W/qXwXI7G7rhj5o9pjx8MS0TXp9SNxO3AhM9HBJ/z";

System.out.println(decrypt(password,encryptedMsg));
System.out.println(decrypt(password,encryptedMsg2));
System.out.println(decrypt(password,encrypt(password,msg)));
}

}

Java AES encryption with keyFile using BouncyCastle SSL

For compatibility with the OpenSSL statement:

  • a random 8 bytes of salt must be generated
  • a 32 bytes key and 16 bytes IV must be derived using EVP_BytesToKey() and the salt
  • the result must be given in OpenSSL format:

    <ASCII Encoding of Salted__>|<salt>|<ciphertext>

For EVP_BytesToKey() you can apply the OpenSSLPBEParametersGenerator class you already suggested.

EVP_BytesToKey() uses a digest. In earlier versions of OpenSSL MD5 was applied by default, from v1.1.0 SHA256. The digest can be set with the -md5 option. Code and OpenSSL statement must both use the same digest to be compatible. OpenSSLPBEParametersGenerator allows the specification of the digest in the constructor, default is MD5.

The following code, is based on your code, i.e. uses OpenSSLPBEParametersGenerator for EVP_BytesToKey() but additionally considers above points. Instead of encrypting the entire data, streams are applied and the data is encrypted chunk by chunk, so that even large files can be processed:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;

...

String inputPath = "...";
String outputPath = "...";
String passwordStr = "...";

// Generate random 8 bytes salt
SecureRandom random = new SecureRandom();
byte salt[] = new byte[8];
random.nextBytes(salt);

// Derive 32 bytes key (AES_256) and 16 bytes IV
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits

// Encrypt with AES-256, CBC using streams
try (FileOutputStream fos = new FileOutputStream(outputPath)) {

// Apply OpenSSL format
fos.write("Salted__".getBytes(StandardCharsets.UTF_8));
fos.write(salt);

// Encrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(true, parameters);
try (FileInputStream fis = new FileInputStream(inputPath);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
while ((bytesRead = fis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
}
}

A file encrypted with this code can be decrypted with OpenSSL as follows:

openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>

Therefore the code is the programmatic analogue of the OpenSSL statement posted at the beginning of your question (whereby the ambiguity regarding the digest still has to be taken into account, i.e. for an OpenSSL version from v1.1.0 SHA256 has to be used instead of MD5).

Note that because of the random salt, different key/IV pairs are generated for each encryption, so there is no reuse, which also removes the vulnerability mentioned in the comment.

How to decrypt file in Java encrypted with openssl command using AES?

OpenSSL generally uses its own password based key derivation method, specified in EVP_BytesToKey, please see the code below. Furthermore, it implicitly encodes the ciphertext as base 64 over multiple lines, which would be required to send it within the body of a mail message.

So the result is, in pseudocode:

salt = random(8)
keyAndIV = BytesToKey(password, salt, 48)
key = keyAndIV[0..31]
iv = keyAndIV[32..47]
ct = AES-256-CBC-encrypt(key, iv, plaintext)
res = base64MimeEncode("Salted__" | salt | ct))

and the decryption therefore is:

(salt, ct) = base64MimeDecode(res)
key = keyAndIV[0..31]
iv = keyAndIV[32..47]
pt = AES-256-CBC-decrypt(key, iv, plaintext)

which can be implemented in Java like this:

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.util.encoders.Base64;

public class OpenSSLDecryptor {
private static final Charset ASCII = Charset.forName("ASCII");
private static final int INDEX_KEY = 0;
private static final int INDEX_IV = 1;
private static final int ITERATIONS = 1;

private static final int ARG_INDEX_FILENAME = 0;
private static final int ARG_INDEX_PASSWORD = 1;

private static final int SALT_OFFSET = 8;
private static final int SALT_SIZE = 8;
private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;

private static final int KEY_SIZE_BITS = 256;

/**
* Thanks go to Ola Bini for releasing this source on his blog.
* The source was obtained from <a href="http://olabini.com/blog/tag/evp_bytestokey/">here</a> .
*/
public static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md,
byte[] salt, byte[] data, int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0)
break;
if (i == md_buf.length)
break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0)
break;
if (i == md_buf.length)
break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}

public static void main(String[] args) {
try {
// --- read base 64 encoded file ---

File f = new File(args[ARG_INDEX_FILENAME]);
List<String> lines = Files.readAllLines(f.toPath(), ASCII);
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line.trim());
}
String dataBase64 = sb.toString();
byte[] headerSaltAndCipherText = Base64.decode(dataBase64);

// --- extract salt & encrypted ---

// header is "Salted__", ASCII encoded, if salt is being used (the default)
byte[] salt = Arrays.copyOfRange(
headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
byte[] encrypted = Arrays.copyOfRange(
headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);

// --- specify cipher and digest for EVP_BytesToKey method ---

Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
MessageDigest md5 = MessageDigest.getInstance("MD5");

// --- create key and IV ---

// the IV is useless, OpenSSL might as well have use zero's
final byte[][] keyAndIV = EVP_BytesToKey(
KEY_SIZE_BITS / Byte.SIZE,
aesCBC.getBlockSize(),
md5,
salt,
args[ARG_INDEX_PASSWORD].getBytes(ASCII),
ITERATIONS);
SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);

// --- initialize cipher instance and decrypt ---

aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = aesCBC.doFinal(encrypted);

String answer = new String(decrypted, ASCII);
System.out.println(answer);
} catch (BadPaddingException e) {
// AKA "something went wrong"
throw new IllegalStateException(
"Bad password, algorithm, mode or padding;" +
" no salt, wrong number of iterations or corrupted ciphertext.");
} catch (IllegalBlockSizeException e) {
throw new IllegalStateException(
"Bad algorithm, mode or corrupted (resized) ciphertext.");
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}

Beware that the code specifies ASCII as character set. The character set used may differ for your application / terminal / OS.


In general you should force OpenSSL to use the NIST approved PBKDF2 algorithm, as using the OpenSSL key derivation method - with an iteration count of 1 - is insecure. This may force you to use a different solution than OpenSSL. Note that password based encryption is inherently rather insecure - passwords are much less secure than randomly generated symmetric keys.


OpenSSL 1.1.0c changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0 switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.

It's probably best to explicitly specify the digest in the command line interface (e.g. -md md5 for backwards compatibility or sha-256 for forwards compatibility) for the and make sure that the Java code uses the same digest algorithm ("MD5" or "SHA-256" including the dash). Also see the information in this answer.



Related Topics



Leave a reply



Submit