Шифрование CryptoJS AES и дешифрование Java AES

Я спрашиваю об этом только потому, что я прочитал много сообщений в течение 2 дней о криптографическом шифровании AES, и только когда я подумал, что получаю его, я понял, что не получаю его вообще.

этот пост ближе всего к моей проблеме, у меня точно такая же проблема, но она без ответа:

шифрование CryptoJS AES и несоответствие значения расшифровки JAVA AES

Я пытался сделать это во многих отношениях, но я не получил его право.

Во-Первых

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

второй

у меня есть доступ к секретному ключу, и я могу его изменить (поэтому при необходимости можно настроить длину).

В шифрование выполняется на CryptoJS, и они отправляют зашифрованную строку в качестве параметра GET.

GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
 return new Promise((resolve, reject) => {
   const currentDateInMilliseconds = new Date().getTime();
   const secret = tokenSecret.secret;
   var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
   encrypted = encrypted.toString();
   self.urlParams = {
     token: encrypted,
     time: currentDateInMilliseconds
   };
   resolve();
 });
};

Я могу легко расшифровать это на javascript, используя CryptoJS с:

var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
    console.log(decrypted.toString(CryptoJS.enc.Utf8)); 

но я не хочу делать это на Javascript, по соображениям безопасности, поэтому я пытаюсь расшифровать это на Java:

String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);

прежде чем я имел представление о том, что я делаю, я попробовал base64 декодирование, добавив некоторые IV и много вещей, которые я читал, конечно, ни один из них не работал.

но после Я начал понимать, что я делаю, я написал этот простой скрипт выше и получил ту же ошибку на посту:недопустимая длина ключа AES

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

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

Итак, мой вопрос в основном, если я получил зашифрованную строку (в JavaScript, как первый скрипт) и секретный ключ, есть ли способ его расшифровать (в Java)? Если да, то как это сделать?

2 ответов


CryptoJS реализует ту же функцию вывода ключа, что и OpenSSL, и тот же формат, чтобы поместить IV в зашифрованные данные. Таким образом, применяется весь код Java, который имеет дело с кодированными данными OpenSSL.

учитывая следующий код Javascript:

var text = "The quick brown fox jumps over the lazy dog.  ";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);

получаем шифрованный текст:

U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=

на стороне Java у нас есть

String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";

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(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);

byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);

System.out.println(decryptedText);

результат:

The quick brown fox jumps over the lazy dog.  

это текст, с которого мы начали. И emojis, акценты и umlauts работают также.

GenerateKeyAndIV является вспомогательной функцией, которая переопределяет функцию вывода ключа OpenSSL EVP_BytesToKey (см. https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).

/**
 * Generates a key and an initialization vector (IV) with the given salt and password.
 * <p>
 * This method is equivalent to OpenSSL's EVP_BytesToKey function
 * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
 * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
 * </p>
 * @param keyLength the length of the generated key (in bytes)
 * @param ivLength the length of the generated IV (in bytes)
 * @param iterations the number of digestion rounds 
 * @param salt the salt data (8 bytes of data or <code>null</code>)
 * @param password the password data (optional)
 * @param md the message digest algorithm to use
 * @return an two-element array with the generated key and IV
 */
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);
    }
}

обратите внимание, что вы должны установить расширение Java Cryptography (JCE)Неограниченная Сила Юрисдикционная Политика. В противном случае AES с размером ключа 256 не будет работать и бросать исключение:

java.security.InvalidKeyException: Illegal key size

обновление

Я заменил Java-код Ola Bini of EVP_BytesToKey, который я использовал в первой версии моего ответа, с более устойчивым и легче для понимания Java-код (см. выше).

см. Также как расшифровать файл на Java, зашифрованный с помощью команды openssl с помощью AES?.


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

все должно быть байт в байт то же самое с обеих сторон. Фактически это означает указание всего с обеих сторон, а не полагаться на значения по умолчанию. Вы можете использовать значения по умолчанию, только если вы используете одну и ту же систему на обоих концах. Даже тогда лучше уточнить именно так.

ключ, IV, режим шифрования, заполнение и преобразование строки в байты все должны быть одинаковыми на обоих концах. Особенно стоит проверить, что ключевые байты одинаковы. Если вы используете функцию Деривации ключей (KDF) для генерации вашего ключа, то все параметры для этого должны быть одинаковыми и, следовательно, точно указанными.

ваша "недопустимая длина ключа AES" вполне может указывать на проблему с созданием вашего ключа. Вы используете getBytes(). Это, вероятно, ошибка. Вам нужно указать, какие байты вы получаете: ANSI, UTF-8, EBCDIC, что угодно. Вероятной причиной этой проблемы является предположение по умолчанию для преобразования строки в байт. Укажите преобразование, которое будет использоваться явно с обоих концов. Таким образом, вы можете быть уверены, что они совпадают.

Crypto предназначен для сбоя, если параметры не совпадают точно для шифрования и дешифрования. Например, даже одно битовое различие в ключе приведет к сбою.