2012-08-02 18 views
5

tengo que descifrar en JAVA un archivo cifrado en UNIX con el siguiente comando:¿Cómo descifrar el archivo en Java cifrado con el comando openssl usando AES?

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

tengo que descifrar en Java como lo hago aquí yo en UNIX

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

Alguien me puede dar un código de Java para hacer esto?

Respuesta

14

OpenSSL generalmente utiliza su propio método de derivación de clave basado en contraseña, especificado en EVP_BytesToKey, consulte el código siguiente. En general, debe obligar a OpenSSL a usar el algoritmo PBKDF2 aprobado por NIST.

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 utiliza en algunos componentes internos. Anteriormente, se usaba MD5 y 1.1.0 cambiaba a SHA256. Tenga cuidado de que el cambio no lo afecte tanto en EVP_BytesToKey como en comandos como openssl enc.

+0

NO deberían diferir: ese es el objetivo de estándares como PKCS5. su consejo en general es definitivamente bueno, – mfrankli

+1

"No se olvide de hacer coincidir la [etiqueta: codificación de caracteres] ..." El cifrado debe coincidir, pero la codificación puede diferir. –

+0

Esto funcionó para mí. Y predeciblemente tuve problemas con ASCII vs UTF8. –

2

A continuación se presentan OpenSSLPBEInputStream y OpenSSLPBEOutputStream que se puede utilizar para cifrar/descifrar corrientes arbitrarios de bytes de una manera que sea compatible con OpenSSL.

Ejemplo de uso:

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

Dónde ALGORITMO (usando sólo clases de JDK) puede ser: "PBEWithMD5AndDES", "PBEWithMD5AndTripleDES", "PBEWithSHA1AndDESede", "PBEWithSHA1AndRC2_40".

Para manejar "openssl aes-256-cbc -a -salt -in contraseña.txt -out contraseña.txt.enc" del cartel original, agregar bouncey castle al classpath, y usar algorthm = "PBEWITHMD5AND256BITAES-CBC -OPENSSL ".

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

La dependencia:

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

El flujo de entrada:

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

} 

la secuencia de salida:

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

} 

Pequeño clase común:

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

No utilice ase-128-cbc, utilice ase-128-ecb.

sólo tienen primeros 16 bytes como clave porque clave es 128 bits

salida de hash se imprime en hexadecimal, que cada 2 caracteres presenta un valor de byte

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

-aes openssl enc -128-BCE -sal -en salida privado -K $ hashpwd

Código de Java está aquí:

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

Probado y aprobado en jdk6 y jdk8.

+0

No usa el modo ECB, no es seguro, consulte [Modo ECB] (https: //en.wikipedia. org/wiki/Block_cipher_mode_of_operation # Electronic_Codebook_.28ECB.29), desplácese hasta Penguin. En lugar de usar el modo CBC con un IV aleatorio, simplemente prefija los datos cifrados con el IV para usarlos en el descifrado, no es necesario que no sean secretos. – zaph

Cuestiones relacionadas