2012-08-02 20 views

risposta

14

OpenSSL generalmente utilizza il proprio metodo di derivazione della chiave basato su password, specificato in EVP_BytesToKey, vedere il codice di seguito. In generale, dovresti forzare OpenSSL ad usare l'algoritmo PBKDF2 approvato dal 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 utilizzato in alcuni componenti interni. Precedentemente, è stato utilizzato MD5 e la versione 1.1.0 è passata a SHA256. Fai attenzione che il cambiamento non influisce su entrambi in EVP_BytesToKey e su comandi come openssl enc.

+0

non dovrebbero differire - questo è l'aspetto di standard come PKCS5. il tuo consiglio nel complesso è sicuramente buono anche se – mfrankli

+1

"Non dimenticarti di abbinare il [tag: codifica caratteri] ..." La crittografia dovrebbe effettivamente coincidere, ma la codifica potrebbe essere diversa. –

+0

Questo ha funzionato per me. E prevedibilmente ho avuto problemi con ASCII vs UTF8. –

2

Questi sono OpenSSLPBEInputStream e OpenSSLPBEOutputStream che può essere usata per cifrare/decifrare i flussi arbitrari di byte in un modo compatibile con OpenSSL.

Esempio utilizzo:

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

Dove ALGORITMO (utilizzando solo le classi JDK) può essere: "PBEWithMD5AndDES", "PBEWithMD5AndTripleDES", "PBEWithSHA1AndDESede", "PBEWithSHA1AndRC2_40".

per gestire "OpenSSL AES-256-CBC -a -sale -in password.txt -out password.txt.enc" del manifesto originale, aggiungere il castello Bouncey al classpath, e utilizzare 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 dipendenza:

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

Il flusso di input:

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

} 

Il flusso di uscita:

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

} 

piccola classe comune:

0.123.
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

Non utilizzare ase-128-cbc, utilizzare ase-128-ecb.

prendono solo primi 16 byte come chiave perché la chiave è di 128 bit

uscita hash è stampato in esadecimale, che ogni 2 caratteri presenta un valore di byte

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

-AES openssl ENC -128-BCE -sale -in -out -K $ hashpwd

codice Java è qui:

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

Testato e passato in jdk6 e jdk8.

+0

Non utilizzare la modalità BCE, non è sicuro, vedi [Modalità ECB] (https: //en.wikipedia. org/wiki/Block_cipher_mode_of_operation # Electronic_Codebook_.28ECB.29), scorrere fino al Penguin. Utilizzare invece la modalità CBC con un IV casuale, solo prefisso i dati crittografati con l'IV per l'utilizzo in decrittografia, non è necessario che non sia segreto. – zaph

Problemi correlati