Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

encryption - Delphi 7 - Encrypt with DEC, and decrypt with PHP OpenSSL

Using: Delphi 7, DEC v5.2

Please refer to this question: Delphi 7 - DCPCrypt - TDCP_rijndael - DecryptString - How to make it work?

From @AmigoJack's excellent answer, I have the Delphi Decrypt function working fine. Based on that, I am now trying to implement the Encrypt function but have been unsuccessful so far. What is happening is that the encryption is done in Delphi, and the string when decrypted in PHP is producing a different string than what was encrypted, implying that something is wrong in the Delphi code.

This is the code:

uses SysUtils, Windows, Classes, DECCipher, DECFmt, DecUtil;

function Encrypt(AStr: string): string;
function Decrypt(AStr: string): string;

implementation

const
  GLUE = '::';
  cPASSWORD = 'myownpassword';

function Encrypt(AStr: string): string;
var
  c: TDecCipher;  
  sKey, 
  sIv,  
  sEncrypted,  
  sPlain: AnsiString;  
  iPosGlue,  
  iLength: Integer;  
begin
  
  sKey := cPASSWORD;
  iLength := 16;
  SetLength(sIv, iLength);

  // Expect DEC 5.2 to only deal with AES-128-CBC, not 256.
  c := ValidCipher(DecCipher.TCipher_Rijndael).Create;
  try
    c.Mode := cmCBCx;
    c.Init(sKey, sIv); // Provide binary key and binary IV

    sPlain := AStr;
    iLength := Length(sPlain);
    SetLength(sEncrypted, iLength); // By now the output length must match the input's
    c.Encode(sPlain[1], sEncrypted[1], iLength);

  finally
    c.Free;
  end;

  Result := TFormat_MIME64.Encode(sEncrypted) + GLUE + TFormat_MIME64.Encode(sIv) + GLUE + IntToStr(iLength);
end;

I am sure that there is something missing with initialization of the variable Iv, and would really be glad if someone could point out the mistake.

UPDATE: As a first step, I've completed the implementation for Encrypt and have it working in Delphi (see my answer below). With that, I seem to have found a completely different, unrelated bug in the code, that I will post in a separate question.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What's a block?

A block cipher (such as AES/Rijndael) only works with the same fixed length (in this case 16 byte = 128 bit). Providing data that doesn't match these 16 bytes is not possible. The data you want to encrypt or decrypt must always be 16 byte = 128 bit in length. Or multiples of that length.

Where does padding apply?

In the real world your data rarily is a multiple of the needed block size. The key, the initialization vector and the data all need to match the block size. If not, they must be padded (or cut) to that size:

  • The key sizes in my example already 16 bytes. Most likely it should be a hash of a textual password (instead of ASCII disguised as binary, as in my example), and there are enough available that output 128 bit = 16 byte - historically MD5 was chosen often. OpenSSH will automatically pad a key with #0 when it's too short and so does DEC5.2 - that means you can use a shorter key both here and in PHP and the output should be the same.
  • The IV needs no further explanation: it's the most random part in all this, so there should be no problem in making it 16 bytes right away.
  • The data can be padded in various ways and OpenSSH by default uses the amount of bytes to be padded as byte value: if 6 bytes of padding are needed, then #6#6#6#6#6#6 is appended; if 2 bytes are needed, then #2#2 is appended.

Why does OpenSSH pad it this way?

  • Only the last of all blocks might be shorter than the needed block size.
  • When decrypting you most likely want to cut off that padding instead of seeing it as part of your input.
  • You look at the last byte and realize it's #15 or lower - now you look at the 14 other preceeding bytes and if they also all are #15 then it's most likely only padding that can be cut off. If the last byte is a #1 then it's not so clear: is this part of the input data or is it padding? To decide/know that it is up to you (i.e. if your input data was text then bytes with such values might never occur). Given the perspective you look at it it might be better than only padding #0 bytes.

PKCS #7 as specified in RFC 2315 §10.3 explains the padding; a comment in PHP's manual to openssl_encrypt() also mentions this.

Encrypting in D7 using DEC5.2

const  // The same glue for concatenating all 3 parts
  GLUE= '::';
var
  c: TDecCipher;  // Successfully tested with DEC 5.2 on Delphi 7
  sKey,  // The binary key we have to provide
  sIv,  // Initialization vector, should be random in the real world
  sEncrypted,  // Output of the encryption
  sPlain: AnsiString;  // What we want to encrypt, in binary
  iPlus,  // Input data padding
  iLength: Integer;  // Plaintext length source, in bytes
begin
  // Keep in mind: Plain, Key and IV are all binary, not text!
  sPlain:= 'AbCdEfGhIjKlMnOpQrStUvWxYz';
  sKey:= '1234567890123456';
  sIv:= #$9e#$8e#$5d#$5a#$b9#$09#$d9#$3c#$99#$1f#$d6#$04#$b9#$8f#$4f#$50;
  iLength:= Length( sPlain );

  // The cipher/algorithm depends on fixed block sizes, so it is automatically
  // padded to the next full length. OpenSSL's padding byte is equal to the amount
  // of bytes to be added: if 6 bytes need to be added, then 6 times #6 is added.
  iPlus:= 16- (iLength mod 16);
  sPlain:= sPlain+ StringOfChar( Chr( iPlus ), iPlus );

  // Expect DEC 5.2 to only deal with AES-128-CBC, not 256.
  c:= ValidCipher( DecCipher.TCipher_Rijndael ).Create;
  try
    c.Mode:= cmCBCx;
    c.Init( sKey, sIv );  // Provide binary key and binary IV
    SetLength( sEncrypted, Length( sPlain ) );  // Both are multiples of 16
    c.Encode( sPlain[1], sEncrypted[1], Length( sPlain ) );

    // Glue it together, should be...
    Writeln
    ( TFormat_MIME64.Encode( sEncrypted )  // '9NC0HhAxFZLuF/omOcidfDQnczlczTS1nIZkNPOlQZk='
    + GLUE+ TFormat_MIME64.Encode( sIv )   // '::no5dWrkJ2TyZH9YEuY9PUA=='
    + GLUE+ IntToStr( iLength )            // '::26'
    );
  finally
    c.Free;
  end;
end;

The padding needs to be the same on both ends: either you instruct OpenSSL to use #0 padding, or you have to mimic the PKCS #7 padding on your side (because DEC is not OpenSSL). Of course: you could also use OpenSSL in Delphi right away instead of relying on DEC, but then none of the details would have surfaced - I rather want to know them to be able to know which parts may break instead of keeping all the "magic" under the hood and only calling one function which does all the work. In the end one has to understand sooner or later how cryption works - you're walking on thin ice if you never tried to use one tool to encrypt and a different one to decrypt.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...