Расшифровка AES/CBC не работает должным образом

У меня есть некоторые выпущенные данные для расшифровки с использованием AES/CBC/PKCS5Padding в Java. Я шифрую два значения A и B, а затем данные из файла. Зашифрованные значения записываются в файл в описанной последовательности. При расшифровке байты для соответствующих частей расположены правильно (подтверждено с помощью отладки) и входные данные для функций расшифровки верны, проблем с заполнением нет.

Код шифрования:

byte[] iv = {..........};

IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");

cipher.init(Cipher.ENCRYPT_MODE, fileKey, ivParameterSpec);

byte[] encryptedA = cipher.update(A);

byte[] encryptedB = cipher.update(B);

while( true){        
         
    if( blocks > 1 ) {
        encrypted = cipher.update(data);
    }
    else {
        encrypted = cipher.doFinal(data);
    }
    blocks--; 
    //write bytes to file
}
      

При шифровании я вижу, что вектор внутри шифра обновляется после каждого обновления(), как и ожидалось (последний зашифрованный текст — это вектор для последующих обновлений. Например, зашифрованныйA — это вектор шифра в момент, когда я вызываю update(B)

Код расшифровки

Cipher cipherB = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherB.init(Cipher.DECRYPT_MODE, fileKey, ivParameterSpecB);

byte[] decryptedA = cipherB.update(encryptedA);
byte[] decryptedB = cipherB.update(encryptedB);

while( true){

      if(blocks > 1 ) {
            decrypted = cipherB.update(encryptedBytes);
      }
      else {
            decrypted = cipherB.doFinal(encryptedBytes);
      } 
       blocks--; 
       //write bytes to file  
}

То, что происходит в этот момент, очень странно.

Первый вызов cipherB.update(encryptedA) вообще ничего не делает. Он возвращает пустой массив и не обновляет вектор внутри шифра. Второй вызов cipherB.update(encryptedB) возвращает значение, которое я ожидал от предыдущего вызова ( cipherB.update(encryptedA) которое является исходным значением: A) и устанавливает вектор в значение зашифрованного A

Можете ли вы заметить что-то неправильное в моем подходе? Известны ли какие-либо проблемы в AES/CBC/PKCS5Padding при использовании поставщика SunJCE по умолчанию?

Обновление: прочитав некоторые комментарии, позвольте мне добавить некоторые дополнительные пояснения.

  1. условия связаны с блоками, используемыми для шифрования полезной нагрузки. первое условие простое, пока (истина), а второе — если (blockCount › 1). есть счетчик блоков, который уменьшается в каждом цикле. Код обновлен

  2. Если A и B опущены при шифровании/дешифровании, данные файла были правильно расшифрованы.

  3. Я попытался расшифровать прямой результат шифрования, например:

    cipherB.update(cipher.update(A))
    

но я все еще получаю тот же пустой массив вместо A

  1. Я не могу полагаться на запуск обновлений дважды, после возврата B путем запуска cipherB.update(encryptedB) что-то идет не так, и на расшифровку данных файла влияет вектор в шифре. Данные, которые я возвращаю, выглядят примерно так

    (12 случайных байтов) Lorem Ipsum и т. д.


person mariosk89    schedule 05.11.2020    source источник
comment
При условии, что A и B, а также encryptedA на самом деле состоят из полного блока (16 байт), это действительно странное поведение, но это не должно быть проблемой. Или, по крайней мере, я не вижу проблемы в таком поведении, поскольку вы всегда можете вызвать следующий update, и это было бы хорошей практикой для защиты кода. Базовый JCE может измениться в будущем, и вы должны писать свой код, не предполагая слишком много.   -  person Artjom B.    schedule 05.11.2020
comment
@ArtjomB. Проблема в том, что condition и other condition его цикл while может зависеть от расшифровываемых данных. В крайнем случае вызовы update() могут вообще ничего не возвращать (например, с шифром AEAD), и только вызов doFinal() возвращает что-либо. Я подозреваю, что OP нужен другой дизайн.   -  person President James K. Polk    schedule 05.11.2020
comment
@ArtjomB. Спасибо, пожалуйста, посмотрите на мой обновленный вопрос. пункт 4   -  person mariosk89    schedule 05.11.2020
comment
При условии, что encryptedA и encryptedB являются полными блоками: первый cipherB.update(encryptedA) ничего не возвращает, потому что во время расшифровки как минимум полный блок остается в буфере. Это связано с заполнением (в контексте CBC): последний блок может содержать дополнение (вплоть до размера всего блока), которое не будет определено до следующего update / doFinal . По той же причине последующий cipherB.update(encryptedB) возвращает только открытый текст, относящийся к encryptedA, encryptedB остается в буфере и т. д.   -  person user 9014097    schedule 06.11.2020
comment
Как я уже сказал, вы не можете полагаться на конкретное поведение вызовов update. Попробуйте изучить CipherInputStream и CipherOutputStream. Поскольку вы пишете в файл, это должен быть альтернативный API, где вы можете прочитать из потока столько байтов, сколько вам нужно. (Я до сих пор не уверен, почему это проблема для вас.)   -  person Artjom B.    schedule 06.11.2020


Ответы (2)


Фрагменты открытого текста и зашифрованного текста не соответствуют друг другу 1 к 1. Вам нужно захватить полный вывод в byte[] и распаковать его самостоятельно.

person Henry    schedule 05.11.2020
comment
Спасибо, пожалуйста, посмотрите мой обновленный ответ. пункт 3 - person mariosk89; 05.11.2020

Режим AES/CBC/PKCS5Padding работает с блоками, поэтому обновление вернет вам только заполненные блоки, а doFinal вернет вам остальные. AES использует 128-битный блок, поэтому метод update возвращает только кратные 16 байтам. Также есть последний блок с отступами. Так что ваше предположение cipherB.update(cipher.update(A)) в данном случае не работает.

Я не очень понимаю, чего вы пытаетесь достичь с помощью условия if(blocks > 1 )

Вы можете использовать следующий код для обработки блоков шифра (упрощенная версия):

  byte[] decrypted = null; 
  byte[] buffer = new byte[BUFFER_SIZE];
  InputStream in = ..;
  for (int bytesRead=in.read(buffer); bytesRead>=0; bytesRead=in.read(buffer)) {
    decrypted = cipher.update(buffer, 0, bytesRead);
    // process the chunk
  }
  decrypted = cipher.doFinal();
  // process the chunk

таким образом, не имеет значения, обрабатываете ли вы один блок или нет.

Существуют также потоковые шифры или режимы, когда метод update напрямую возвращает зашифрованный или расшифрованный фрагмент независимо от размера входных данных, например AES/CTR режим или Salsa20 шифр.

person gusto2    schedule 06.11.2020