Расшифровка Java AES обнаруживает неверный ключ

Я пишу приложение для Android, которое выполняет шифрование / дешифрование файлов AES. Я хочу иметь возможность определять, указан ли неправильный пароль и, следовательно, для расшифровки получен несоответствующий ключ. Я использую AES / CBC / PKCS7Padding с 256-битным ключом. Если я сделаю cipher.doFinal (), я могу попробовать / поймать BadPaddingException, и он говорит мне, что что-то не так и, вероятно, ключ был неправильным. Но если я использую CipherInputStream для чтения зашифрованного файла, я не получаю отзывов о правильности заполнения. Поэтому, если я намеренно указываю неверный пароль, он расшифровывает файл, а затем сообщает, что все в порядке, однако расшифрованный файл является полным мусором. Итак, мой вопрос: как определить плохое заполнение при использовании CipherInputStream?


person Alex Amiryan    schedule 20.07.2012    source источник
comment
Я использую PBEWithSHA256And256BitAES-CBC-BC   -  person Alex Amiryan    schedule 22.07.2012


Ответы (6)


Добавьте к своим данным какой-нибудь известный заголовок. Сначала расшифруйте его, и если он не соответствует тому, что вы ожидали, остановите и верните ошибку.

person Nikolay Elenkov    schedule 21.07.2012
comment
Это сработает, если будет затронуто все сообщение или ключ, иначе это будет плохой CRC. Лично я бы предпочел исправить саму проблему (см. Мой второй ответ), а затем использовать шифрование в режиме GCM. - person Maarten Bodewes; 21.07.2012
comment
Цель этого не в том, чтобы быть CRC, просто чтобы упростить / ускорить проверку, есть ли у них правильный ключ / пароль. GCM или CBC + HMAC можно использовать, чтобы убедиться, что все сообщение не было изменено. Если они извлекают ключ из пароля, они могут также использовать какую-то контрольную сумму / хэш ключа для быстрой проверки пароля. - person Nikolay Elenkov; 22.07.2012
comment
Я думаю, что расширю CipherInputStream и изменю его, чтобы выбросить BadPaddingException, потому что проверка заполнения полностью приемлема для меня, поэтому я не использовал аутентификацию HMAC при первоначальной разработке своего приложения. - person Alex Amiryan; 22.07.2012
comment
Что ж, вы знаете свое приложение лучше всего, но что произойдет, если кто-то, скажем, удаляет несколько блоков ваших данных, и оно правильно расшифровывается, но на самом деле является неполным? - person Nikolay Elenkov; 22.07.2012
comment
Ваш известный подход к данным будет работать как индикатор, и он, безусловно, будет работать лучше, чем просто полагаться на заполнение (что могло бы пойти не так, по крайней мере, 1/256 раз). Тем не менее, если вы можете добавлять данные, я бы предпочел использовать тег аутентификации (GCM) или дополнительный HMAC (который требует другого ключа, чем тот, который вы используете для шифрования). У CBC есть интересное свойство, заключающееся в том, что ошибки появляются только в двух блоках, если бит перевернут. - person Maarten Bodewes; 22.07.2012
comment
На самом деле это то, что я пытался сказать - что может потребоваться какая-то форма аутентификации (которой нет в известных данных). - person Nikolay Elenkov; 22.07.2012

Попробуйте вместо этого использовать режим GCM (поставщик Java 7 или Bouncy Castle). Уловка с заполнением заключается в том, что иногда оно оказывается правильным после того, как сообщение было изменено (примерно раз в 256). В режиме GCM будет добавлена ​​защита целостности, поэтому любое изменение приведет к исключению, производному от BadPaddingException.

Одна вещь: вы должны добавить (случайный) одноразовый номер при шифровании с помощью GCM (на самом деле это правило также и для режима CBC, но последствия использования неслучайного IV в CBC менее серьезны).

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

[РЕДАКТИРОВАТЬ]: это не ответ, но ввод можно использовать для создания лучшего CipherInputStream, см. Мой другой ответ по этому вопросу.

person Maarten Bodewes    schedule 20.07.2012
comment
Спасибо за предложение, но он приводит к успеху каждый раз, а не один раз из 256. Я читал код CipherInputStream, и он ловит BadPaddingException при обработке последнего блока и просто возвращает, что поток завершен. Может мне нужно как-то доработать код класса CipherInputStream? - person Alex Amiryan; 20.07.2012
comment
Если у вас установлен NoPadding, ваш код никогда не будет обнаруживать ошибочные отступы. Убедитесь, что ваш код дешифрования установлен для PKCS7. - person rossum; 20.07.2012
comment
Конечно, это PKCS7Padding. Вот почему я задаю этот вопрос. - person Alex Amiryan; 20.07.2012
comment
Извините, это, конечно, не помогает, вместо этого следует закрыть или в конце неактивный InputStream. - person Maarten Bodewes; 20.07.2012
comment
@rossum кажется, что CipherInputStream проводит это исключение под таблицей (что для меня пахнет довольно плохим дизайном), посмотрите на мой другой ответ. - person Maarten Bodewes; 22.07.2012

Я думаю, что плохой padding по какой-то причине пойман, это из CipherInputStream источника:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {obuffer = null;}
        catch (BadPaddingException e) {obuffer = null;}
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}
person Maarten Bodewes    schedule 21.07.2012
comment
Вероятно, это необходимо для правильной обработки исключения, потоки не должны вызывать исключения безопасности. Обратите внимание, что мой предыдущий ответ включал режим GCM, но это не поможет, так как исключение из-за плохой целостности также является BadPaddingException. Может быть, возьмите CipherInputStream и перепишите его - вместо этого оберните BadPaddingException в IOException, чтобы выполнить контракт. - person Maarten Bodewes; 21.07.2012

Вот модифицированная версия метода getMoreData () в CipherInputStream, она может быть полезна тем, кто столкнулся с моей проблемой:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {
            throw new IOException(e);
        }
        catch (BadPaddingException e) {
            throw new IOException(e);
        }
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}
person Alex Amiryan    schedule 23.07.2012
comment
Не могли бы вы, пожалуйста, хотя бы проголосовать за за мой ответ, который включает источник? - person Maarten Bodewes; 30.07.2012

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

Итак, что я сделал:

Расшифруйте зашифрованную строку.
Зашифруйте строку еще раз, используя правильный ключ.
Расшифруйте предыдущую зашифрованную строку.
Сопоставьте, если исходный расшифрованный ключ равен новому расшифрованному ключу.

    Cipher c = Cipher.getInstance(algorithm);
    c.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
    byte[] decValue = c.doFinal(encryptedData.getBytes());
    decryptedValue = new String(decValue,"UTF-8");

  //now i have the string decrypted in decryptedValue

  byte[] encryptAgain = encrypt(decryptedValue);
  String encryptAgaindecripted = new String(c.doFinal(encryptAgain),"UTF-8");

  //if keys match then it uses the same key and string is valid
 if (decryptedValue.equals(encryptAgaindecripted)){
  //return valid
 }

надеюсь, это кому-то поможет.

person user2505009    schedule 11.09.2014

Я тоже столкнулся с этим. У меня был тест, который надежно терпел неудачу пару раз из тысячи запусков с AES / CBC / PKCS5Padding, который поддерживается в Java.

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

Однако я сделал другое исправление и просто добавил хэш содержимого md5 к простому тексту перед шифрованием, который я проверяю при расшифровке. Просто добавьте содержимое к хешу md5 и при расшифровке возьмите первые 22 символа хеша md5 и убедитесь, что остальная часть строки имеет тот же хеш, и вызовите исключение, если это не так, или верните открытый текст (без хеша md5) если он совпадает. Это будет работать независимо от алгоритма шифрования. Вероятно, в режиме GCM это действительно не нужно. В любом случае, таким образом вы сможете избежать дополнительных зависимостей от надувного замка.

person Jilles van Gurp    schedule 09.09.2014