Шифроване на библиотеката 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. Може би някой може да ми каже как това внедряване се различава от mcrypt на PHP с 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 скрипта? Или познаването на ключа е достатъчно, като за ECB?


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: talereader използва режим 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)), като се предполага, че използвате не-unicode версия на Delphi. - person Henrick Hellström; 10.02.2012
comment
DEC прилага Rijndael с размер на блока от 128 бита и размери на ключовете 128, 192 и 256 бита. Ако ключът, който предавате на метода Init, има дължина ‹= 16, ще се използва AES-128, 16 ‹ дължина ‹= 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, така че трябваше да го направя ръчно.

Предоставяне на работещ код по-долу:

Делфи:

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, като ‹shameless plug›OpenStrSecII или StreamSec Tools‹/shameless plug› или 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. Това може да е проблемът.

[РЕДАКТИРАНЕ]

Комбинирах идеите на всички (ansistring и т.н.) в една единствена идея с поправка.

Освен това използвате codestring( -- трябва да е 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. Понякога знаците всъщност са като бекспейс и изпълняват функцията, вместо да се показват на екрана. Base64Encode основно преобразува символите в нещо, което можете да покажете в текст.

Преди да прехвърлите кодираните данни през интернет или към друго приложение на същия или друг език, ТРЯБВА да кодирате и декодирате base64, за да не загубите данни. Не го забравяйте и в PHP!

person kthxbai2u    schedule 19.10.2014