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;
}
}