Подтвердить что ты не робот

Что такое IV по умолчанию при шифровании с помощью шифрования aes_256_cbc?

Я создал случайный 256-битный симметричный ключ в файле для использования для шифрования некоторых данных с помощью командной строки OpenSSL, которые мне нужно расшифровывать позже программно, используя библиотеку OpenSSL. У меня нет успеха, и я думаю, что проблема может быть в векторе инициализации, который я использую (или не использую).

Я шифрую данные с помощью этой команды:

/usr/bin/openssl enc -aes-256-cbc -salt -in input_filename -out output_filename -pass file:keyfile

Я использую следующий вызов для инициализации дешифрования данных:

EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, keyfile.data(), nullptr))

keyfile - это vector<unsigned char>, который содержит 32 байта ключа. Мой вопрос касается последнего параметра. Он должен быть вектором инициализации алгоритма шифрования. Я не указывал IV при шифровании, поэтому некоторые значения по умолчанию должны были использоваться.

Пропущено ли значение nullptr для этого параметра означает "использовать значение по умолчанию"? Является ли значение по умолчанию значением null, и ничто не добавляется к первому блоку шифрования?

Я должен упомянуть, что я могу расшифровать из командной строки без предоставления IV.

4b9b3361

Ответ 1

Что такое IV по умолчанию при шифровании с помощью шифрования EVP_aes_256_cbc() [sic]...

Пропускает ли nullptr для этого параметра означает "использовать значение по умолчанию"? Является ли значение по умолчанию значением null, и ничто не добавляется к первому блоку шифрования?

Нет. Вы должны его предоставить. Для полноты IV должен быть непредсказуемым.

Непредсказуемый немного отличается от уникального и случайного. Например, SSLv3 используется для использования последнего блока зашифрованного текста для следующего блока IV. Он был уникальным, но он не был ни случайным, ни непредсказуемым, и это сделало SSLv3 уязвимым для выбранных атак с открытым текстом.

Другие библиотеки делают умные вещи, такие как предоставление нулевого вектора (строка из 0). Их нападавшие благодарны им за это. Также см. Почему использование неслучайного IV с режимом CBC является уязвимостью? на переполнении стека и Безопасен ли AES в режиме CBC если используется известный и/или фиксированный IV? на Crypto.SE.


/usr/bin/openssl enc -aes-256-cbc...

Я должен упомянуть, что я могу расшифровать из командной строки без предоставления IV.

OpenSSL использует внутреннюю функцию деривации mashup/key, которая берет пароль и выводит ключ и iv. Его называют EVP_BytesToKey, и вы можете прочитать об этом на страницах руководства. На страницах человека также говорится:

Если общий ключ и длина IV меньше длины дайджеста, а MD5 используется, алгоритм деривации совместим с PKCS # 5 v1.5, в противном случае для получения дополнительных данных используется нестандартное расширение.

Есть много примеров EVP_BytesToKey, как только вы знаете, что искать. Ключ Openssl к ключу является одним из C. Как расшифровать файл в Java, зашифрованном командой openssl, используя AES в один в Java.


EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, keyfile.data(), nullptr))

Я не указывал IV при шифровании, поэтому некоторые значения по умолчанию должны были использоваться.

Проверьте возвращаемые значения. Вызов должен был провалиться где-то по пути. Может быть, не в EVP_DecryptInit_ex, но верно до EVP_DecryptFinal.

Если это не сработает, напишите отчет об ошибке.

Ответ 2

EVP_DecryptInit_ex - это интерфейс для примитива дешифрования AES. Это всего лишь одна часть того, что вам нужно для расшифровки формата шифрования OpenSSL. Формат шифрования OpenSSL не является хорошо документированным, но вы можете работать с ним обратно из кода и некоторых документов. Вычисление ключа и IV объясняется в документации EVP_BytesToKey:

   The key and IV is derived by concatenating D_1, D_2, etc until enough
   data is available for the key and IV. D_i is defined as:

           D_i = HASH^count(D_(i-1) || data || salt)

   where || denotes concatentaion, D_0 is empty, HASH is the digest
   algorithm in use, HASH^1(data) is simply HASH(data), HASH^2(data) is
   HASH(HASH(data)) and so on.

   The initial bytes are used for the key and the subsequent bytes for the
   IV.

"HASH" здесь MD5. На практике это означает, что вы вычисляете хэши следующим образом:

Hash0 = ''
Hash1 = MD5(Hash0 + Password + Salt)
Hash2 = MD5(Hash1 + Password + Salt)
Hash3 = MD5(Hash2 + Password + Salt)
...

Затем вы извлекаете нужные вам байты для ключа, а затем вытаскиваете байты, которые вам нужны для IV. Для AES-128 это означает, что Hash1 является ключом, а Hash2 является IV. Для AES-256 ключ - это Hash1 + Hash2 (конкатенированный, не добавленный), а Hash3 - это IV.

Вам нужно снять верхний заголовок Salted___, а затем использовать соль для вычисления ключа и IV. Затем у вас есть кусочки для подачи в EVP_DecryptInit_ex.

Так как вы делаете это на С++, вы, вероятно, можете просто прокопать код enc и повторно использовать его (после проверки его совместимости с вашим использованием).

Обратите внимание, что OpenSSL IV генерируется случайным образом, поскольку он является результатом процесса хеширования, включающего случайную соль. Безопасность первого блока не зависит от того, что IV является случайным per se; просто требуется, чтобы конкретная пара IV + Key никогда не повторялась. Процесс OpenSSL гарантирует, что до тех пор, пока случайная соль никогда не будет повторяться.

Возможно, что использование MD5 таким образом запутывает ключ и IV таким образом, что утечка информации, но я никогда не видел анализа, который утверждает, что. Если вам нужно использовать формат OpenSSL, у меня не будет никаких колебаний по поводу его IV поколения. Большие проблемы с форматом OpenSSL состоят в том, что он быстро переходит в грубую силу (4 раунда MD5 недостаточно растягивается), и ему не хватает аутентификации.

Ответ 3

const cipher = crypto.createCipher('aes-256-cbc', '67f969129e2f78f2ee286d16efec0dad');
var encrypted = cipher.update('Hello Java', 'utf8', 'base64');
encrypted += cipher.final('base64');
console.log(encrypted);

const decipher = crypto.createDecipher('aes-256-cbc', '67f969129e2f78f2ee286d16efec0dad');
var decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');
console.log(decrypted);

Я использую вышеупомянутую функцию в узле JS, что нормально. Но я не могу расшифровать то же самое с помощью Java. Я попытался с помощью приведенного ниже кода Java. Пожалуйста помоги.

public static String decryptOpenSSL(String cipherText, String secret) {
        try {
            byte[] cipherData = Base64.getDecoder().decode(cipherText);
            byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            final byte[][] keyAndIV = generateKeyAndIV(32, 16, 1, saltData, secret.getBytes(), md5);
            System.out.println(new String(keyAndIV[0]));
            System.out.println(new String(keyAndIV[1]));
            SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
            IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
            byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] decryptedData = cipher.doFinal(encrypted);
            String decryptedText = new String(decryptedData);
            return decryptedText;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static byte[][] generateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {

        int digestLength = md.getDigestLength();
        int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
        byte[] generatedData = new byte[requiredLength];
        int generatedLength = 0;

        try {
            md.reset();
            // Repeat process until sufficient data has been generated
            while (generatedLength < keyLength + ivLength) {
                // Digest data (last digest if available, password data, salt if available)
                if (generatedLength > 0) {
                    md.update(generatedData, generatedLength - digestLength, digestLength);
                }
                md.update(password);
                if (salt != null) {
                    md.update(salt, 0, 8);
                }
                md.digest(generatedData, generatedLength, digestLength);
                // additional rounds
                for (int i = 1; i < iterations; i++) {
                    md.update(generatedData, generatedLength, digestLength);
                    md.digest(generatedData, generatedLength, digestLength);
                }
                generatedLength += digestLength;
            }
            // Copy key and IV into separate byte arrays
            byte[][] result = new byte[2][];
            result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
            if (ivLength > 0) {
                result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
            }
            return result;
        } catch (DigestException e) {
            throw new RuntimeException(e);
        } finally {
            // Clean out temporary data
            Arrays.fill(generatedData, (byte) 0);
        }
    }

public static void main(String[] args) {
    String decData = decryptOpenSSL("IIrVEmf4ssJRnRDnebEeWnqba1qux7fUBOiOtZViWnM=", "67f969129e2f78f2ee286d16efec0dad");
    System.out.println(decData);
}