Delphi DEC 库 (Rijndael) 加密
Posted
技术标签:
【中文标题】Delphi DEC 库 (Rijndael) 加密【英文标题】:Delphi DEC library (Rijndael) encryption 【发布时间】:2012-03-02 20:18:12 【问题描述】:我正在尝试使用 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解密不起作用,输出乱码,与实际数据不同。
当然,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 模式,我会在 ECB 工作后立即使用 CBC。问题是,我是否还必须将 Delphi 中生成的 IV 传输到 PHP 脚本?或者知道密钥就足够了,比如欧洲央行?
【问题讨论】:
这可能是一个非常愚蠢的问题。但是使用delphi你能解密你的加密数据吗?哦,这个问题的答案有帮助吗:***.com/q/8313992/41338 你调用 mcrypt_create_iv()。你在 Delphi 中使用的 IV 是什么? @ldsandon:talereader 正在使用 ECB 模式。没有 IV。 希望 PHP 知道 - 不知道调用 mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)) 会发生什么;也许它只是被忽略了(我希望),也许它触发了一些不好的事情。如果它返回 False,它可能会将错误的参数传递给 mcrypt_decrypt。 @Isandon 想到这一点,通过在加密和解密中生成 IV 来测试 PHP 中的加密/解密,并且输出正常。所以看起来在 ECB 中,mcrypt 忽略了传递的 IV。 【参考方案1】:您正在调用 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;
结束;
【讨论】:
谢谢,这让我走上了正轨。我实际上正在使用我自己的密码转换(从发布的代码中省略),它只使用 PasswordSecret + Timestamp 的 sha1 的前 24 个字符(在 POST 数据中进行)。当我在 PHP 中收到 mcrypt 接受最多 24 个字符的密钥的错误时,我不得不这样做。 问题的另一部分是 DEC 实现实际上是 128 位 Rijndael。发现通过更改PHP解密函数使用RIJNDAEL_128。现在PHP解密函数的输出有原始数据+最后的一些乱码。从我之前在 *** 上的其他帖子中读到的内容来看,这里有一个我必须弄清楚的填充问题。 对,DEC 不会在 ECB 模式下填充您的纯文本,这意味着您传递给函数的纯文本的长度必须是密码块大小的倍数(16 Rijndael/AES)。 PHP 默认使用零填充,因此您应该使用 StringOfChar(#0,16-(Length(MsgData) mod 16)) 填充 MsgData,假设您使用的是非 unicode 版本的 Delphi。 DEC 实现了 Rijndael,块大小为 128 位,密钥大小为 128、192 和 256 位。如果您传递给 Init 方法的密钥长度 恰好为 32。 根据可用的文档 MCRYPT_RIJNDAEL_128 对应于 AES(Rijndael 具有 128 位块大小),无论密钥大小如何。 MCRYPT_RIJNDAEL_256 不是 AES,DEC 不支持。【参考方案2】:好的,总结一下,我的代码存在 3 个问题:
由于我对 mcrypt 和一般密码的理解不足,MCRYPT_RIJNDAEL_256 指的是 128 位 块,而不是指密钥大小。我的正确选择应该是 MCRYPT_RIJNDAEL_128,它是 AES 标准,也受 DEC 3.0 支持。
DEC 有它自己的默认密钥派生,所以我需要绕过它,这样我也不必在 PHP 中实现它。实际上,我使用的是我自己的密钥派生算法,该算法很容易在 PHP 中重现(sha1(key) 的前 32 个字符)。
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)));
【讨论】:
嗯。 SHA-1 只会输出 20 个字节。最简单的解决方案可能是将密钥大小减少到 128 位/16 字节。另一种选择是使用 SHA-256 并从 DEC 3.0 切换到另一个支持 SHA-256 的库,例如我发现的 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。
这与 Blowfish 所需的技术相同。有时字符实际上就像退格键,执行功能而不是显示在屏幕上。 Base64Encode 基本上将字符转换为可以在文本中显示的内容。
在通过互联网传输编码数据或以相同或另一种语言传输到另一个应用程序之前,您必须进行 base64 编码和解码,以免丢失数据。在 PHP 中也不要忘记它!
【讨论】:
以上是关于Delphi DEC 库 (Rijndael) 加密的主要内容,如果未能解决你的问题,请参考以下文章
是否有像 PHP 一样进行 Rijndael 256 位加密的 JavaScript 库?