Delphi 7 - 使用 DEC 加密,使用 PHP OpenSSL 解密

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

使用:Delphi7,DEC v5.2

请参考这个问题:

根据@AmigoJack 的出色回答,我的 Delphi Decrypt 功能运行良好。基于此,我现在正在尝试实现 Encrypt 功能,但到目前为止一直没有成功。正在发生的事情是,加密是在 Delphi 中完成的,而在 PHP 中解密的字符串生成的字符串与加密的字符串不同,这意味着 Delphi 代码中存在错误.

这是代码:

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;

我确定变量 Iv 的初始化遗漏了一些东西,如果有人能指出错误,我将非常高兴。

更新: 作为第一步,我已经完成了 Encrypt 的实现并让它在 Delphi 中运行(请参阅下面的回答)。这样,我似乎在代码中发现了一个完全不同的、不相关的错误,我将 post 在一个单独的问题中。

根据上面 Michael 和 Olivier 的评论,我对代码进行了更正,并在 Delphi 中实现了 Encrypt 的工作实现,代码如下。

代码:

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

function Encrypt(AStr: string): string;

implementation

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

function Encrypt(AStr: string): string;
var
  c: TDecCipher; // Successfully tested with DEC 5.2 on Delphi 7
  sKey, // The binary key we have to provide
  sIv, // Initialization vector, random bytes
  sEncrypted, // Encrypted binary we want to get
  sPlain: AnsiString; // Actual data to encrypt
  iLength: Integer; // Plaintext length target, in bytes
begin

  sKey := cPASSWORD;
  iLength := 16;
  // ** sKey := sKey + StringOfChar(#0, iLength - Length(sKey));
  SetLength(sIv, iLength);
  RandomBuffer(sIv[1], 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); // Set the output byte array length 
    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;

function Decrypt(AStr: string): string;
var
  c: TDecCipher; // Successfully tested with DEC 5.2 on Delphi 7
  sKey, // The binary key we have to provide
  sIv, // Initialization vector, decoded from AStr
  sEncrypted, // Actual data to decrypt, decoded from AStr
  sPlain: AnsiString; // Decrypted binary we want to get
  iPosGlue, // Next found glue token to cut one part off
  iLength: Integer; // Plaintext length target, in bytes
begin
  iPosGlue := Pos(GLUE, AStr);
  sEncrypted := Copy(AStr, 1, iPosGlue - 1); // Still Base64
  Delete(AStr, 1, iPosGlue - 1 + Length(GLUE));

  iPosGlue := Pos(GLUE, AStr);
  sIv := Copy(AStr, 1, iPosGlue - 1);
  Delete(AStr, 1, iPosGlue - 1 + Length(GLUE));

  iLength := StrToInt(AStr);

  sKey := cPASSWORD;

  // Decode Base64 back into binary
  sEncrypted := TFormat_MIME64.Decode(sEncrypted);
  sIv := TFormat_MIME64.Decode(sIv);

  // 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(sPlain, Length(sEncrypted)); // By now the output length must match the input's
    c.Decode(sEncrypted[1], sPlain[1], Length(sEncrypted));
    SetLength(sPlain, iLength); // Now cut it to the actual expected length
  finally
    c.Free;
  end;

  Result := sPlain;
end;

// Sample implementation: (Delphi 7)
// var
//   s: string;
// begin
//   s := 'The quick brown fox jumps over the lazy rabbit..';
//   WriteLn(s);
//   s := Encrypt(s);
//   WriteLn(s);
//   s := Decrypt(s);
//   WriteLn(s);
// 
// end;

什么是区块?

A block cipher (such as AES/Rijndael) 仅适用于相同的固定长度(在本例中为 16 字节 = 128 位)。提供与这 16 个字节不匹配的数据是不可能。您要加密或解密的数据的长度必须始终为 16 字节 = 128 位。或该长度的倍数。

填充应用在哪里?

在现实世界中,您的数据很少是所需块大小的倍数。 keyinitialization vectordata 都需要匹配块大小。如果不是,则必须将它们填充(或剪切)到该尺寸:

  • 我的示例中的 key 大小已经是 16 个字节。它很可能应该是文本密码的哈希值(而不是像我的示例中那样伪装成二进制的 ASCII),并且经常选择 there are enough available that output 128 bit = 16 byte - historically MD5OpenSSH 会在密钥太短时自动用 #0 填充密钥,DEC5.2 也是如此 - 这意味着您可以使用更短的密钥此处和 PHP 中的键,输出应该相同。
  • IV不用多说了,它是这里面最随机的部分,所以马上变成16字节应该没问题。
  • data可以用多种方式填充,OpenSSH默认使用要填充的字节数作为字节值:如果需要6个字节的padding,然后追加#6#6#6#6#6#6;如果需要 2 个字节,则附加 #2#2

为什么 OpenSSH 会这样填充它?

  • 所有块中只有最后一个可能比所需的块大小短。
  • 解密时,您很可能希望切断该填充,而不是将其视为输入的一部分。
  • 您查看最后一个字节并意识到它是 #15 或更低 - 现在您查看其他 14 个前面的字节,如果它们也都是 #15 那么很可能只有填充可以被切断。如果最后一个字节是 #1 那么就不太清楚了:这部分是输入数据还是填充?对于 decide/know,这取决于您(即,如果您的输入数据是文本,则可能永远不会出现具有此类值的字节)。从您的角度来看,它可能比仅填充 #0 字节更好。

PKCS #7 as specified in RFC 2315 §10.3 explains the padding; a comment in PHP's manual to openssl_encrypt()也提到了这一点。

使用 DEC5.2 在 D7 中加密

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:= #e#e#d#a#$b9##$d9#c##f#$d6##$b9#f#f#;
  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;

两端的填充必须相同:要么您指示 OpenSSL 使用 #0 填充,要么您必须模仿 PKCS #7 填充在你这边(因为 DEC 不是 OpenSSL)。当然:您也可以立即在 Delphi 中使用 OpenSSL 而不是依赖 DEC,但随后 none 的细节就会浮出水面——我宁愿了解它们,以便能够知道哪些部分可能会损坏,而不是将所有“魔法”隐藏在引擎盖下,只调用一个函数来完成所有工作工作。最后,人们迟早要了解加密的工作原理 - 如果您从未尝试使用一种工具进行加密,而使用另一种工具进行解密,那么您就是如履薄冰。