Расшифровать файл, зашифрованный с помощью openssl, с помощью aes-cb c -256 - PullRequest
0 голосов
/ 05 мая 2020

Я зашифровал файл с помощью команд ниже

openssl rand 32> test.key

openssl en c -aes-256-cb c -iter 10000 -pbkdf2 - salt -in test.txt -out test.txt.en c -pass file: test.key

Теперь я пытаюсь расшифровать его, используя java. tring с последних нескольких дней, но безуспешно.

Может ли кто-нибудь помочь?

мой код

package test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.IOUtils;

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 = 10000;

    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(final int key_len, final int iv_len, final MessageDigest md,
            final byte[] salt, final byte[] data, final int count) {
        final byte[][] both = new byte[2][];
        final byte[] key = new byte[key_len];
        int key_ix = 0;
        final 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(final String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
     final String fileName = "test.txt.enc";

      final File f = new File(fileName );
      try {
            // --- read base 64 encoded file ---

            List<String> lines = new ArrayList<>();

            try (BufferedReader br = new BufferedReader(new FileReader(f))) {
              //br returns as stream and convert it into a List
              lines = br.lines().collect(Collectors.toList());

          } catch (final IOException e) {
              e.printStackTrace();
          }

            final StringBuilder sb = new StringBuilder();
            for (final String line : lines) {
                sb.append(line.trim());
            }


            final String random_bin_key = "test.key";
            final byte[] passwordKey = IOUtils.toByteArray(new FileInputStream(random_bin_key));

            // --- extract salt & encrypted ---
            final byte[] headerSaltAndCipherText = sb.toString().getBytes();
            // header is "Salted__", ASCII encoded, if salt is being used (the default)
            final byte[] salt = Arrays.copyOfRange(
                    headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
            final byte[] encrypted = Arrays.copyOfRange(
                    headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);

            // --- specify cipher and digest for EVP_BytesToKey method ---

            final Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
            final 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,
                    passwordKey,
                    ITERATIONS);
            final SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
            final IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);

            // --- initialize cipher instance and decrypt ---

            aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
            final byte[] decrypted = aesCBC.doFinal(encrypted);
            final String answer = new String(decrypted);
            System.out.println(answer);
        } catch (final BadPaddingException e) {
           System.out.println(e.getMessage());
        } catch (final IllegalBlockSizeException e) {
          System.out.println(e.getMessage());
        } catch (final GeneralSecurityException e) {
          System.out.println(e.getMessage());
        } catch (final IOException e) {
          System.out.println(e.getMessage());
        }
}

Я получаю ошибку

Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

I реферируется по ссылке

https://raymii.org/s/tutorials/Encrypt_and_decrypt_files_to_public_keys_via_the_OpenSSL_Command_Line.html

https://community.cloudera.com/t5/Support-Questions/How-do-I-decrypt-AES-256-CBC-data-in-HDF-if-it-was-encrypted/td-p/97961#

пробовал с

` final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
    // strip off the salt and iv
    final ByteBuffer buffer = ByteBuffer.wrap(encryptedText);
    byte[] saltBytes = new byte[16];
    buffer.get(saltBytes, 0, saltBytes.length);
    saltBytes =  Arrays.copyOfRange(saltBytes, 8, 16);
    final byte[] ivBytes1 = new byte[cipher.getBlockSize()];
    buffer.get(ivBytes1, 0, ivBytes1.length);
    final int length = buffer.capacity() - 16 - ivBytes1.length;
    // length = length+ 16 -(length%16);
    final byte[] encryptedTextBytes = new byte[length];

    buffer.get(encryptedTextBytes);
    // Deriving the key
     final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
     final PBEKeySpec spec = new PBEKeySpec(new String(password).toCharArray(), saltBytes, 10000,
    256);
     final SecretKey secretKey = factory.generateSecret(spec);
     final SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes1));
    byte[] decryptedTextBytes = null;
    try {
      decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
    } catch (final IllegalBlockSizeException e) {
      e.printStackTrace();
    } catch (final BadPaddingException e) {
      e.printStackTrace();
    }

Получение исключения badpadding

попробовал с PBKDF2WithHmacSHA256 все еще получаю ошибку

1 Ответ

1 голос
/ 06 мая 2020

У вас несколько проблем. Наиболее очевидным является то, что вы пытаетесь прочитать IV из файла, но openssl enc в своем режиме на основе пароля по умолчанию извлекает ключ и IV из пароля и соли - даже при использовании PBKDF2. Однако как стандартные (Sun / Oracle / OpenJDK), так и поставщики BouncyCastle в Java реализуют PBKDF2 для получения только ключа - так, как он используется в PBES2 .

Даже без этого ваш метод генерации «пароля» в виде случайных байтов тоже не будет работать. Стандарт PKCS5 фактически определяет, что PBKDF2 принимает пароль как

строку октетов произвольной длины, интерпретация которой как текстовая строка не указана. Однако в интересах взаимодействия рекомендуется, чтобы приложения следовали некоторым общим правилам кодирования текста. ASCII и UTF-8 [RFC3629] - две возможности. (ASCII является подмножеством UTF-8.)

Многие системы более серьезно относятся к кодированию с возможностью взаимодействия, и Java, в частности (который изначально был разработан для использования во всем мире), определяет PBEKeySpec как содержат символов - char[] в Java это UTF-16 - которые кодируются как UTF-8 при выполнении PBKDF2. Напротив, openssl - это программа C, созданная до начала века, когда C начал признавать существование других стран, кроме США, поэтому она знает только о байтах - байтах, которые могут быть ASCII или каким-либо другим однобайтовым кодом, например EBCDI C, но, возможно, вообще не символами и уж точно не какими-либо из тех странных иностранных символов, которые не помещаются в байт. Вероятность того, что последовательность из 32 случайных байтов будет действительной UTF-8, очень мала; мне слишком сложно рассчитывать аналитически, но я провел тест на 100 миллионов случайных значений и получил только одно, которое будет работать с вашей схемой. (Я собирался протестировать миллиард, но устал ждать.)

Кроме того, поскольку пароль должен быть текстовым, openssl читает -pass file: как текстовый файл и рассматривает его как строку. Это означает, что если какой-либо из случайных байтов является нулевым байтом или байтом, соответствующим символу новой строки, оставшаяся часть данных в файле отбрасывается и игнорируется для получения ключа и IV. Это будет происходить в среднем примерно в 1 из 4 раз для случайных 32-байтовых значений и примерно в 1 из 20 раз в файле достаточно рано, чтобы сделать результат криптографически слабым и поддающимся взлому.

Возникает вопрос: почему вы вообще используете шифрование на основе пароля? Если ваш «ключ» составляет 32 байта от приличного безопасного ГСЧ - а это openssl rand - вам не нужно его усиливать, он уже действителен в качестве ключа. Вы можете использовать openssl enc для шифрования на основе ключей , а не на основе пароля, и это более эффективно, безопаснее и намного проще в Java - огромная победа. ЕСЛИ вы используете новый случайный ключ для каждого шифрования, вам даже не нужно использовать настоящий IV, вы можете просто использовать нулевой IV, как я сделал ниже. Но если вы собираетесь повторно использовать ключ / any, вам необходимо использовать уникальный и непредсказуемый - обычно случайный - IV для каждого шифрования и передавать его вместе с данными, возможно, просто поместив его в

Так или иначе, вот довольно простая Java программа, которая может обрабатывать любой случай: форма openssl pdbkf2 с «паролем», который на самом деле не является паролем и не UTF-8 , или более разумная форма на основе клавиш (но для этой демонстрации с нулевым IV):

//nopackage
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class SO61613286 {
    static public void main (String[] args) throws Exception /* let JVM give error */{
        // arguments: P/K, filename output from openssl enc, filename of text pw or binary key
        byte[] file = Files.readAllBytes(Paths.get(args[1])); 
        byte[] fil2 = Files.readAllBytes(Paths.get(args[2])); 
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        if( args[0].startsWith("P") ){
            // possibly truncate 'password' in fil2
            int n = 0; for( ; n < fil2.length; n++ ) if( fil2[n]==0 || fil2[n]=='\n' ) break;
            if( n < fil2.length ) fil2 = Arrays.copyOf(fil2, n);
            // extract salt and derive ...
            byte[] salt = Arrays.copyOfRange(file, 8, 16);
            byte[] derive = PBKDF2 ("HmacSHA256", fil2, salt, 10000, 48);
            // ... key is first 32, IV is last 16
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(derive,0,32,"AES"), new IvParameterSpec(derive,32,16));
            // remainder of file is ciphertext
            System.out.write( cipher.doFinal(file,16,file.length-16) );
        }else{
            // just use fil2 as key and zeros for IV ...
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(fil2,"AES"), new IvParameterSpec(new byte[16]));
            // ... all of file is ciphertext
            System.out.write( cipher.doFinal(file,0,file.length) );
            // !!!if key will be reused, must use better IVs and transmit with the data!!!
        }
    }
    public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
            throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
        byte[] result = new byte[len];
        Mac mac = Mac.getInstance(prf);
        mac.init (new SecretKeySpec (pass,prf));
        byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
        while( /*remaining*/len>0 ){
            for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break; 
            byte[] u = saltcnt, x = new byte[mac.getMacLength()];
            for( int i = 1; i <= iter; i++ ){
                u = mac.doFinal (u); 
                for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
            }
            int len2 = Math.min (len, x.length);
            System.arraycopy (x,0, result,result.length-len, len2);
            len -= len2;
        }
        return result;
    }
    public static void testutf8 (){
        Random r = new Random();
        byte[] t = new byte[32];
        for( int i = 0; i < 1000000000; i++ ){
            r.nextBytes(t); 
            if( Arrays.equals(new String (t, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), t) ) 
                System.out.println(i+" "+Arrays.toString(t));
            if( i % 1000000 == 999999 ) System.out.println (i);
        }
    }
}

и демо:

$ openssl rand 32 >SO61613286.rnd   # repeated several times until I got this:
$ xxd SO61613286.rnd   # notice the null byte
0000000: ab1a 1384 9238 0900 c947 6b9a c23d 5ee0  .....8...Gk..=^.
0000010: 32f0 6b2f 91ec 2dd9 a69d eb7d e00e 37ff  2.k/..-....}..7.
$
$ echo the name of the cat >SO61613286.in
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc1 -pass file:SO61613286.rnd -pbkdf2 -iter 10000
$ java8 -cp . SO61613286 P SO61613286.enc1 SO61613286.rnd
the name of the cat
$
$ openssl aes-256-cbc -in SO61613286.in -out SO61613286.enc2 -K $(xxd -p -c32 SO61613286.rnd) -iv 00
hex string is too short, padding with zero bytes to length
$ # that's a warning and can be ignored, as long as we don't need real IVs (see above)
$ java8 -cp . SO61613286 K SO61613286.enc2 SO61613286.rnd      
the name of the cat
$
...