8

我正在尝试使用DEC 3.0 库Delphi Encryption Compedium Part I)对 Delphi 7 中的数据进行加密,并通过 POST 将其发送到 PHP 脚本,在那里我使用mcrypt(RIJNDAEL_256,ECB 模式)对其进行解密。

德尔福部分:

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 的解密不起作用,输出是乱码,与实际数据不同。

当然,DelphiKey和 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 模式,我会在 ECB 工作后立即使用 CBC。问题是,我是否还必须将 Delphi 中生成的 IV 传输到 PHP 脚本?或者知道密钥就足够了,比如欧洲央行?

4

3 回答 3

6

您正在调用 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;

结尾;

于 2012-02-10T11:36:30.840 回答
2

好的,总而言之,我的代码存在 3 个问题:

  1. 由于我对 mcrypt 和密码的一般理解不足,MCRYPT_RIJNDAEL_256 指的是 128 位,而不是指密钥大小。我的正确选择应该是 MCRYPT_RIJNDAEL_128,它是 AES 标准,也受 DEC 3.0 支持。

  2. DEC 有它自己的默认密钥派生,所以我需要绕过它,这样我也不必在 PHP 中实现它。实际上,我使用的是我自己的密钥派生算法,该算法很容易在 PHP 中重现(sha1(key) 的前 32 个字符)。

  3. 正如 mcrypt 所期望的那样,DEC 不会将明文填充为密码块大小的倍数,因此我必须手动完成。

在下面提供工作代码:

德尔福:

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)));
}
于 2012-02-10T14:35:16.160 回答
0

我发现的 256 位密钥是 32 个字符或 32 个字节。不是 24。这可能是问题所在。

[编辑]

我将每个人的想法(支持等)合并为一个带有修复的想法。

此外,您正在使用 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。

这与河豚所需的技术相同。有时字符实际上就像退格键,执行功能而不是显示在屏幕上。Base64Encode 基本上将字符转换为可以在文本中显示的内容。

在通过 Internet 传输编码数据或以相同或另一种语言传输到另一个应用程序之前,您必须进行 base64 编码和解码,以免丢失数据。不要在 PHP 中忘记它!

于 2014-10-19T00:43:28.623 回答