I\'m using openssl to encode a string using the following command :
openssl enc -aes-128-cbc -a -salt -pass pass:mypassword
Solved it using Bouncy Castle library.
Here is the code:
package example;
import java.util.Arrays;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
public class OpenSSLAesDecrypter
{
private static final int AES_NIVBITS = 128; // CBC Initialization Vector (same as cipher block size) [16 bytes]
private final int keyLenBits;
public OpenSSLAesDecrypter(int nKeyBits)
{
this.keyLenBits = nKeyBits;
}
public byte[] decipher(byte[] pwd, byte[] src)
{
// openssl non-standard extension: salt embedded at start of encrypted file
byte[] salt = Arrays.copyOfRange(src, 8, 16); // 0..7 is "SALTED__", 8..15 is the salt
try
{
// Encryption algorithm. Note that the "strength" (bitsize) is controlled by the key object that is used.
// Note that PKCS5 padding and PKCS7 padding are identical.
BlockCipherPadding padding = new PKCS7Padding();
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), padding);
CipherParameters params = getCipherParameters(pwd, salt);
cipher.reset();
cipher.init(false, params);
int buflen = cipher.getOutputSize(src.length - 16);
byte[] workingBuffer = new byte[buflen];
int len = cipher.processBytes(src, 16, src.length - 16, workingBuffer, 0);
len += cipher.doFinal(workingBuffer, len);
// Note that getOutputSize returns a number which includes space for "padding" bytes to be stored in.
// However we don't want these padding bytes; the "len" variable contains the length of the *real* data
// (which is always less than the return value of getOutputSize.
byte[] bytesDec = new byte[len];
System.arraycopy(workingBuffer, 0, bytesDec, 0, len);
return bytesDec;
}
catch (InvalidCipherTextException e)
{
System.err.println("Error: Decryption failed");
return null;
}
catch (RuntimeException e)
{
System.err.println("Error: Decryption failed");
return null;
}
}
private CipherParameters getCipherParameters(byte[] pwd, byte[] salt)
{
// Use bouncycastle implementation of openssl non-standard (pwd,salt)->(key,iv) algorithm.
// Note that if a "CBC" cipher is selected, then an IV is required as well as a key. When using a password,
// Openssl
// *derives* the IV from the (pwd,salt) pair at the same time as it derives the key.
//
// * PBE = Password Based Encryption
// * CBC = Cipher Block Chaining (ie IV is needed)
//
// Note also that when the IV is derived from (pwd, salt) the salt **must** be different for each message; this is
// the default for openssl - just make sure to NOT explicitly provide a salt, or encryption security is badly
// affected.
OpenSSLPBEParametersGenerator gen = new OpenSSLPBEParametersGenerator();
gen.init(pwd, salt);
CipherParameters cp = gen.generateDerivedParameters(keyLenBits, AES_NIVBITS);
return cp;
}
public static void main(String[] args)
{
OpenSSLAesDecrypter d = new OpenSSLAesDecrypter(128);
String r = new String(d.decipher("mypassword".getBytes(),
Base64.decodeBase64("U2FsdGVkX187CGv6DbEpqh/L6XRKON7uBGluIU0nT3w=")));
System.out.println(r);
}
}
Use the following dependencies to compile/run it:
openssl enc
by default uses a (modestly) nonstandard password-based encryption algorithm, and a custom but simple data format.
openssl enc
takes both -K
and -iv
in hex, while Java crypto takes them as bytes; convert as necessary. Since/if you base64-ed the ciphertext, de-base64 first; this is provided in java8, and there are numerous libraries available for earlier java versions. Otherwise you need to unpack the file format. After de-base64, discard the first 8 bytes, take the next 8 bytes as the salt, and the remaining bytes as the ciphertext.
If you need PBE for the specific algorithm AES128-CBC or 192 or 256 and you can use a thirdparty crypto library namely http://www.BouncyCastle.org , it implements openssl PBE for those three algorithms. Instantiate SecretKeyFactory
for PBEWITHMD5AND128BITAES-CBC-OPENSSL
-- or 192 or 256 but only if Unlimited Strength Policy is installed (update: or Oracle version >= 8u161 or OpenJDK) -- and give it a PBEKeySpec
with the key as chars, salt, and count of 1, and use the result in a Cipher
of the same algorithm.
Otherwise you must do the PBE yourself. Fortunately(?) it's pretty simple. Put the following method whereever is convenient:
public static /*or as appropriate */ void opensslBytesToKey (
byte[] pass, byte[] salt /*or null*/, // inputs
int iter, String hashname, // PBKDF1-ish
byte[] key, byte[] iv /*or null*/ // outputs
) throws NoSuchAlgorithmException
{
MessageDigest md = MessageDigest.getInstance (hashname);
byte[] temp = null, out = new byte[key.length+(iv!=null?iv.length:0)];
int outidx = 0;
while(outidx < out.length){
if(temp!=null) md.update(temp);
md.update(pass);
if(salt!=null) md.update(salt);
temp = md.digest();
for(int i=1; i<iter; i++) temp = md.digest (temp);
int use = Math.min (out.length-outidx, temp.length);
System.arraycopy (temp,0, out,outidx, use);
outidx += use;
}
System.arraycopy (out,0, key,0, key.length);
if(iv!=null) System.arraycopy (out,key.length, iv,0, iv.length);
}
and call it with the password as bytes, the salt, iteration count 1, "MD5", and output arrays that are the correct size for your AES key (16, 24, or 32 bytes) and an AES IV (always 16 bytes). Use these in a SecretKeySpec
and IvParameterSpec
respectively with Cipher
for (correction) AES/CBC/PKCS5Padding
.
Aside: you can't encrypt a string as such, only bytes (or more exactly octets). C programs, including openssl, on practically all systems translate strings/characters in ASCII to and from bytes implicitly, but using any characters outside the ASCII set may produce inconsistent and unusable results. Java treats strings/characters as Unicode (or more exactly UTF-16) and converts them to and from bytes mostly explicitly; this conversion is reliable (and consistent with C) for ASCII, but can vary for non-ASCII characters.
UPDATE: OpenSSL 1.1.0 (2016-08) changes the default hash for enc
PBE from MD5 to SHA256. Change the call in my option 3 depending on which version of OpenSSL was used to encrypt, or if the (previously undocumented) -md
option was used. For more details see (my) https://crypto.stackexchange.com/questions/3298/is-there-a-standard-for-openssl-interoperable-aes-encryption/#35614