Bouncy Castle CTS Mode для Blowfish Двигатель работает не так, как ожидалось

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

Исходная информация:

Я пытаюсь TCipher_Blowfish_ Legacy Encryption из Delphi Encryption Compendium, который использует Blowfish Engine (TCipher_Blowfish_) с режимом работы CTS (cmCTS). Закрытый ключ хешируется RipeMD256 (THash_RipeMD256).

Проблемы:

Входной текстовый массив байтов должен быть одного размера CIPHER_BLOCK. Насколько я могу судить, это не должно.

Из Википедии:

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

Результат не совпадает с предыдущей:

Я использую:

  • Тот же IV
  • Тот же пароль
  • Один и тот же вводный текст

Унаследованное приложение использует ANSI String, новый использует Unicode, поэтому для каждой строки ввода, которую я назвал Encoding.ASCII.GetBytes("plainText"), Encoding.ASCII.GetBytes("privatepassword").

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

Я могу подтвердить, что проблема специфична для Bouncy Clastle (режим работы или отсутствующая конфигурация/шаг), потому что я загрузил вторую библиотеку Blowfish.cs и использовал вход 8 байтов (того же размера, что и блок шифрования) и используя Encrypt_CBC(bytes[]) с тем же IV результатом будет тот же результат, что и унаследованный формат.

Это эскиз кода, который я использую для Blowfish.cs и Bouncy Castle:

Компилятор шифрования Delphi

var 
  IV: Array [0..7] of Byte (1,2,3,4,5,6,7,8);
  Key: String = '12345678';
with TCipher_Blowfish.Create('', nil) do
begin
  try
    InitKey(Key, @IV); //Key is auto hashed using RIPE256 here;
    Result:= CodeString('12345678', paEncode, -1); //Output bytes is later encoded as MIME64 here, the result is the hash.
  finally
    Free;  
  end;
end;

Blofish.cs

var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
Blowfish b = new BlowFish(hashOfPrivateKey);

b.IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8};

var input = Encoding.ASCII.GetBytes("12345678");
var output = b.Encrypt_CBC(input);

Я предполагаю, что CTS и CBC всегда будут иметь одинаковый результат, если входной сигнал составляет 8 бит. Неужели это просто удача или совпадение или в основном истина?

Загадочный замок

IBufferedCipher inCipher = CipherUtilities.GetCipher("BLOWFISH/CTS");
var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
var key = new KeyParameter(hashOfPrivateKey);
var IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8};
var cipherParams = new ParametersWithIV(key, IV); 
inCipher.Init(true, cipherParams);
var input = Encoding.ASCII.GetBytes("12345678");

//try one: direct with DoFinal
var output = inCipher.DoFinal(input);
// output bytes different from expected

inCipher.Reset();

//try two: ProcessBytes then DoFinal
var outBytes = new byte[input.Length];
var res = inCipher.ProcessBytes(input, 0, input.Length, outBytes, 0);
var r = inCipher.DoFinal(outBytes, res);
// outBytes bytes different from expected

Как я уже сказал, я сравниваю CBC с CTS, исходя из предположения, что при вводе 8 байтов вывод будет таким же. Я не могу перенаправить реализацию с Bouncy Castle, даже если с тем же входом результат не совпадает.

Я не знаю:

  • Если режим CTS, используемый в Delphi Encryption Compendium, использует CBC вместе с CTS. Я не мог найти документально нигде.
  • Разница между вызовом только DoFinal() и ProcessBytes(), а затем DoFinal() в Bouncy Castle, я полагаю, что это необходимо, когда входной блок больше размера блока двигателя, в этом случае они одного размера.
  • Если Delphi Encryption Compendium является правильным/неправильным или если Bouncy Castle является правильным/неправильным. У меня недостаточно знаний в криптографии, чтобы понять реализацию, иначе я не стал бы задавать вопрос здесь (мне нужно руководство).

Ответы

Ответ 1

Я предполагаю, что CTS и CBC всегда будут иметь одинаковый результат, если входной сигнал составляет 8 бит. Неужели это просто удача или совпадение или в основном истина?

Нет, это ложное утверждение.

Вот цитата из Википедии:

Шифрование в Ciphertext для режима CBC необязательно требует, чтобы открытый текст был длиннее одного блока. В случае, когда открытым текстом является один блок длинным или меньшим, вектор инициализации (IV) может выступать в качестве предыдущего блока зашифрованного текста.

Таким образом, даже для вашего случая с 8-байтовым входом, CTS-алгоритм вступает в игру и влияет на выход. В основном ваше выражение о равенстве CTS и CBS может быть отменено:

CTS и CBC всегда будут иметь одинаковый результат до двух последних блоков.

Вы можете проверить его с помощью следующего образца:

static byte[] EncryptData(byte[] input, string algorithm)
{
    IBufferedCipher inCipher = CipherUtilities.GetCipher(algorithm);
    var hashOfPrivateKey = HashValue(Encoding.ASCII.GetBytes("12345678"));
    var key = new KeyParameter(hashOfPrivateKey);
    var IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8 };
    var cipherParams = new ParametersWithIV(key, IV);
    inCipher.Init(true, cipherParams);

    return inCipher.DoFinal(input);
}

static void Main(string[] args)
{
    var data = Encoding.ASCII.GetBytes("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
    var ctsResult = EncryptData(data, "BLOWFISH/CTS");
    var cbcResult = EncryptData(data, "BLOWFISH/CBC");
    var equalPartLength = data.Length - 2 * 8;
    var equal = ctsResult.Take(equalPartLength).SequenceEqual(cbcResult.Take(equalPartLength));
}

Так что это в основном ответ на ваш главный вопрос. Вы не должны ожидать того же выхода для CTS и CBC на 8-байтовом входе.

Вот ответы (надеюсь) на ваши другие вопросы:

Если режим CTS, используемый в Delphi Encryption Compendium, использует CBC вместе с CTS. Я не мог найти документально нигде.

Я не нашел никакой документации для режима CTS в Delphi Encryption Compendium, но есть такие комментарии в исходном коде:

cmCTSx = двойной CBC, с заполнением CFS8 усеченного финального блока

Режимы cmCTSx, cmCFSx, cmCFS8 - это разработанные мной фирменные режимы. Эти режимы работают, например, cmCBCx, cmCFBx, cmCFB8, но с двойным XOR'ing входного потока в регистр обратной связи.

Похоже, что режим CTS реализован по-своему в Delphi Encryption Compendium, который не будет совместим со стандартной реализацией Bouncy Castle.

Разница между вызовом только DoFinal() и ProcessBytes(), а затем DoFinal() в Bouncy Castle, я полагаю, что это необходимо, когда входной блок больше размера блока двигателя, в этом случае они одного размера.

Для шифрования данных требуется пара вызовов ProcessBytes()/DoFinal(). Это может потребоваться, например, при передаче огромных данных. Однако, если у вас есть подпрограмма, которая принимает весь массив байтов для шифрования, вы можете просто вызвать следующую удобную перегрузку DoFinal() один раз:

var encryptedData = inCipher.DoFinal(plainText);

Эта перегрузка DoFinal() будет вычислять размер выходного буфера и делать необходимые вызовы ProcessBytes() и DoFinal() под капотом.

Если Delphi Encryption Compendium является правильным/неправильным или если Bouncy Castle является правильным/неправильным. У меня недостаточно знаний в криптографии, чтобы понять реализацию, иначе я не стал бы задавать вопрос здесь (мне нужно руководство).

Подведем итог:

  1. Вы не должны ожидать того же выхода для CTS и CBC для 8-байтового ввода.
  2. Похоже, что Delphi Encryption Compendium использует собственный алгоритм для CTS. Поскольку Bouncy Castle реализован в соответствии со стандартами, эти библиотеки будут давать разные результаты. Если ваше новое приложение не требуется для поддержки зашифрованных данных, созданных с помощью устаревшего приложения Delphi, вы можете просто использовать Bouncy Castle и быть в порядке. В другом случае вы должны использовать тот же пользовательский алгоритм CTS, который использует Delphi Encryption Compendium, к которому, к сожалению, потребуется порт его источников для С#.

ОБНОВИТЬ

(Более подробная информация о реализации компендиума Delphi Encryption в версии 3.0)

Вот код кодирования CTS из DEC версии 3.0:

S := @Source;
D := @Dest;

// ...

begin
    while DataSize >= FBufSize do
    begin
        XORBuffers(S, FFeedback, FBufSize, D);
        Encode(D);
        XORBuffers(D, FFeedback, FBufSize, FFeedback);
        Inc(S, FBufSize);
        Inc(D, FBufSize);
        Dec(DataSize, FBufSize);
    end;
    if DataSize > 0 then
    begin
        Move(FFeedback^, FBuffer^, FBufSize);
        Encode(FBuffer);
        XORBuffers(S, FBuffer, DataSize, D);
        XORBuffers(FBuffer, FFeedback, FBufSize, FFeedback);
    end;
end;

Здесь мы видим двойной XOR'ing, упомянутый в документации DEC. В основном этот код реализует следующий алгоритм:

C[i] = Encrypt( P[i] xor F[i-1] )
F[i] = F[i-1] xor C[i]
F[0] = IV

тогда как стандартный алгоритм будет:

C[i] = Encrypt( P[i] xor C[i-1] )
C[0] = IV

Шаг F[i] = F[i-1] xor C[i] является авторским изобретением DEC и делает результаты шифрования различными. Обработка последних двух блоков, которая имеет решающее значение для режима CTS, также реализуется не стандартом.

Вот комментарий от DEC v 3.0 ReadMe.txt, который описывает, почему автор добавил такую модификацию:

cmCTS Mode, XOR Данные до и теперь после шифрования. Это улучшает Securityeffect при использовании InitVector, выход защищен при использовании плохого InitVector, около 1% потери скорости

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