Equivalente Java di una crittografia CBC OpenSSL AES

Io non sono un profi di crittografia e specialmente perché OpenSSL ha molta documentazione mancante, non sono sicuro di come posso risolvere questo problema.

Ho un sistema esterno che si aspetta di ricevere messaggi crittografati. L’unico esempio fornito utilizza OpenSSL in questo modo:

$ openssl enc -aes-256-cbc -a -in t.txt -k testpass U2FsdGVkX1/RUdaSJKRXhHv3zUyTsQwu5/ar2ECKDlrNyH5GL4xRR4fgxkiWqkS1 cQstcoSIgWfRPSOFj/5OtdNLeNXiVR6MxSKJ+NvS9LyUD8+Rg6XIcYUvxR4gHi3w DWT44LAMCpRAh1Q0t4Z2g7rwb0D05T6ygLaWvB5zD/xGZD3brTqSlWmiJb9Imgda M6soZO7BhbYdqWqEUl5r6+EbkD21f6L3NX3hJFo+BJ+VFctiAlBO8NwT5l4ogo/s GErm8gqRr57XoX/kvKAimg== 

Dove il file t.txt contiene questa stringa su una riga:

 AMOUNT=10&TID=#19:23&CURRENCY=EUR&LANGUAGE=DE&SUCCESS_URL=http://some.url/sucess&ERROR_URL=http://some.url/error&CONFIRMATION_URL=http://some.url/confirm&NAME=customer full name` 

Ho trovato questa altra domanda e sono stato in grado di fare la crittografia usando il seguente codice:

 String password = "passPhrase"; String salt = "15charRandomSalt"; int iterations = 100; /* Derive the key, given password and salt. */ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(Charset.forName("UTF8")), iterations, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); /* Encrypt the message. */ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] cipherText = cipher.doFinal(toBeEncrypted.getBytes("UTF-8")); encryptedData = Base64.getEncoder().encodeToString(cipherText); encryptedData += Base64.getEncoder().encodeToString(iv); 

Quello che non riesco a capire è come dovrei generare un output simile (encryptedData) a ciò che OpenSSL fa. Ho il sale, iv e cipherText, è l’output OpenSSL Base64 risultato codificato di una concatenazione di questi? o solo uno di loro?

L’unica cosa che condivido con quell’altro sistema prima che la crittografia sia la passphrase. Come potrebbero decifrare il risultato se il sale e il numero di iterazioni non sono noti a loro?

Qualcuno può dare risposte a quei parametri sconosciuti e anche dirmi se il codice precedente è l’equivalente del processo OpenSSL?

Di seguito è riportato un programma Java per decrittografare la suddetta crittografia OPENSSL (richiede Java 8):

 import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Decoder; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class TestAesDecrypt { public static void main(final String[] args) throws Exception { final byte[] pass = "testpass".getBytes(StandardCharsets.US_ASCII); final byte[] magic = "Salted__".getBytes(StandardCharsets.US_ASCII); final String inFile = "e:/t/e.txt"; String source = new String(Files.readAllBytes(Paths.get(inFile)), StandardCharsets.US_ASCII); source = source.replaceAll("\\s", ""); final Decoder decoder = Base64.getDecoder(); final byte[] inBytes = decoder.decode(source); final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, magic.length); if (!Arrays.equals(shouldBeMagic, magic)) { System.out.println("Bad magic number"); return; } final byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8); final byte[] passAndSalt = concat(pass, salt); byte[] hash = new byte[0]; byte[] keyAndIv = new byte[0]; for (int i = 0; i < 3; i++) { final byte[] data = concat(hash, passAndSalt); final MessageDigest md = MessageDigest.getInstance("MD5"); hash = md.digest(data); keyAndIv = concat(keyAndIv, hash); } final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32); final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48); final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); final SecretKeySpec key = new SecretKeySpec(keyValue, "AES"); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16); final String clearText = new String(clear, StandardCharsets.ISO_8859_1); System.out.println(clearText); } private static byte[] concat(final byte[] a, final byte[] b) { final byte[] c = new byte[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } } 

Questa domanda ha una risposta accettata che è un po ‘vecchia, tuttavia questo sembra essere qualcosa che viene ripetutamente. Ho 2 progetti in cui comunichiamo con terze parti e il codice è OpenSSL AES con una chiave pre-condivisa.

Ho usato la libreria non ancora comune-ssl. Tuttavia sembra che sia bloccato alla versione 0.3.xe con nessuna release in quasi 2 anni, nessun traffico di mailing list o sviluppo visibile devo concludere che questo è essenzialmente morto.

Sulla base di alcune domande aggiuntive sullo stackoverflow, ho trovato sia Spring Security che Encryptor4j, che sembrano offrire una codifica del testo abbastanza conveniente. Tuttavia, tentando di convincere gli Encryptors di Spring Security a lavorare per decodificare una stringa di testo codificata conosciuta, non sono riuscito a convincermi che la generazione IV e Key utilizzata da OpenSSL non sia semplicemente supportata nell’implementazione fornita.

Esaminando il codice di cui sopra, oltre a una nota implementazione C # e PHP, sono stato in grado di creare una class di utilità che sta attualmente superando i miei test per l’interoperabilità. Generalmente preferirei usare una libreria conosciuta, ma se ce n’è una non riesco a trovarla. La class ( https://gist.github.com/rrsIPOV/4d0f6be7c58173c16e9edf9f97c7d7f2 ) è la seguente:

 import groovy.transform.CompileStatic; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; import java.security.SecureRandom; import static java.nio.charset.StandardCharsets.* /** * Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a shared key (aka password) with symetric ciphers. */ @CompileStatic class OpenSslAes { /** OpenSSL's magic initial bytes. */ private static final String SALTED_STR = "Salted__"; private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII); static String encryptAndURLEncode(String password, String clearText) { String encrypted = encrypt(password, clearText); return URLEncoder.encode(encrypted, UTF_8.name() ); } /** * * @param password The password / key to encrypt with. * @param data The data to encrypt * @return A base64 encoded string containing the encrypted data. */ static String encrypt(String password, String clearText) { final byte[] pass = password.getBytes(US_ASCII); final byte[] salt = (new SecureRandom()).generateSeed(8); final byte[] inBytes = clearText.getBytes(UTF_8); final byte[] passAndSalt = array_concat(pass, salt); byte[] hash = new byte[0]; byte[] keyAndIv = new byte[0]; for (int i = 0; i < 3 && keyAndIv.length < 48; i++) { final byte[] hashData = array_concat(hash, passAndSalt); final MessageDigest md = MessageDigest.getInstance("MD5"); hash = md.digest(hashData); keyAndIv = array_concat(keyAndIv, hash); } final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32); final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48); final SecretKeySpec key = new SecretKeySpec(keyValue, "AES"); final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); byte[] data = cipher.doFinal(inBytes); data = array_concat(array_concat(SALTED_MAGIC, salt), data); return Base64.getEncoder().encodeToString( data ); } /** * @see http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption for what looks like a useful answer. The not-yet-commons-ssl also has an implementation * @param password * @param source The encrypted data * @return */ static String decrypt(String password, String source) { final byte[] pass = password.getBytes(US_ASCII); final byte[] inBytes = Base64.getDecoder().decode(source); final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length); if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) { throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value."); } final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8); final byte[] passAndSalt = array_concat(pass, salt); byte[] hash = new byte[0]; byte[] keyAndIv = new byte[0]; for (int i = 0; i < 3 && keyAndIv.length < 48; i++) { final byte[] hashData = array_concat(hash, passAndSalt); final MessageDigest md = MessageDigest.getInstance("MD5"); hash = md.digest(hashData); keyAndIv = array_concat(keyAndIv, hash); } final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32); final SecretKeySpec key = new SecretKeySpec(keyValue, "AES"); final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48); final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16); return new String(clear, UTF_8); } private static byte[] array_concat(final byte[] a, final byte[] b) { final byte[] c = new byte[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } } 

Puoi guardare questa discussione specificando l’algoritmo di generazione della chiave come la concatenazione di due hash MD5.

Per quanto riguarda il sale menzionato qui, la pagina di opensssl enc man dice:

Quando viene utilizzato il sale, i primi otto byte dei dati crittografati vengono riservati per il sale: viene generato a caso durante la crittografia di un file e letto dal file crittografato quando viene decodificato.

Al momento openssl versione 1.1.0f-3 richiede una funzione di digest SHA-256. Senza questo non riesce a decodificare.