如何使用AES解密用openssl命令encryption的Java文件?

我需要使用以下命令在JAVA中使用在UNIX中encryption的文件进行解密:

openssl aes-256-cbc -a -salt -in password.txt -out password.txt.enc mypass mypass 

我必须在java中解密,就像我在UNIX中所做的一样

 openssl aes-256-cbc -d -a -in password.txt.enc -out password.txt.new mypass 

有人可以给我一个Java代码来做到这一点?

OpenSSL通常使用自己的密码派生方法,在EVP_BytesToKey指定,请参阅下面的代码。 一般而言,您应该强制OpenSSL使用NIST批准的PBKDF2algorithm。

 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; /** * Class created for StackOverflow by owlstead. * This is open source, you are free to copy and use for any purpose. */ 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); } } } 

OpenSSL 1.1.0c更改了某些内部组件中使用的摘要algorithm 。 以前使用MD5,1.1.0切换到SHA256。 在EVP_BytesToKey和像openssl enc这样的命令中,注意这个改变不会影响到你。

以下是OpenSSLPBEInputStreamOpenSSLPBEOutputStream ,可用于以与OpenSSL兼容的方式encryption/解密任意字节stream。

用法示例:

  // The original clear text bytes byte[] originalBytes = ... // Encrypt these bytes char[] pwd = "thePassword".toCharArray(); ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); OpenSSLPBEOutputStream encOS = new OpenSSLPBEOutputStream(byteOS, ALGORITHM, 1, pwd); encOS.write(originalBytes); encOS.flush(); byte[] encryptedBytes = byteOS.toByteArray(); // Decrypt the bytes ByteArrayInputStream byteIS = new ByteArrayInputStream(encryptedBytes); OpenSSLPBEInputStream encIS = new OpenSSLPBEInputStream(byteIS, ALGORITHM, 1, pwd); 

algorithm(仅使用JDK类)可以是:“PBEWithMD5AndDES”,“PBEWithMD5AndTripleDES”,“PBEWithSHA1AndDESede”,“PBEWithSHA1AndRC2_40”。

为了处理原始海报的“openssl aes-256-cbc -a -salt -in password.txt -out password.txt.enc”,将bouncey castle添加到classpath中,并使用algorthm =“PBEWITHMD5AND256BITAES-CBC-OPENSSL”。

 /* Add BC provider, and fail fast if BC provider is not in classpath for some reason */ Security.addProvider(new BouncyCastleProvider()); 

依赖关系:

  <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.44</version> </dependency> 

inputstream:

 import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.io.InputStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; public class OpenSSLPBEInputStream extends InputStream { private final static int READ_BLOCK_SIZE = 64 * 1024; private final Cipher cipher; private final InputStream inStream; private final byte[] bufferCipher = new byte[READ_BLOCK_SIZE]; private byte[] bufferClear = null; private int index = Integer.MAX_VALUE; private int maxIndex = 0; public OpenSSLPBEInputStream(final InputStream streamIn, String algIn, int iterationCount, char[] password) throws IOException { this.inStream = streamIn; try { byte[] salt = readSalt(); cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.DECRYPT_MODE, algIn, iterationCount); } catch (InvalidKeySpecException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IOException(e); } } @Override public int available() throws IOException { return inStream.available(); } @Override public int read() throws IOException { if (index > maxIndex) { index = 0; int read = inStream.read(bufferCipher); if (read != -1) { bufferClear = cipher.update(bufferCipher, 0, read); } if (read == -1 || bufferClear == null || bufferClear.length == 0) { try { bufferClear = cipher.doFinal(); } catch (IllegalBlockSizeException | BadPaddingException e) { bufferClear = null; } } if (bufferClear == null || bufferClear.length == 0) { return -1; } maxIndex = bufferClear.length - 1; } return bufferClear[index++] & 0xff; } private byte[] readSalt() throws IOException { byte[] headerBytes = new byte[OpenSSLPBECommon.OPENSSL_HEADER_STRING.length()]; inStream.read(headerBytes); String headerString = new String(headerBytes, OpenSSLPBECommon.OPENSSL_HEADER_ENCODE); if (!OpenSSLPBECommon.OPENSSL_HEADER_STRING.equals(headerString)) { throw new IOException("unexpected file header " + headerString); } byte[] salt = new byte[OpenSSLPBECommon.SALT_SIZE_BYTES]; inStream.read(salt); return salt; } } 

输出stream:

 import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; public class OpenSSLPBEOutputStream extends OutputStream { private static final int BUFFER_SIZE = 5 * 1024 * 1024; private final Cipher cipher; private final OutputStream outStream; private final byte[] buffer = new byte[BUFFER_SIZE]; private int bufferIndex = 0; public OpenSSLPBEOutputStream(final OutputStream outputStream, String algIn, int iterationCount, char[] password) throws IOException { outStream = outputStream; try { /* Create and use a random SALT for each instance of this output stream. */ byte[] salt = new byte[PBECommon.SALT_SIZE_BYTES]; new SecureRandom().nextBytes(salt); cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE, algIn, iterationCount); /* Write header */ writeHeader(salt); } catch (InvalidKeySpecException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IOException(e); } } @Override public void write(int b) throws IOException { buffer[bufferIndex] = (byte) b; bufferIndex++; if (bufferIndex == BUFFER_SIZE) { byte[] result = cipher.update(buffer, 0, bufferIndex); outStream.write(result); bufferIndex = 0; } } @Override public void flush() throws IOException { if (bufferIndex > 0) { byte[] result; try { result = cipher.doFinal(buffer, 0, bufferIndex); outStream.write(result); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new IOException(e); } bufferIndex = 0; } } @Override public void close() throws IOException { flush(); outStream.close(); } private void writeHeader(byte[] salt) throws IOException { outStream.write(OpenSSLPBECommon.OPENSSL_HEADER_STRING.getBytes(OpenSSLPBECommon.OPENSSL_HEADER_ENCODE)); outStream.write(salt); } } 

小普通class:

 import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; class OpenSSLPBECommon { protected static final int SALT_SIZE_BYTES = 8; protected static final String OPENSSL_HEADER_STRING = "Salted__"; protected static final String OPENSSL_HEADER_ENCODE = "ASCII"; protected static Cipher initializeCipher(char[] password, byte[] salt, int cipherMode, final String algorithm, int iterationCount) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException { PBEKeySpec keySpec = new PBEKeySpec(password); SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm); SecretKey key = factory.generateSecret(keySpec); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(cipherMode, key, new PBEParameterSpec(salt, iterationCount)); return cipher; } } 

不要使用ase-128-cbc,请使用ase-128-ecb。

由于密钥是128位,所以只需要前16个字节作为密钥

散列输出以hex打印,每2个字符表示一个字节值

hashpwd = echo -n $password| openssl sha1 | sed 's#.*=\\s*##g' | cut -c 1-32 echo -n $password| openssl sha1 | sed 's#.*=\\s*##g' | cut -c 1-32

openssl enc -aes-128-ecb -salt -in -out -K $ hashpwd

Java代码在这里:

 import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; //openssl enc -nosalt -aes-128-ecb // -in <input file> // -out <output file> // -K <16 bytes in hex, for example : "abc" can be hashed in SHA-1, the first 16 bytes in hex is a9993e364706816aba3e25717850c26c> private final static String TRANSFORMATION = "AES"; // use aes-128-ecb in openssl public static byte[] encrypt(String passcode, byte[] data) throws CryptographicException { try { Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, genKeySpec(passcode)); return cipher.doFinal(data); } catch (Exception ex) { throw new CryptographicException("Error encrypting", ex); } } public static String encryptWithBase64(String passcode, byte[] data) throws CryptographicException { return new BASE64Encoder().encode(encrypt(passcode, data)); } public static byte[] decrypt(String passcode, byte[] data) throws CryptographicException { try { Cipher dcipher = Cipher.getInstance(TRANSFORMATION); dcipher.init(Cipher.DECRYPT_MODE, genKeySpec(passcode)); return dcipher.doFinal(data); } catch (Exception e) { throw new CryptographicException("Error decrypting", e); } } public static byte[] decryptWithBase64(String passcode, String encrptedStr) throws CryptographicException { try { return decrypt(passcode, new BASE64Decoder().decodeBuffer(encrptedStr)); } catch (Exception e) { throw new CryptographicException("Error decrypting", e); } } public static SecretKeySpec genKeySpec(String passcode) throws UnsupportedEncodingException, NoSuchAlgorithmException { byte[] key = passcode.getBytes("UTF-8"); MessageDigest sha = MessageDigest.getInstance("SHA-1"); key = sha.digest(key); key = Arrays.copyOf(key, 16); // use only first 128 bit return new SecretKeySpec(key, TRANSFORMATION); } 

testing并通过jdk6和jdk8。