【问题标题】:Delphi FMX dcpcrypt wrong result on macOS 64-bitDelphi FMX dcpcrypt 在 macOS 64 位上的错误结果
【发布时间】:2019-07-31 20:45:52
【问题描述】:

我使用的是 Delphi 10.3.2 我正在尝试将 DCPCrypt 单元与 firemonkey 框架一起使用来加密字符串。 它在 Win32、Win64 和 macOS 32 目标上 100% 运行,结果始终相同。但是当我为 macOS 64 编译时,结果就不同了。

这是使用的代码:

function EncodeAES(code:ansistring; key:ansistring):string;
var
    s,u:ansistring;
  enc: TEncoding;
  k,iv, Data, Crypt: TBytes;
  Cipher: TDCP_rijndael;
begin
    u:='';
    enc:=TEncoding.ANSI;
    Data := enc.GetBytes(code); // VMpuXJGbUNOv
    k:=enc.GetBytes(key);   // kj3214ed)k32nre2
    iv:=enc.GetBytes(u);
    Cipher:=TDCP_rijndael.Create(nil);
    Cipher.Init(K[0], 128, @iv[0]);
    Crypt:=Copy(Data, 0, Length(Data));
    BytePadding(Crypt, Cipher.BlockSize, pmPKCS7);
    Cipher.EncryptECB(Crypt[0], Crypt[0]);
    Cipher.Free;
    s:=tencoding.ANSI.GetString(crypt);
    result:=StringToHex(s);
end;

这是 BytePadding 函数:

procedure BytePadding(var Data: TBytes; BlockSize: integer; PaddingMode: TPaddingMode);
var
    I, DataBlocks, DataLength, PaddingStart, PaddingCount: integer;
begin
    BlockSize := BlockSize div 8;
    if PaddingMode in [pmZeroPadding, pmRandomPadding] then
        if Length(Data) mod BlockSize = 0 then Exit;
    DataBlocks := (Length(Data) div BlockSize) + 1;
    DataLength := DataBlocks * BlockSize;
    PaddingCount := DataLength - Length(Data);
    if PaddingMode in [pmANSIX923, pmISO10126, pmPKCS7] then
        if PaddingCount > $FF then Exit;
    PaddingStart := Length(Data);
    SetLength(Data, DataLength);
    case PaddingMode of
        pmZeroPadding, pmANSIX923, pmISO7816: // fill with $00 bytes
            FillChar(Data[PaddingStart], PaddingCount, 0);
        pmPKCS7: // fill with PaddingCount bytes
            FillChar(Data[PaddingStart], PaddingCount, PaddingCount);
        pmRandomPadding, pmISO10126: // fill with random bytes
            for I := PaddingStart to DataLength-1 do Data[I] := Random($FF);
    end;
    case PaddingMode of
        pmANSIX923, pmISO10126:
            Data[DataLength-1] := PaddingCount; // set end-marker with number of bytes added
        pmISO7816:
            Data[PaddingStart] := $80; // set fixed end-markder $80
    end;
end;

我这样调用函数:

procedure TForm1.BtnClick(Sender: TObject);
var
    s:string;
begin
    s:=EncodeAES('VMpuXJGbUNOv','kj3214ed)k32nre2');
end;

Win32/Win64/macOS 32 的结果(正确): E32A9DE47CC60BDB70CA27885128D17A

macOS 64 的结果(错误): CF622155545E485AC3A083E8A0478493

我做错了什么?

【问题讨论】:

  • 啊!!!不再。如此多的问题将二进制和文本视为可互换的。请让它停下来!不过说真的,加密算法是在二进制上运行的。您的加密函数需要使用字节数组作为输入和输出。
  • 我假设当 data、crypt、iv 和 k 变量声明为 TBytes 时,它们是字节数组。
  • @Softtouch 是的,但您有责任在机器/平台上正确且一致地使用有效字节填充它们,而您的 EncodeAES() 代码根本没有这样做。
  • DCPCrypt2 在 DCPcrypt2.pas 中的定义不正确。 dword = longword 应该是:dword = Cardinal。长字在 64 位 Posix 系统上是 8 个字节
  • @DaveNottage 我会选择UInt32 而不是Cardinal

标签: delphi firemonkey


【解决方案1】:

加密对二进制数据进行操作,而不是文本数据。在处理文本时,必须在加密前将字符编码为字节,然后在解密后将字节解码为字符。这意味着在加密之前和解密之后使用相同的字符编码。您正在尝试进行这种编码,但您没有考虑到TEncoding.ANSI 不能跨操作系统平台移植,甚至不能跨使用相同操作系统平台的不同机器移植。为了更好的移植性,需要使用一致的编码,比如TEncoding.UTF8

另外,TEncoding 只能与UnicodeString 一起使用,因此将TEncoding.GetBytes()TEncoding.GetString()AnsiString 一起使用将使用RTL 的 ANSI 定义在ANSI 和Unicode 之间执行隐式转换,不是 你的,如果你的字符串中包含任何非 ASCII 字符,则会产生你可能不期望的字节。

您的EncodeAES() 函数最好使用(Unicode)String 进行所有字符串处理,而忘记AnsiString 甚至存在。尽管像 Linux 这样的平台在很大程度上是 UTF-8 系统,但 Delphi 的默认 (Unicode)string 在所有平台上都使用 UTF-16。如果要编码 ANSI 字符串,请使用 RawByteString 以避免任何隐式转换,如果调用代码要使用 UTF8StringAnsiString(N) 等。按原样加密 8 位字符,而不使用 TEncoding。仅对 UnicodeString 数据使用 TEncoding

最后,您的EncodeAES() 函数不应将加密字节解码为UnicodeString,只是为了将其转换为十六进制字符串。您应该按原样对加密字节进行十六进制编码。

试试这个:

function EncodeAES(const Data: TBytes; const Key: string): string; overload;
const
  Hex: array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
var
  enc: TEncoding;
  iv, k, Crypt: TBytes;
  Cipher: TDCP_rijndael;
  B: Byte;
  I: Integer;
begin
  enc := TEncoding.UTF8;
  iv := enc.GetBytes('');
  k := enc.GetBytes(Key);

  Cipher := TDCP_rijndael.Create(nil);
  try
    Cipher.Init(k[0], 128, @iv[0]);
    Crypt := Copy(Data, 0, Length(Data));
    BytePadding(Crypt, Cipher.BlockSize, pmPKCS7);
    Cipher.EncryptECB(Crypt[0], Crypt[0]);
  finally
    Cipher.Free;
  end;

  SetLength(Result, Length(Crypt)*2);
  I := Low(Result);
  for B in Crypt do
  begin
    Result[ I ] := Hex[(B shr 4) and $F];
    Result[I+1] := Hex[B and $F];
    Inc(I, 2);
  end;
end;

function EncodeAES(const S, Key: string): string; overload;
begin
  Result := EncodeAES(TEncoding.UTF8.GetBytes(S), Key);
end;

function EncodeAES(const S: RawByteString; const Key: string): string; overload;
begin
  Result := EncodeAES(BytesOf(S), Key);
end;

【讨论】:

  • 现在完全明白了。不幸的是,当要加密的字符串长于 15 个字符时,dcpcrypt 组件中一定还有一些东西会导致它失败。我使用的是正常的 TMS Cryptography Pack,它可以正常工作,但它们现在还没有更新到 macOS 64,我的项目无法编译,所以我正在寻找另一个解决方案。
  • 我用密钥 1111111111111111 (15x1) 加密 111111111111111 (15x1)。在线工具,我自己的加密代码和其他 Win32 组件返回都一样:FD97A42377D8235121E41640B935BDDD 现在我再加一个 1,所以它是 16x1。在线工具,和其他Win32组件返回都一样:AD0110483D0559D12E8CB97203E6C908F30C69C4F945E654EBD4B388B1C8F790但基于DCPCrypt返回自己的代码:AD0110483D0559D12E8CB97203E6C90810101010101010101010101010101010我使用的示例代码雷米以上。我究竟做错了什么?我不明白...
猜你喜欢
  • 1970-01-01
  • 2022-06-30
  • 2014-01-12
  • 1970-01-01
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
相关资源
最近更新 更多