【发布时间】:2019-03-13 08:24:14
【问题描述】:
我有一个客户端提供一个 API,该 API 规定发送给他们的数据必须使用 AES、128 位密钥、ECB 模式和 PKCS5Padding 进行加密。我正在尝试在 Delphi 10.3 Rio 中使用 LockBox 3,但没有得到与他们指出用于验证的 online test tool 相同的加密字符串。它很近,但并不完全在那里。
在这里阅读了很多关于Unicode、PKCS5Padding 和related questions 的内容,我已经到了该尝试的结尾。我必须承认,我在加密方面做得并不多,在我提问之前,我一直在尽可能多地阅读以了解这一点。
有几件事我想确认一下:
- 密码和密钥的区别。我读过 LB3 使用密码来生成密钥,但我有来自客户端的关于如何生成密钥的具体说明,所以我制作了自己的 Base64 编码密钥并调用 InitFromStream 来初始化它。我相信这可以代替设置密码,对吗?或者密码可能仅由非对称密码器(不是对称密码器,如 AES)使用?
- PKCS5Padding:我担心我在LB3 Help site 上读到的内容说填充是根据密码、链接模式等的选择智能完成的。这是否意味着没有办法强制它使用特定的填充方法?我已将数据转换为字节数组并由自己的 PKCS5Padding 实现,但我认为 LB3 可能仍在填充之外。 (我尝试过查看代码,但没有发现任何证据表明它正在这样做。)
我应该在 Delphi 中使用不同的加密库来完成此操作吗?我已经查看了 DelphiEncryptionCompendium 和 DcPCryptV2,但我发现 LB3 似乎得到了最多的支持,我觉得它是最容易使用,尤其是在我的 Unicode 版本的 Delphi 中。另外,过去几年我用过很多次 LockBox 2,所以我认为它会更熟悉(事实并非如此)。
为了说明我的尝试,我将我的代码从项目中提取到一个控制台应用程序中。也许我上面的假设是正确的,我的代码或 LB3 参数中有一个明显的错误,我不明白有人会指出:
program LB3ConsoleTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes, System.NetEncoding,
uTPLb_Codec, uTPLb_CryptographicLibrary,
uTPLb_StreamUtils, uTPLb_Constants;
var
Codec: TCodec;
CryptographicLibrary: TCryptographicLibrary;
function PKCS5PadStringToBytes(RawData: string; const PadSize: Integer): TBytes;
{ implement our own block padding }
var
DataLen: Integer;
PKCS5PaddingCount: ShortInt;
begin
Result := TEncoding.UTF8.GetBytes(RawData);
DataLen := Length(RawData);
PKCS5PaddingCount := PadSize - DataLen mod PadSize;
if PKCS5PaddingCount = 0 then
PKCS5PaddingCount := PadSize;
Inc(DataLen, PKCS5PaddingCount);
SetLength(Result, DataLen);
FillChar(Result[DataLen - PKCS5PaddingCount], PKCS5PaddingCount, PKCS5PaddingCount);
end;
procedure InitializeAESKey(const AESKey: string);
{ convert the string to a byte array,
use that to initialize a ByteStream,
and call LB3's InitFromStream }
var
AESKeyBytes: TBytes;
AESKeyStream: TBytesStream;
begin
AESKeyBytes := TEncoding.UTF8.GetBytes(AESKey);
AESKeyStream := TBytesStream.Create(AESKeyBytes);
Codec.InitFromStream(AESKeyStream);
end;
const
RawData = '{"invoice_id":"456456000018047","clerk_id":"0023000130234234","trans_amount":1150034534,"cust_code":"19455605000987890641","trans_type":"TYPE1"}';
AESKeyStr = 'CEAA31AD1EE4BDC8';
var
DataBytes: TBytes;
DataStream: TBytesStream;
ResultStream: TBytesStream;
ResultBytes: TBytes;
Base64Encoder: TBase64Encoding;
begin
// create the LockBox3 objects
Codec := TCodec.Create(nil);
CryptographicLibrary := TCryptographicLibrary.Create(nil);
try
// setup LB3 for AES, 128-bit key, ECB
Codec.CryptoLibrary := CryptographicLibrary;
Codec.StreamCipherId := uTPLb_Constants.BlockCipher_ProgId;
Codec.BlockCipherId := Format(uTPLb_Constants.AES_ProgId, [128]);
Codec.ChainModeId := uTPLb_Constants.ECB_ProgId;
// prep the data, the key, and the resulting stream
DataBytes := PKCS5PadStringToBytes(RawData, 8);
DataStream := TBytesStream.Create(DataBytes);
InitializeAESKey(AESKeyStr);
ResultStream := TBytesStream.Create;
// ENCRYPT!
Codec.EncryptStream(DataStream, ResultStream);
// take the result stream, convert it to a byte array
ResultStream.Seek(0, soFromBeginning);
ResultBytes := Stream_to_Bytes(ResultStream);
// convert the byte array to a Base64-encoded string and display
Base64Encoder := TBase64Encoding.Create(0);
Writeln(Base64Encoder.EncodeBytesToString(ResultBytes));
Readln;
finally
Codec.Free;
CryptographicLibrary.Free;
end;
end.
此程序生成一个 216 个字符长的加密字符串,只有最后 25 个字符与 online tool 生成的不同。
为什么?
【问题讨论】:
-
这看起来有点,呃,可疑:
FillChar(Result[DataLen - PKCS5PaddingCount], PKCS5PaddingCount, PKCS5PaddingCount);。你确定这是正确的吗? PKCS5PaddingCount 的值是多少? -
FWIW,当时从 Delphi 2007 迁移到(我认为)XE5 时,我也遇到了 LockBox 问题,而且我之前加密的东西无法用新版本解密。我最终用 2007 版本的解密逻辑编译了一个单独的 dll,所以我可以在我的应用程序的 XE5 版本中解密它。
-
IIRC PKCS5 填充大约是 8 字节块,而 AES 使用 16 字节块。所以我猜你的意思是 PKCS7 填充,而不是 PKCS5 填充。
-
另请注意,ECB 链接模式非常不安全,应不惜一切代价避免。值得更改为新模式(和 256 位?)。
-
对于一个跨平台(Delphi + FPC),维护良好的开源替代方案,处理速度非常快(肯定是最快的,因为它是唯一使用 AES-NI 的),请考虑我们的github.com/synopse/mORMot/blob/master/SynCrypto.pas
标签: delphi encryption lockbox-3