PHP в Delphi и обратно Шифрование-дешифрование с использованием Rijndael

У меня проблемы с расшифровкой строк, отправленных из PHP в Delphi с использованием шифра rijndael. Я использую mcrypt на стороне PHP и DCP_rijndael на стороне Delphi.

На данный момент у меня есть код ниже.

PHP:

function encRJ($key, $iv, $data)
{
    $r = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
    $r = base64_encode($r);
    return $r;
}

И в Делфи:

function decRJ(Data: string; Key: string; IV: string): string;
var ciph: TDCP_rijndael;
begin
  Data := Base64DecodeStr(Data);

  ciph:= TDCP_rijndael.Create(Self);
  ciph.Init(Key[1], 256, @IV[1]);
  ciph.DecryptCBC(Data[1], Data[1], Length(Data));
  ciph.Free;

  Result := Data;
end;

Я пытался использовать несколько модулей в Интернете, реализующих шифр, и обнаружил, что большинство людей говорят о компонентах DCP. Тем не менее, мне не удалось правильно его расшифровать. Я пытался использовать массивы байтов для параметров, AnsiStrings, WideStrings и т. д., но, к сожалению, не повезло.

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


person Josh    schedule 13.08.2011    source источник
comment
Я бы предложил реализовать функции шифрования и дешифрования как в PHP, так и в Delphi. Затем вы можете проверить, что они оба генерируют одинаковые промежуточные значения и что они могут расшифровывать промежуточные значения друг друга. Это должно сузить круг вопросов, есть ли систематическая проблема или ошибка в конкретной функции.   -  person Gus    schedule 13.08.2011
comment
На веб-сайте nist.gov и во многих других местах есть множество тестовых векторов, которые вы можете использовать, чтобы увидеть, как работают эти реализации. Обработка функции Delphi для Data мне кажется неправильной. Результатом декодирования base64 должны быть произвольные байты, а не строка в кодировке UTF8.   -  person President James K. Polk    schedule 13.08.2011
comment
@GregS, я пробовал декодировать обычный незашифрованный текст с помощью base64, и все отлично. Я не думаю, что проблема находится там. Даже если это так, я пробовал 2 другие реализации Base64, которые дали тот же конечный результат.   -  person Josh    schedule 14.08.2011
comment
Я думаю, что @Greg беспокоит не декодирование Base64, а вызов UTF8ToUnicodeString. Это обрабатывает результат декодирования Base64 как строку UTG8 и пытается преобразовать ее в UTF16-LE. Если в декодированном Base64 есть какие-либо байты, которые будут обрабатываться как многобайтовые символы в UTF-8, вы будете изменять длину (превращая 1+ байт в один символ) и тем самым изменяя данные, которые вы передаете Rijndael. .   -  person Marjan Venema    schedule 14.08.2011
comment
Я просто изменил его на обычный Base64Decode. Тем не менее, не повезло. В настоящее время я предполагаю, что это проблема заполнения, как сказал @rossum ниже.   -  person Josh    schedule 14.08.2011


Ответы (2)


Кажется, я потратил слишком много времени на это, но...

Ваша проблема в размере блока. TDCP_rijndael эквивалентен MCRYPT_RIJNDAEL_128 (не _256). Однако значение «256» в вызове ciph.Init(...) по-прежнему верно. В остальном он выглядит вполне нормально. То есть, предполагая, что вы используете ansistrings для key/iv или вы используете не-юникод Delphi.
Для версий Unicode Delphi я бы склонялся к использованию TBytes и key[0] / iv[0].

Прокладка все еще может быть проблемой. Если да, то вот что я испортил на основе справочных страниц PHP и некоторых проб и ошибок.

PHP:

function Encrypt($src, $key, $iv)
{
  $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
  //echo "Block size: " . $block . "\r\n";
  $pad = $block - (strlen($src) % $block);
  $src .= str_repeat(chr($pad), $pad);  

  $enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $src, MCRYPT_MODE_CBC, $iv);
  $r = base64_encode($enc);
  return $r;
}

function Decrypt($src, $key, $iv)
{
  $enc = base64_decode($src);
  $dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv);

  $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
  $pad = ord($dec[($len = strlen($dec)) - 1]);
  return substr($dec, 0, strlen($dec) - $pad);
}

Дельфи:

function DecryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
  key, iv, src, dest: TBytes;
  cipher: TDCP_rijndael;
  slen, pad: integer;
begin
  //key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
  //iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
  key := TEncoding.ASCII.GetBytes(AKey);
  iv := TEncoding.ASCII.GetBytes(AIv);

  src := Base64DecodeBytes(TEncoding.UTF8.GetBytes(Data));

  cipher := TDCP_rijndael.Create(nil);
  try
    cipher.CipherMode := cmCBC;
    slen := Length(src);
    SetLength(dest, slen);
    cipher.Init(key[0], 256, @iv[0]); // DCP uses key size in BITS not BYTES
    cipher.Decrypt(src[0], dest[0], slen);
    // Remove the padding. Get the numerical value of the last byte and remove
    // that number of bytes
    pad := dest[slen - 1];
    SetLength(dest, slen - pad);

    // Base64 encode it
    result := TEncoding.Default.GetString(dest);
  finally
    cipher.Free;
  end;
end;

function EncryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
  cipher: TDCP_rijndael;
  key, iv, src, dest, b64: TBytes;
  index, slen, bsize, pad: integer;
begin
  //key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
  //iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
  key := TEncoding.ASCII.GetBytes(AKey);
  iv := TEncoding.ASCII.GetBytes(AIv);

  src := TEncoding.UTF8.GetBytes(Data);

  cipher := TDCP_rijndael.Create(nil);
  try
    cipher.CipherMode := cmCBC;
    // Add padding.
    // Resize the Value array to make it a multiple of the block length.
    // If it's already an exact multiple then add a full block of padding.
    slen := Length(src);
    bsize := (cipher.BlockSize div 8);
    pad := bsize - (slen mod bsize);
    Inc(slen, pad);
    SetLength(src, slen);
    for index := pad downto 1 do
    begin
      src[slen - index] := pad;
    end;

    SetLength(dest, slen);
    cipher.Init(key[0], 256, @iv[0]); // DCP uses key size in BITS not BYTES
    cipher.Encrypt(src[0], dest[0], slen);

    b64 := Base64EncodeBytes(dest);
    result := TEncoding.Default.GetString(b64);
  finally
    cipher.Free;
  end;
end;

Функции PHP и Delphi теперь дают мне один и тот же ответ.

РЕДАКТИРОВАТЬ

Base64DecodeBytes — это фрагмент кода, который я добавил в модуль DCP Base64:

function Base64DecodeBytes(Input: TBytes): TBytes;
var
  ilen, rlen: integer;
begin
  ilen := Length(Input);
  SetLength(result, (ilen div 4) * 3);
  rlen := Base64Decode(@Input[0], @result[0], ilen);
  // Adjust the length of the output buffer according to the number of valid
  // b64 characters
  SetLength(result, rlen);
end; 

EDIT 2018 (Воскрешение мертвых...):

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: ему много лет, он не тестировался в последнее время и не использовался с Delphi 2010. Вероятно, сейчас есть много лучших альтернатив. Используйте на свой риск.

function Base64EncodeBytes(Input: TBytes): TBytes;
var
  ilen: integer;
begin
  ilen := Length(Input);
  SetLength(result, ((ilen + 2) div 3) * 4);
  Base64Encode(@Input[0], @result[0], ilen);
end;
person shunty    schedule 16.08.2011
comment
Кажется, я не могу найти ссылку на вашу функцию Base64DecodeBytes в Delphi. Он объявлен в одном из модулей Delphi по умолчанию или это функция, которую вы написали сами? - person Josh; 18.08.2011
comment
Упс. Много лет назад я внес свои собственные изменения в материал DCP, чтобы сделать его совместимым с юникодом, а также добавил материал Base64XXXBytes. Отредактировал ответ, чтобы добавить (непроверенную) функцию. - person shunty; 18.08.2011
comment
Base64DecodeBytes (вход: ТБ): ТБ; глючит!!! он работает только с коротким текстом для шифрования (иногда возвращает ошибку проверки диапазона crypt или ошибку в декодировании), пожалуйста, используйте функции Soap.EncdDecd, которые в порядке. - person Evilripper; 29.04.2016
comment
@Evilripper: принято к сведению. И я поверю тебе на слово. Но, честно говоря, посту уже 5 лет, и я без проблем пролистал довольно много МБ, так что пока остановлюсь на нем. Это работает в нашей ситуации, поэтому я не собираюсь с этим связываться. В данный момент я не использую Delphi, но учту вашу рекомендацию, если когда-нибудь вернусь к ней. На самом деле - я думаю, что в последующем ответе на SO я вместо этого использовал EncdDecd :-) - person shunty; 29.04.2016
comment
Можете ли вы опубликовать свою реализацию Base64EncodeBytes. Это отсутствует - person RaelB; 29.04.2018
comment
Добавлена ​​функция кодирования @raelb. Но, пожалуйста, обратите внимание на отказ от ответственности (и предыдущие комментарии от EvilRipper)! - person shunty; 30.04.2018

Ни ваш PHP, ни ваши методы Delphi не указывают никаких дополнений. Если отступы по умолчанию отличаются, у вас возникнут проблемы. Явно укажите PKCS7 (или PKCS5) для обоих.

Комментарий GregS о результате декодирования Base64 верен. Вы предоставляете зашифрованный зашифрованный текст вашему методу decRJ(). Это будут случайные байты. Попытка преобразовать его в UTF-8 исказит его настолько, что его нельзя будет расшифровать. Входящий зашифрованный текст должен быть преобразован из Base64 напрямую в массив байтов. Шифрованный текст не является строкой символов, поэтому его необходимо преобразовать в Base64 для передачи в виде текста. Это снова будет текст только после расшифровки обратно в открытый текст.

person rossum    schedule 13.08.2011
comment
Я пробовал несколько способов преобразования Base64 str в различные типы данных вместе с массивом байтов, но все равно не повезло. Теперь я проверил демонстрацию PHP здесь и обнаружил, что даже его способ не работает. Странный. Я не знаю, что делать атм. - person Josh; 14.08.2011
comment
Напишите метод encrypt() в Delphi и заставьте его работать с вашим методом Delphi decRJ(). - person rossum; 14.08.2011