LockBox 3 Шифрование не соответствует онлайн-инструменту, подозрительное заполнение или проблема с ключом

У меня есть клиент, предоставляющий API, который требует, чтобы отправляемые ему данные были зашифрованы с помощью AES, 128-битного ключа, режима ECB и PKCS5Padding. Я пытаюсь использовать LockBox 3 в Delphi 10.3 Rio и не получаю ту же зашифрованную строку, что и инструмент онлайн-тестирования, на который они указали для проверки. Это близко, но не совсем там.

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

Есть пара вещей, которые я хотел бы подтвердить:

  • Разница между паролем и ключом. Я читал, что LB3 использует пароль для генерации ключа, но у меня есть конкретные инструкции от клиента о том, как должен быть сгенерирован ключ, поэтому я создал свой собственный ключ в кодировке Base64 и вызываю InitFromStream для его инициализации. Я считаю, что это заменяет установку пароля, верно? Или, может быть, пароли используются только асимметричными шифраторами (а не симметричными, как AES)?
  • PKCS5Padding: меня беспокоит то, что я прочитал на справочном сайте LB3 указанное заполнение выполняется разумно в зависимости от выбора шифра, режима цепочки и т. д. Значит ли это, что нет способа заставить его использовать определенный метод заполнения? Я преобразовал данные в массив байтов и реализовал собственный PKCS5Padding, но я думаю, что LB3 все еще может быть заполнен за пределами этого. (Я пытался просмотреть код и не нашел никаких доказательств того, что это то, что он делает.)

Должен ли я использовать для этого другую библиотеку шифрования в Delphi? Я проверил DelphiEncryptionCompendium и DcPCryptV2, но я обнаружил, что LB3 имеет наибольшую поддержку, и я это почувствовал. с ним было проще всего работать, особенно в моей Unicode-версии Delphi. Кроме того, я довольно часто использовал LockBox 2 в прошлые годы, поэтому я подумал, что он будет более знакомым (как оказалось, это не так).

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

program LB3ConsoleTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Classes, System.NetEncoding,
  uTPLb_Codec, uTPLb_CryptographicLibrary,
  uTPLb_StreamUtils, uTPLb_Constants;

var
  Codec: TCodec;
  CryptographicLibrary: TCryptographicLibrary;

function PKCS5PadStringToBytes(RawData: string; const PadSize: Integer): TBytes;
{ implement our own block padding }
var
  DataLen: Integer;
  PKCS5PaddingCount: ShortInt;
begin
  Result := TEncoding.UTF8.GetBytes(RawData);
  DataLen := Length(RawData);

  PKCS5PaddingCount := PadSize - DataLen mod PadSize;
  if PKCS5PaddingCount = 0 then
    PKCS5PaddingCount := PadSize;
  Inc(DataLen, PKCS5PaddingCount);

  SetLength(Result, DataLen);
  FillChar(Result[DataLen - PKCS5PaddingCount], PKCS5PaddingCount, PKCS5PaddingCount);
end;

procedure InitializeAESKey(const AESKey: string);
{ convert the string to a byte array,
  use that to initialize a ByteStream,
  and call LB3's InitFromStream }
var
  AESKeyBytes: TBytes;
  AESKeyStream: TBytesStream;
begin
  AESKeyBytes := TEncoding.UTF8.GetBytes(AESKey);
  AESKeyStream := TBytesStream.Create(AESKeyBytes);
  Codec.InitFromStream(AESKeyStream);
end;

const
  RawData = '{"invoice_id":"456456000018047","clerk_id":"0023000130234234","trans_amount":1150034534,"cust_code":"19455605000987890641","trans_type":"TYPE1"}';
  AESKeyStr = 'CEAA31AD1EE4BDC8';
var
  DataBytes: TBytes;
  DataStream: TBytesStream;
  ResultStream: TBytesStream;
  ResultBytes: TBytes;
  Base64Encoder: TBase64Encoding;
begin
  // create the LockBox3 objects
  Codec := TCodec.Create(nil);
  CryptographicLibrary := TCryptographicLibrary.Create(nil);
  try
    // setup LB3 for AES, 128-bit key, ECB
    Codec.CryptoLibrary := CryptographicLibrary;
    Codec.StreamCipherId := uTPLb_Constants.BlockCipher_ProgId;
    Codec.BlockCipherId  := Format(uTPLb_Constants.AES_ProgId, [128]);
    Codec.ChainModeId    := uTPLb_Constants.ECB_ProgId;

    // prep the data, the key, and the resulting stream
    DataBytes := PKCS5PadStringToBytes(RawData, 8);
    DataStream := TBytesStream.Create(DataBytes);
    InitializeAESKey(AESKeyStr);
    ResultStream := TBytesStream.Create;

    // ENCRYPT!
    Codec.EncryptStream(DataStream, ResultStream);

    // take the result stream, convert it to a byte array
    ResultStream.Seek(0, soFromBeginning);
    ResultBytes := Stream_to_Bytes(ResultStream);

    // convert the byte array to a Base64-encoded string and display
    Base64Encoder := TBase64Encoding.Create(0);
    Writeln(Base64Encoder.EncodeBytesToString(ResultBytes));

    Readln;
  finally
    Codec.Free;
    CryptographicLibrary.Free;
  end;
end.

Эта программа генерирует зашифрованную строку длиной 216 символов, в которой только последние 25 символов отличаются от онлайн-инструмент производит.

Почему?


person David Cornelius    schedule 13.03.2019    source источник
comment
Это выглядит немного, эээ, подозрительно: FillChar(Result[DataLen - PKCS5PaddingCount], PKCS5PaddingCount, PKCS5PaddingCount);. Вы уверены, что это правильно? Каково значение PKCS5PaddingCount?   -  person Rudy Velthuis    schedule 13.03.2019
comment
FWIW, при переходе с Delphi 2007 на (я думаю) XE5 в то время у меня также были проблемы с LockBox, и то, что я зашифровал раньше, не могло быть расшифровано в более новой версии. В итоге я скомпилировал отдельную dll с версией логики дешифрования 2007 года, чтобы я мог расшифровать ее в сборке XE5 моего приложения.   -  person GolezTrol    schedule 13.03.2019
comment
Заполнение IIRC PKCS5 составляет около 8-байтовых блоков, тогда как AES использует 16-байтовые блоки. Итак, я думаю, вы имели в виду заполнение PKCS7, а не заполнение PKCS5.   -  person Arnaud Bouchez    schedule 13.03.2019
comment
Также обратите внимание, что режим цепочки ECB довольно небезопасен, и его следует избегать любой ценой. Стоит перейти на новый режим (и 256-битный?).   -  person Arnaud Bouchez    schedule 13.03.2019
comment
Для кросс-платформенной (Delphi + FPC), хорошо поддерживаемой альтернативы с открытым исходным кодом с очень быстрым процессом (наверняка самым быстрым, поскольку он единственный, использующий AES-NI), рассмотрите наш github.com/synops/mORMot/blob/master/SynCrypto.pas   -  person Arnaud Bouchez    schedule 13.03.2019
comment
@RudyVelthuis Вот как работает PKCS5/PKCS7: добавьте количество байтов заполнения, чтобы оно соответствовало размеру всего блока шифрования.   -  person Arnaud Bouchez    schedule 13.03.2019
comment
@Arnaud: Хорошо, спасибо. Так что я понимаю, что это правильно и даже необходимо.   -  person Rudy Velthuis    schedule 13.03.2019
comment
@RudyVelthuis, да, я уверен, что FillChar прав. Я прошел через это в коде, и он добавляет и заполняет правильные байты. Чтение о PKCS5Padding удивило меня тем, что байты заполняются числом заполненных байтов (следите за этим?).   -  person David Cornelius    schedule 13.03.2019
comment
@GolezTrol Интересный подход - вызов DLL, созданной в более ранней версии Delphi. Я могу попробовать это. Я также рассматривал возможность использования внешней сторонней DLL, возможно, даже коммерческой.   -  person David Cornelius    schedule 13.03.2019
comment
@David: да, Арно уже сказал мне.   -  person Rudy Velthuis    schedule 13.03.2019


Ответы (1)


AES использует 16-байтовые блоки, а не 8-байтовые.

Таким образом, вам нужен PKCS7 с 16-байтовым заполнением, а не PKCS5 с фиксированным размером 8 байт.

Пожалуйста, попробуй

DataBytes := PKCS5PadStringToBytes(RawData, 16);

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

person Arnaud Bouchez    schedule 13.03.2019
comment
Насколько я понимаю, AES может использовать как 8-байтовые, так и 16-байтовые блоки. Стандартом (и, возможно, по умолчанию во многих библиотеках) являются 16-байтовые блоки, но API, который я вызываю, специально использует PKCS5Padding. Он также указывает режим ECB, еще одно требование (как упоминалось в исходном сообщении). Слабость этого режима, я полагаю, объясняется динамической генерацией ключа AES из данных, которые включают одноразовый код авторизации клиента. - person David Cornelius; 13.03.2019
comment
AES работает с блоками по 16 байт - я не знаю, о чем вы говорите. API, который вы вызываете, наверняка вводит в заблуждение. PKCS5 предназначен только для заполнения 8 байтов: разрешение параметра для размера заполнения - это то, для чего предназначен PKCS7, а не PKCS5. И ECB не следует использовать даже с эфемерным ключом. Если пользователь может угадать только некоторые незашифрованные байты (например, первые 16 байтов), это позволит расшифровать весь контент. - person Arnaud Bouchez; 14.03.2019
comment
Честно говоря, мне все равно, что такое дополнение — я просто хочу, чтобы моя зашифрованная строка могла быть расшифрована сторонним API на другом конце. И для этого я должен иметь возможность сопоставить мой вывод с выводом онлайн-инструмента, devglan.com/online-tools/aes-encryption-decryption. Причина, по которой я продолжаю упоминать PKCS5Padding, заключается в том, что этого требует документация по API, а также ECB. Неважно, насколько он небезопасен, устарел или что-то в этом роде, это то, что я должен сделать, чтобы завершить этот проект. - person David Cornelius; 14.03.2019
comment
Итак, переход на RawData, 16 решил вашу проблему? - person Arnaud Bouchez; 14.03.2019
comment
Нет, я уже пробовал это, прежде чем опубликовать вопрос. Я нашел одну вещь странной/интересной: использование 8-байтового заполнения вернуло правильную длину строки, 16-байтовое заполнение вернуло строку слишком длинной, но если я обрезал ее по длине, полученной в результате 8-байтовой дополненной строки, зашифрованный результат совпал! Однако мне нужно было добавить еще один параметр в RawData, и теперь я не могу найти похожий шаблон. В противном случае этот хак сработал бы. - person David Cornelius; 14.03.2019