(back to article)

Decrypt data that has previously been encrypted via OpenSSL using a password

This example uses the BouncyCastle library via the standard Java APIs. The standard Java distribution does not support the slightly non-standard OpenSSL approach to mapping (password,salt) into (key,IV) so using BouncyCastle is necessary.

While the native BouncyCastle APIs are cleaner than the Java ones, you might have a requirement to use the standard APIs for some reason. This is how to do it..


package example;

import java.security.GeneralSecurityException;
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 org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * Implement code to decrypt a file that has been encrypted by openssl using the aes-***-cbc algorithm and a <i>password</i>.
 */
public class OpenSSLAesDecipherer {
    // Encryption algorithm. Note that the "strength" (bitsize) is controlled by the key object that is used.
    //
    // Note also that if a "CBC" cipher is selected, then normally an IvParameterSpec object wrapping the IV
    // data must be passed to the Cipher.init method. However Openssl has an interesting "hack" where the IV
    // is derived from the password using an openssl-specific extension to the normal algorithms; the
    // bouncycastle PBEKey implementation knows how to do this, and the bouncycastle AES Cipher implementation
    // checks whether its key parameter is a bouncycastle key, and if so then downcasts it and retrieves the
    // IV value. Ugly but effective.
    private static final String CIPHER_ID = "AES/CBC/PKCS5Padding";

    private static final String BOUNCY_CASTLE_PROVIDER_ID = "BC";

    private final int keyLenBits;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    OpenSSLAesDecipherer(int keyLenBits) {
        this.keyLenBits = keyLenBits;
    }

    public byte[] decipher(byte[] pwd, byte[] src) throws GeneralSecurityException {
        // 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

        Cipher cipher = Cipher.getInstance(CIPHER_ID, BOUNCY_CASTLE_PROVIDER_ID);
        SecretKey key = getKey(pwd, salt);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(src, 16, src.length-16);
    }
    
    private SecretKey getKey(byte[] pwd, byte[] salt) throws GeneralSecurityException {http://moi.vonos.net/2013/02/eclipse-emf/
        // Convert array of 8-bit-bytes to array of 16-bit-chars, treating input as ASCII..
        char[] pwdAsChars = new char[pwd.length];
        for(int i=0; i<pwd.length; ++i) {
            pwdAsChars[i] = (char) pwd[i];
        }

        // Use bouncycastle implementation of openssl non-standard (pwd,salt)->(key,iv) algorithm.
        // * PBE = Password Based Encryption
        // * CBC = Cipher Block Chaining (ie IV is needed)
        // The bouncycastle Cipher implementation for algorithm "AES/CBC" checks whether its key object
        // is a bouncycastle implementation, and if so then extracts the IV (if any).
        String keyAlgo = String.format("PBEWITHMD5AND%dBITAES-CBC-OPENSSL", keyLenBits);
        SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo, BOUNCY_CASTLE_PROVIDER_ID);
        PBEKeySpec keySpec = new PBEKeySpec(pwdAsChars, salt, 1); // NB: iteration-count param here is currently ignored
        SecretKey keyObj = factory.generateSecret(keySpec);
        return keyObj;
    }
}