【问题标题】:LockBox 3 Encrypting not matching online tool, suspect padding or key problemLockBox 3 加密不匹配在线工具,怀疑填充或密钥问题
【发布时间】:2019-03-13 08:24:14
【问题描述】:

我有一个客户端提供一个 API,该 API 规定发送给他们的数据必须使用 AES、128 位密钥、ECB 模式和 PKCS5Padding 进行加密。我正在尝试在 Delphi 10.3 Rio 中使用 LockBox 3,但没有得到与他们指出用于验证的 online test tool 相同的加密字符串。它很近,但并不完全在那里。

在这里阅读了很多关于UnicodePKCS5Paddingrelated questions 的内容,我已经到了该尝试的结尾。我必须承认,我在加密方面做得并不多,在我提问之前,我一直在尽可能多地阅读以了解这一点。

有几件事我想确认一下:

  • 密码和密钥的区别。我读过 LB3 使用密码来生成密钥,但我有来自客户端的关于如何生成密钥的具体说明,所以我制作了自己的 Base64 编码密钥并调用 InitFromStream 来初始化它。我相信这可以代替设置密码,对吗?或者密码可能仅由非对称密码器(不是对称密码器,如 AES)使用?
  • PKCS5Padding:我担心我在LB3 Help site 上读到的内容说填充是根据密码、链接模式等的选择智能完成的。这是否意味着没有办法强制它使用特定的填充方法?我已将数据转换为字节数组并由自己的 PKCS5Padding 实现,但我认为 LB3 可能仍在填充之外。 (我尝试过查看代码,但没有发现任何证据表明它正在这样做。)

我应该在 Delphi 中使用不同的加密库来完成此操作吗?我已经查看了 DelphiEncryptionCompendiumDcPCryptV2,但我发现 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


【解决方案1】:

AES 使用 16 字节块,而不是 8 字节。

所以你需要 PKCS7 填充 16 个字节,而不是 PKCS5 固定为 8 个字节。

请尝试

DataBytes := PKCS5PadStringToBytes(RawData, 16);

还可以考虑将链接模式更改为远离 ECB,which is pretty weak, so is to be avoided for any serious work

【讨论】:

  • 据我了解,AES 可以使用 8 字节块或 16 字节块。标准(可能是许多库中的默认值)是 16 字节块,但我调用的 API 专门使用 PKCS5Padding。它还指定了 ECB 模式,这是另一个要求(如原始帖子中所述)。我想,这种模式的弱点是通过从包含一次性客户授权码的数据中动态生成 AES 密钥来解决的。
  • AES 适用于 16 字节块 - 我不知道您指的是什么。当然,您调用的 API 具有误导性。 PKCS5 仅用于 8 字节填充:允许填充大小的参数是 PKCS7 的用途 - 而不是 PKCS5。并且即使使用临时密钥,也应该使用 ECB。如果用户只能猜测一些未加密的字节(例如前 16 个字节),则可以解密整个内容。
  • 说实话,我不在乎填充是什么——我只是希望我的加密字符串能够被另一端的第 3 方 API 解密。为此,我必须能够将我的输出与在线工具devglan.com/online-tools/aes-encryption-decryption 的输出相匹配。我一直提到 PKCS5Padding 的原因是这就是 API 文档所需要的——那和 ECB。无论多么不安全或过时或其他什么,这都是我完成这个项目所必须生产的。
  • 那么改成RawData, 16 解决了你的问题吗?
  • 不,我在发布问题之前已经尝试过了。我确实发现了一件奇怪/有趣的事情:使用 8 字节填充返回了正确长度的字符串,16 字节填充返回了一个太长的字符串,但是如果我以 8 字节填充字符串的长度将其切断,则加密结果匹配!但是,我需要向 RawData 添加另一个参数,现在找不到类似的模式。否则,那个“hack”本来可以工作的。
猜你喜欢
  • 2019-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-07
  • 1970-01-01
  • 1970-01-01
  • 2015-03-11
  • 2018-09-10
相关资源
最近更新 更多