2012-08-02 120 views
5

我需要JAVA解密在UNIX使用下面的命令加密的文件:如何使用AES解密使用openssl命令加密的Java文件?

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代碼來做到這一點?

回答

14

OpenSSL通常使用自己的基於密碼的密鑰導出方法,在EVP_BytesToKey中指定,請參閱下面的代碼。一般而言,您應該強制OpenSSL使用NIST批准的PBKDF2算法。

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 changed the digest algorithm在一些內部組件使用。以前使用MD5,1.1.0切換到SHA256。請注意,變更不會影響您在EVP_BytesToKeyopenssl enc之類的命令。

+0

他們不應該不同 - 這就是PKCS5這樣的標準的全部內容。你的建議總的來說絕對是好的,儘管 – mfrankli 2012-08-02 22:59:53

+1

「不要忘記匹配[標籤:字符編碼] ...」加密應該確實匹配,但編碼可能不同。 – 2012-08-05 17:36:43

+0

這對我有用。可以預見,我遇到了ASCII與UTF8的問題。 – 2013-04-09 19:08:53

2

下面是OpenSSLPBEInputStreamOpenSSLPBEOutputStream可使用的方式,是使用OpenSSL兼容加密/解密的字節任意流。

實例:

// 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); 

凡算法(使用剛剛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> 

輸入流:

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

} 

輸出流:

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

} 

小公共類:

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

} 
-1

請勿使用ase-128-cbc,請使用ase-128-ecb。

只取前16個字節作爲鍵,因爲鍵是128位

散列輸出被印刷在十六進制,其中每2個字符給出一個字節值

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

OpenSSL的ENC -AES -128-ECB -salt -in退房手續-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); 
    } 

測試並通過jdk6和jdk8。

+0

不要使用ECB模式,它不安全,請參閱[ECB模式](https://en.wikipedia)。 org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_.28ECB.29),向下滾動到企鵝。 取而代之的是將CBC模式與隨機IV一起使用,只是將加密的數據與IV一起用於解密,它並不需要保密。 – zaph 2017-01-23 15:47:44

相關問題