How to decode a string encoded with openssl aes-128-cbc using java?

I'm using openssl to encode a string using the following command :

openssl enc -aes-128-cbc -a -salt -pass pass:mypassword <<< "stackoverflow"

Result give me an encoded string: U2FsdGVkX187CGv6DbEpqh/L6XRKON7uBGluIU0nT3w=

Until now, i only need to decode this using openssl, so the following command returns the string previously encoded:

 openssl enc -aes-128-cbc -a -salt -pass pass:mypassword -d <<< "U2FsdGVkX187CGv6DbEpqh/L6XRKON7uBGluIU0nT3w="

Result: stackoverflow

Now, i need to decode the encoded string in a java application.

My question is:

Is anyone can provide me a simple java class to decode a string encoded with the previously given openssl command?

Many thanks.


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:

  • apache common codec
  • Bouncy Castle

openssl enc by default uses a (modestly) nonstandard password-based encryption algorithm, and a custom but simple data format.

  1. If you don't really need PBE, only some openssl encryption, the question linked by @Artjom has a good answer: Use "raw" key and IV in openssl, and then use the same key and IV in Java. And let them both default to "PKCS5" (really PKCS#7) padding. Note that 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.

  1. 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.

  2. 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