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 位。或该长度的倍数。
填充应用在哪里?
在现实世界中,您的数据很少是所需块大小的倍数。 key、initialization vector 和 data 都需要匹配块大小。如果不是,则必须将它们填充(或剪切)到该尺寸:
- 我的示例中的 key 大小已经是 16 个字节。它很可能应该是文本密码的哈希值(而不是像我的示例中那样伪装成二进制的 ASCII),并且经常选择 there are enough available that output 128 bit = 16 byte - historically MD5。 OpenSSH 会在密钥太短时自动用
#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 的细节就会浮出水面——我宁愿了解它们,以便能够知道哪些部分可能会损坏,而不是将所有“魔法”隐藏在引擎盖下,只调用一个函数来完成所有工作工作。最后,人们迟早要了解加密的工作原理 - 如果您从未尝试使用一种工具进行加密,而使用另一种工具进行解密,那么您就是如履薄冰。
使用: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 位。或该长度的倍数。
填充应用在哪里?
在现实世界中,您的数据很少是所需块大小的倍数。 key、initialization vector 和 data 都需要匹配块大小。如果不是,则必须将它们填充(或剪切)到该尺寸:
- 我的示例中的 key 大小已经是 16 个字节。它很可能应该是文本密码的哈希值(而不是像我的示例中那样伪装成二进制的 ASCII),并且经常选择 there are enough available that output 128 bit = 16 byte - historically MD5。 OpenSSH 会在密钥太短时自动用
#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 的细节就会浮出水面——我宁愿了解它们,以便能够知道哪些部分可能会损坏,而不是将所有“魔法”隐藏在引擎盖下,只调用一个函数来完成所有工作工作。最后,人们迟早要了解加密的工作原理 - 如果您从未尝试使用一种工具进行加密,而使用另一种工具进行解密,那么您就是如履薄冰。