Какие параметры AES используются и какие шаги выполняются внутри crypto-js при шифровании сообщения с паролем?

Фон: приложение, над которым я работаю, должно работать в автономном режиме. Я должен зашифровать некоторые текстовые данные, используя пароль в качестве ключа на стороне сервера Java. Зашифрованные данные передаются на страницу HTML5, и на стороне клиента с помощью библиотеки crypto-js зашифрованные данные сервера должны быть расшифрованы.

Моя проблема: чтобы зашифровать мое сообщение таким образом, чтобы клиент мог его расшифровать с помощью crypt-js (используя пароль, введенный пользователем), мне нужно знать точные шаги, которые ожидает crypto-js. при шифровании сообщения.

Что мне нужно знать: у меня есть следующий код шифрования, который выполняет шифрование сообщения на стороне клиента с помощью crypto-js.

var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());

Мне нужно знать параметры AES, используемые CryptoJS при шифровании сообщения (Не уверен, что они собой представляют, но это звучит так: размер ключа (256), заполнение (pkcs5), режим (CBC), алгоритм PBE (PBKDF2) , соль (случайная), количество итераций (100)). Было бы здорово, если бы кто-нибудь мог это подтвердить ... Я пытался разгадать эту загадку последние несколько дней?

Мне нужно знать, какие шаги выполняет CryptoJS при шифровании сообщения AES.


person user1455719    schedule 01.12.2014    source источник
comment
сэр, я перенес правки отсюда в отдельный вопрос. stackoverflow.com/questions/27248644/   -  person user1455719    schedule 02.12.2014


Ответы (3)


CryptoJS использует нестандартный OpenSSL. KDF для получения ключей (EvpKDF) с < strong> MD5 в качестве алгоритма хеширования и 1 итерация. IV также является производным от пароля, что означает, что для его расшифровки на стороне Java необходимы только фактический зашифрованный текст, пароль и соль.

Другими словами, PBKDF2 не используется для получения ключа в парольном режиме CryptoJS. По умолчанию AES-256 используется в режиме CBC с заполнением PKCS5 (это то же, что и заполнение PKCS7). Имейте в виду, что вам могут понадобиться файлы политики юрисдикции неограниченной силы JCE. См. Также Почему существуют ограничения об использовании шифрования с ключами сверх определенной длины?

Следующий код воссоздает KDF в Java (keySize и ivSize равны 8, соответственно, 4 для AES-256 и взяты из).

public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
    int targetKeySize = keySize + ivSize;
    byte[] derivedBytes = new byte[targetKeySize * 4];
    int numberOfDerivedWords = 0;
    byte[] block = null;
    MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
    while (numberOfDerivedWords < targetKeySize) {
        if (block != null) {
            hasher.update(block);
        }
        hasher.update(password);
        block = hasher.digest(salt);
        hasher.reset();

        // Iterations
        for (int i = 1; i < iterations; i++) {
            block = hasher.digest(block);
            hasher.reset();
        }

        System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
                Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));

        numberOfDerivedWords += block.length/4;
    }

    System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
    System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);

    return derivedBytes; // key + iv
}

Вот полный класс для справки:

public class RecreateEVPkdfFromCryptoJS {
    public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException {
        String msg = "hello";
        String password = "mypassword";
        String ivHex = "aab7d6aca0cc6ffc18f9f5909753aa5f";
        int keySize = 8; // 8 words = 256-bit
        int ivSize = 4; // 4 words = 128-bit
        String keyHex = "844a86d27d96acf3147aa460f535e20e989d1f8b5d79c0403b4a0f34cebb093b";
        String saltHex = "ca35168ed6b82778";
        String openSslFormattedCipherTextString = "U2FsdGVkX1/KNRaO1rgneK9S3zuYaYZcdXmVKJGqVqk=";
        String cipherTextHex = "af52df3b9869865c7579952891aa56a9";
        String padding = "PKCS5Padding";

        byte[] key = hexStringToByteArray(keyHex);
        byte[] iv = hexStringToByteArray(ivHex);
        byte[] salt = hexStringToByteArray(saltHex);
        byte[] cipherText = hexStringToByteArray(cipherTextHex);

        byte[] javaKey = new byte[keySize * 4];
        byte[] javaIv = new byte[ivSize * 4];
        evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv);
        System.out.println(Arrays.equals(key, javaKey) + " " + Arrays.equals(iv, javaIv));

        Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Must specify the mode explicitly as most JCE providers default to ECB mode!!

        IvParameterSpec ivSpec = new IvParameterSpec(javaIv);
        aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec);

        byte[] byteMsg = aesCipherForEncryption.doFinal(cipherText);
        System.out.println(Arrays.equals(byteMsg, msg.getBytes("UTF-8")));
    }

    public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        return evpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
    }

    public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        int targetKeySize = keySize + ivSize;
        byte[] derivedBytes = new byte[targetKeySize * 4];
        int numberOfDerivedWords = 0;
        byte[] block = null;
        MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
        while (numberOfDerivedWords < targetKeySize) {
            if (block != null) {
                hasher.update(block);
            }
            hasher.update(password);
            block = hasher.digest(salt);
            hasher.reset();

            // Iterations
            for (int i = 1; i < iterations; i++) {
                block = hasher.digest(block);
                hasher.reset();
            }

            System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
                    Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));

            numberOfDerivedWords += block.length/4;
        }

        System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
        System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);

        return derivedBytes; // key + iv
    }

    /**
     * Copied from http://stackoverflow.com/a/140861
     * */
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
}

и код JavaScript, который использовался для генерации значений в коде Java:

var msg = "hello";
var password = "mypassword"; // must be present on the server
var encrypted = CryptoJS.AES.encrypt( msg, password );
var ivHex = encrypted.iv.toString();
var ivSize = encrypted.algorithm.ivSize; // same as the blockSize
var keySize = encrypted.algorithm.keySize;
var keyHex = encrypted.key.toString();
var saltHex = encrypted.salt.toString(); // must be sent as well
var openSslFormattedCipherTextString = encrypted.toString(); // not used
var cipherTextHex = encrypted.ciphertext.toString(); // must be sent
person Artjom B.    schedule 02.12.2014
comment
Подобный код можно найти в моих ответах на PHP и Python. - person Artjom B.; 11.08.2017

Следуя отличному ответу @Artjom B как на этот вопрос, так и на здесь для пользователей Python, я присоединяюсь к полному коду Java, который помог мне расшифровать строку, которая была зашифрована таким образом

var encrypted = CryptoJS.AES.encrypt(message, password).toString();

Этот фрагмент кода Java полезен, когда вы знаете только пароль (т.е. соль не была отправлена ​​с зашифрованной строкой):

public String decrypt(String encrypted, String password) throws Exception {
    int keySize = 8;
    int ivSize = 4;
    // Start by decoding the encrypted string (Base64)
    // Here I used the Android implementation (other Java implementations might exist)
    byte[] cipherText = Base64.decode(encrypted, Base64.DEFAULT);
    // prefix (first 8 bytes) is not actually useful for decryption, but you should probably check that it is equal to the string "Salted__"
    byte[] prefix = new byte[8];
    System.arraycopy(cipherText, 0, prefix, 0, 8);
    // Check here that prefix is equal to "Salted__"
    // Extract salt (next 8 bytes)
    byte[] salt = new byte[8];
    System.arraycopy(cipherText, 8, salt, 0, 8);
    // Extract the actual cipher text (the rest of the bytes)
    byte[] trueCipherText = new byte[cipherText.length - 16];
    System.arraycopy(cipherText, 16, trueCipherText, 0, cipherText.length - 16);
    byte[] javaKey = new byte[keySize * 4];
    byte[] javaIv = new byte[ivSize * 4];
    evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv);
    Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivSpec = new IvParameterSpec(javaIv);
    aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec);

    byte[] byteMsg = aesCipherForEncryption.doFinal(trueCipherText);
    return new String(byteMsg, "UTF-8");
}

public  byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
    return evpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
}

public  byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
    int targetKeySize = keySize + ivSize;
    byte[] derivedBytes = new byte[targetKeySize * 4];
    int numberOfDerivedWords = 0;
    byte[] block = null;
    MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
    while (numberOfDerivedWords < targetKeySize) {
        if (block != null) {
            hasher.update(block);
        }
        hasher.update(password);
        block = hasher.digest(salt);
        hasher.reset();

        // Iterations
        for (int i = 1; i < iterations; i++) {
            block = hasher.digest(block);
            hasher.reset();
        }

        System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
                Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));

        numberOfDerivedWords += block.length/4;
    }

    System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
    System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);

    return derivedBytes; // key + iv
}
person Mohammed El Moumni    schedule 01.10.2018

Я ищу документацию здесь:

  • размер ключа: «Если вы используете парольную фразу, будет сгенерирован 256-битный ключ».
  • заполнение: Pkcs7 (по умолчанию)
  • режим: CBC (по умолчанию)
  • iv: создается и сохраняется в объекте зашифрованного текста: используется с encrypted.iv

Материал для генерации ключа:

  • соль: создается и хранится в объекте зашифрованного текста: используется с "encrypted.salt" (хотя на самом деле это не говорит об этом, поэтому я предполагаю здесь)
  • Алгоритм pbe: неясно. Это не задокументировано.
  • количество итераций: я нигде не могу найти это задокументировано. В примерах кода используется 1000.

Вы можете установить параметры вручную, что может быть безопаснее, чем полагаться на значения по умолчанию, например какой-то псевдокод на основе примеров из документации:

var salt = CryptoJS.lib.WordArray.random(128/8);
var iv = CryptoJS.lib.WordArray.random(128);
var key256Bits10000Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 256/32, iterations: 10000 }); //I don't know this is dividing by 32
var encrypted = CryptoJS.AES.encrypt("Message", key, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv:iv });

Вероятно, вам придется поэкспериментировать. Я бы делал это шаг за шагом. Получите ключи на основе паролей, которые будут совпадать, изменив эти параметры, затем получите совпадающий зашифрованный текст, а затем определите расшифровку. Избегайте стремления упростить такие вещи, как пропуск IV или использование ECB.

person Isaac Potoczny-Jones    schedule 01.12.2014
comment
PBKDF2 фактически не используется во время шифрования в режиме пароля - person Artjom B.; 01.12.2014
comment
Странный. Я предполагал, потому что это единственное, что указано в другом месте. Вы знаете, как он генерирует ключ в режиме пароля? Если это так, то еще больше причин делать это вручную. - person Isaac Potoczny-Jones; 01.12.2014
comment
Artjom B, было бы здорово, если бы ты мог написать ответ. Я как бы застрял и сбит с толку. - person user1455719; 02.12.2014
comment
user1455719, почему бы вам не попробовать опубликовать конкретный код, который вы пытаетесь использовать на сторонах Java и Javascript, и тогда, возможно, кто-то сможет помочь. Я настоятельно рекомендую сгенерировать ключ явно и отладить шаг за шагом, пока вы не добьетесь совпадения двух сторон. - person Isaac Potoczny-Jones; 02.12.2014
comment
Я отредактировал вопрос с подробным описанием моих кодов javascript и java. Просмотрите вопрос - person user1455719; 02.12.2014