Шифрование библиотеки Delphi DEC (Rijndael)

Я пытаюсь использовать библиотеку DEC 3.0 (Delphi Encryption Compedium Part I) для шифрования данных в Delphi 7 и отправки их в сценарий PHP через POST, где я расшифровываю их с помощью mcrypt (RIJNDAEL_256, режим ECB).

Часть Delphi:

uses Windows, DECUtil, Cipher, Cipher1;

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create(KeyStr, nil);
  RCipher.Mode:= cmECB;
  Result:= RCipher.CodeString(MsgData, paEncode, fmtMIME64);
  RCipher.Free;
end;

Часть PHP:

function decryptMsgContent($msgContent, $sKey) {
    return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sKey, base64_decode($msgContent), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
}

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

Конечно, Delphi Key и PHP $Key - это одна и та же строка из 24 символов.

Теперь я знаю, что DEC 3.0 устарел и устарел, и я не являюсь экспертом в области шифрования и не могу сказать, является ли реализация на самом деле Rijndael 256. Может быть, кто-нибудь скажет мне, чем эта реализация отличается от PHP mcrypt w / RIJNDAEL_256. Может быть, другой размер ключа или размер блока, но по коду этого не скажешь. Вот отрывок из Cipher1.pas:

const
{ don’t change this }
  Rijndael_Blocks =  4;
  Rijndael_Rounds = 14;

class procedure TCipher_Rijndael.GetContext(var ABufSize, AKeySize, AUserSize: Integer);
begin
  ABufSize := Rijndael_Blocks * 4;
  AKeySize := 32;
  AUserSize := (Rijndael_Rounds + 1) * Rijndael_Blocks * SizeOf(Integer) * 2;
end;

Дополнительный вопрос:

Я знаю, что режим ECB не рекомендуется, и я воспользуюсь CBC, как только у меня заработает ECB. Вопрос в том, нужно ли мне передавать сгенерированный IV в Delphi и в PHP-скрипт? Или достаточно знания ключа, как для ЕЦБ?


person binar    schedule 10.02.2012    source источник
comment
Это может быть очень глупый вопрос. Но можете ли вы расшифровать зашифрованные данные с помощью delphi? О, и помогает ли ответ на этот вопрос: stackoverflow.com/q/8313992/41338   -  person RobS    schedule 10.02.2012
comment
Вы вызываете mcrypt_create_iv (). Какой IV вы использовали в Delphi?   -  person    schedule 10.02.2012
comment
@ldsandon: программа чтения сказок использует режим ECB. Нет IV.   -  person Henrick Hellström    schedule 10.02.2012
comment
Надеюсь, PHP это знает - не знаю, что происходит с вызовом mcrypt_create_iv (mcrypt_get_iv_size (MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)); может, его просто игнорируют (надеюсь), может, что-то плохое срабатывает. Если он вернет False, он может передать неверный параметр в mcrypt_decrypt.   -  person    schedule 10.02.2012
comment
@Isandon Подумал об этом, протестировал шифрование / дешифрование в PHP, генерируя IV как при шифровании, так и при дешифровании, и результат в порядке. Похоже, что в ECB mcrypt игнорирует переданный IV.   -  person binar    schedule 10.02.2012
comment
@RobS Если я не могу решить эту проблему с помощью текущей реализации (DEC), я, вероятно, перейду к другой реализации (DCPCrypt, Lockbox выглядит хорошо или даже последнее обновление DEC), но все же это меня озадачивает. Я использовал эту библиотеку DEC так долго и никогда не испытывал проблем, если использовал ее только в приложении Delphi.   -  person binar    schedule 10.02.2012
comment
@RobS и нет, на самом деле это не глупый вопрос. Это базовая отладка, amd до сих пор не думала о том, чтобы протестировать ее на этой настройке, потому что раньше я использовал ее в других приложениях. Алгоритм в Delphi работает (зашифровать потом расшифровать). Хенрик Хельстрём указал на проблему.   -  person binar    schedule 10.02.2012


Ответы (3)


Вы вызываете TCipher.Create (const Password: String; AProtection: TProtection); конструктор, который будет вычислять хэш пароля перед передачей его методу Init, который выполняет стандартное расписание ключей реализованного алгоритма. Чтобы переопределить это происхождение ключа, используйте:

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('', nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmECB;
  Result:= RCipher.CodeString(MsgData, paEncode, fmtMIME64);
  RCipher.Free;

конец;

person Henrick Hellström    schedule 10.02.2012
comment
Спасибо, это поставило меня на верный путь. На самом деле я использую свое собственное преобразование пароля (опущено в опубликованном коде), которое принимает только первые 24 символа sha1 из PasswordSecret + Timestamp (который входит в данные POST). Я был вынужден сделать это, когда я получил ошибку в PHP, что mcrypt принял ключ длиной не более 24 символов. - person binar; 10.02.2012
comment
Другая часть проблемы заключалась в том, что реализация DEC на самом деле имеет 128 бит Rijndael. Обнаружено, что, изменив функцию дешифрования PHP на использование RIJNDAEL_128. Теперь на выходе функции дешифрования PHP есть исходные данные + немного тарабарщины в конце. Из того, что я читал ранее в других сообщениях о stackoverflow, где-то здесь есть проблема с заполнением, которую мне нужно выяснить. - person binar; 10.02.2012
comment
Кроме того, глядя на источник DEC, можно ли внести некоторые изменения, чтобы преобразовать его в вариант с размером блока 128 бит и размером ключа 256 (AES 256), чтобы он сочетался с mcrypt w / RIJNDAEL_256? - person binar; 10.02.2012
comment
Правильно, DEC не будет дополнять ваш простой текст в режиме ECB, что означает, что простой текст, который вы передаете вашей функции, должен иметь длину, кратную размеру блока шифра (16 для Rijndael / AES). PHP по умолчанию использует нулевое заполнение, поэтому вы должны дополнить свой MsgData StringOfChar (# 0,16- (Length (MsgData) mod 16)), предполагая, что вы используете версию Delphi, отличную от Unicode. - person Henrick Hellström; 10.02.2012
comment
DEC реализует Rijndael с размером блока 128 бит и размером ключа 128, 192 и 256 бит. Если ключ, который вы передаете методу Init, имеет длину ‹= 16, будет использоваться AES-128, 16‹ length ‹= 24 будет использоваться AES-192, а в противном случае - AES-256. Однако, чтобы гарантировать совместимость с mcrypt (или любой другой реализацией), вы должны убедиться, что длина ключа составляет ровно 32, если вы хотите AES-256. - person Henrick Hellström; 10.02.2012
comment
Согласно имеющейся документации MCRYPT_RIJNDAEL_128 соответствует AES (Rijndael с размером блока 128 бит), независимо от размера ключа. MCRYPT_RIJNDAEL_256 не является AES и не поддерживается DEC. - person Henrick Hellström; 10.02.2012
comment
Хорошо, итак 1). Я ошибочно принял MCRYPT_RIJNDAEL_128 / 256 за размер ключа, тогда как на самом деле 128 означает размер блока. 2). Сделал то, что вы сказали, с набивкой, и это работает как шарм. 3). Вот почему многие примеры mcrypt используют trim () для результирующего (декодированного) текста из-за заполнения. Хм. Учиться так здорово. Большое тебе спасибо! - person binar; 10.02.2012

Итак, подводя итог, с моим кодом было 3 проблемы:

  1. Из-за моего плохого понимания mcrypt и шифров в целом MCRYPT_RIJNDAEL_256 относится к 128-битному блоку и не относится к размеру ключа. Мой правильный выбор должен был быть MCRYPT_RIJNDAEL_128, который является стандартом AES и также поддерживается DEC 3.0.

  2. У DEC есть собственное производное ключа по умолчанию, поэтому мне нужно было обойти его, чтобы мне не пришлось реализовывать его и в PHP. На самом деле я использую свой собственный алгоритм получения ключа, который легко воспроизвести в PHP (первые 32 символа sha1 (ключ)).

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

Предоставляем рабочий код ниже:

Delphi:

uses Windows, DECUtil, Cipher, Cipher1, CryptoAPI;

function EncryptMsgData(MsgData, Key: string): string;
var RCipher: TCipher_Rijndael;
    KeyStr: string;
begin
  Result:= '';
  try
    // key derivation; just making sure to feed the cipher a 24 chars key
    HashStr(HASH_SHA1, Key, KeyStr);
    KeyStr:= Copy(KeyStr, 1, 24);
    RCipher:= TCipher_Rijndael.Create('', nil);
    RCipher.Init(Pointer(KeyStr)^, Length(KeyStr), nil);
    RCipher.Mode:= cmECB;
    Result:= RCipher.CodeString(MsgData + StringOfChar(#0,16-(Length(MsgData) mod 16)), paEncode, fmtMIME64);
    RCipher.Free;
  except
  end;
end;

PHP:

function decryptMsgContent($msgContent, $sKey) {
    $sKey = substr(sha1(sKey), 0, 24);
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $sKey, base64_decode($msgContent), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND)));
}
person binar    schedule 10.02.2012
comment
Гм. SHA-1 выводит только 20 байтов. Самым простым решением, вероятно, является уменьшение размера ключа до 128 бит / 16 байт. Другой альтернативой является использование SHA-256 и переключение с DEC 3.0 на другую библиотеку, поддерживающую SHA-256, такую ​​как ‹бесстыдный плагин› OpenStrSecII или StreamSec Tools ‹/ бесстыдный плагин› или DCP Crypt. Третья альтернатива - использовать SHA-1 с растяжением клавиш, но это может быть легко реализовать в PHP, а в DEC 3.0 не реализована ни одна из функций PKCS # 5 v2.0. - person Henrick Hellström; 11.02.2012
comment
Правильно ли я говорю, что в приведенном выше коде на самом деле используется AES-128, а не 192, хотя длина ключа составляет 24 символа? Следует ли уменьшить длину ключа до 16? Поскольку пока я буду придерживаться AES-128, это небольшой проект. Кроме того, я переключился на CBC, позволил DEC сгенерировать IV внутренне, и я просто передаю кодировку IV base64 вместе с данными. - person binar; 12.02.2012
comment
Он использует AES-192, но в ключе содержится не более 160 бит энтропии (ограничено выходной длиной SHA-1), а последние 4 байта ключа (с # 21 по # 24) не определены (хотя, вероятно, 0 раз уж у вас он заработал). - person Henrick Hellström; 12.02.2012

Я нашел 256-битный ключ - это 32 символа или 32 байта. Не 24. Это может быть проблемой.

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

Я объединил все идеи (ответы и т. Д.) В одну идею с исправлением.

Кроме того, вы используете кодовую строку (- это должно быть Encodestring (

Я вставил рабочий источник шифрования и дешифрования ниже:


function EncryptMsgData(MsgData, Key: AnsiString): AnsiString;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('', nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmCBC;
  Result:= RCipher.EncodeString(MsgData);
  RCipher.Free;
end;

function DecryptMsgData(MsgData, Key: AnsiString): AnsiString;
var RCipher: TCipher_Rijndael;
begin
  RCipher:= TCipher_Rijndael.Create('',nil);
  RCipher.Init(Pointer(Key)^,Length(Key),nil);
  RCipher.Mode:= cmCBC;
  Result:= RCipher.DecodeString(MsgData);
  RCipher.Free;
end;

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

Чтобы хранить и использовать зашифрованные данные в виде строки, вы можете использовать Base64Encode (

Но не забывайте Base64Decode перед расшифровкой.

Это та же техника, что и для Blowfish. Иногда символы на самом деле похожи на backspace и выполняют функцию, а не отображаются на экране. Base64Encode в основном преобразует символы во что-то, что вы можете отобразить в тексте.

Перед передачей закодированных данных через Интернет или в другое приложение на том же или другом языке вы ДОЛЖНЫ кодировать и декодировать base64, чтобы не потерять данные. Не забывайте это и в PHP!

person kthxbai2u    schedule 19.10.2014