Delphi & CryptoAPI - 如何计算 HMAC-SHA512 哈希值?

Delphi & CryptoAPI - how to calculate HMAC-SHA512 hash?

有人知道如何使用 MS CryptoAPI 在 Delphi 2010+ 中计算 HMAC-SHA512 哈希值吗?

来自 MS 网站的示例,https://docs.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--creating-an-hmac 生成了错误的结果。

我找到了这个答案 to be somehow usefull (because it is manual rewrite from https://en.wikipedia.org/wiki/HMAC),但它不在 Pascal 中,我尝试将其重构为 Pascal 的尝试没有成功。它有效,但仍然计算出错误的结果。

有人可以帮我吗?

编辑:: 这是我遇到问题的代码:

uses
  Windows,
  JwaWinCrypt,
  JwaWinError;

const
  BLOCK_SIZE = 64;

type
  EHMACError = class(Exception);

function WinError(const RetVal: BOOL; const FuncName: String): BOOL;
var
  dwResult: Integer;
begin
  Result:=RetVal;
  if not RetVal then begin
    dwResult:=GetLastError();
    raise EHMACError.CreateFmt('Error [x%x]: %s failed.'#13#10'%s', [dwResult, FuncName, SysErrorMessage(dwResult)]);
  end;
end;

function TBytesToHex(const Value: TBytes): String;
const
  dictionary: Array[0..15] of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
var
  i: Integer;
begin
  Result:='';
  for i:=0 to High(Value) do
    Result:=Result + dictionary[Value[i] shr 4] + dictionary[Value[i] and [=11=]F];
end;

function hmac(AKey, AMessage: TBytes; Algid: ALG_ID): TBytes;

  function hash(const hProv: HCRYPTPROV; hData: TBytes): TBytes;
  var
    len, cb: DWORD;
    hHash: HCRYPTHASH;
  begin
    SetLength(Result, 0);
    WinError(CryptCreateHash(hProv, Algid, 0, 0, hHash), 'CryptCreateHash');
    try
      len:=Length(hData);
      cb:=SizeOf(len);
      WinError(CryptHashData(hHash, @hData[0], len, 0), 'CryptHashData');
      WinError(CryptGetHashParam(hHash, HP_HASHSIZE, @len, cb, 0), 'CryptGetHashParam(HP_HASHSIZE)');
      SetLength(Result, len);
      WinError(CryptGetHashParam(hHash, HP_HASHVAL, @Result[0], len, 0), 'CryptGetHashParam(HP_HASHVAL)');
    finally
      WinError(CryptDestroyHash(hHash), 'CryptDestroyHash');
    end;
  end;

  function double_hash(const hProv: HCRYPTPROV; hData1, hData2: TBytes): TBytes;
  var
    len, len1, len2, cb: DWORD;
    hHash: HCRYPTHASH;
  begin
    SetLength(Result, 0);
    WinError(CryptCreateHash(hProv, Algid, 0, 0, hHash), 'DH_CryptCreateHash');
    try
      len1:=Length(hData1);
      len2:=Length(hData2);
      cb:=SizeOf(DWORD);
      WinError(CryptHashData(hHash, @hData1[0], len1, 0), 'DH_CryptHashData(hData1)');
      WinError(CryptHashData(hHash, @hData2[0], len2, 0), 'DH_CryptHashData(hData1)');
      WinError(CryptGetHashParam(hHash, HP_HASHSIZE, @len, cb, 0), 'DH_CryptGetHashParam(HP_HASHSIZE)');
      SetLength(Result, len);
      WinError(CryptGetHashParam(hHash, HP_HASHVAL, @Result[0], len, 0), 'DH_CryptGetHashParam(HP_HASHVAL)');
    finally
      WinError(CryptDestroyHash(hHash), 'DH_CryptDestroyHash');
    end;
  end;

var
  hProv: HCRYPTPROV;
  hHash: HCRYPTHASH;
  i_key_pad, o_key_pad: TBytes;
  data, ret: TBytes;
  len, i: Integer;
  c: Byte;
  ifree: Boolean;
begin
  ifree:=False;
  SetLength(Result, 0);
  SetLength(i_key_pad, BLOCK_SIZE);
  SetLength(o_key_pad, BLOCK_SIZE);
  WinError(CryptAcquireContext(hProv, Nil, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT), 'CryptAcquireContext');
  try
    data:=AKey;
    len:=Length(data);
    if len > BLOCK_SIZE then begin
      data:=hash(hProv, data);
      ifree:=True;
    end;
    //
    i:=BLOCK_SIZE-1;
    while i >= 0 do begin
      if i < len then
        c:=data[i]
      else
        c:=0;
      i_key_pad[i]:= xor c;
      o_key_pad[i]:=c xor c;
      Dec(i);
    end;
    data:=double_hash(hProv, i_key_pad, AMessage);
    Result:=double_hash(hProv, o_key_pad, data);
    SetLength(data, 0);
  finally
    if ifree then
      SetLength(data, 0);
    SetLength(i_key_pad, 0);
    SetLength(o_key_pad, 0);
    WinError(CryptReleaseContext(hProv, 0), 'CryptReleaseContext');
  end;
end;

...它被调用:

Result:=hmac(Password, InString, CALG_SHA_512);

示例:

TBytesToHex(hmac('pass', 'test', CALG_SHA_512)); 产生(十六进制编码)

1319bb7baefc3fbaf07824261c240cecd04a54cd83cdf0deb68e56cadff20e7c644e2e956660ab9df47a19502173090df5ec3d0b9236d59917afc4f3607cf980

online HMAC calculator 产生

46beca277a5fec10beba65b0c2fb3917115f352eb8b2560e9ada0a3dbafb6c7a3fc456b1e13a07c4a9c856b633b70b2403907ca89894021772393e3f97e78684

对于相同的输入

我的问题的完整解决方案,感谢@whosrdaddy 的帮助。

//
// HMAC-SHA512 - cryptoapi hash generation
//
// based on:
//   https://en.wikipedia.org/wiki/HMAC
//   https://github.com/ogay/hmac
//
// refactored from:
//   
//
unit CryptoAPI_HMAC_SHA512;

interface

uses
  SysUtils,
  Classes;

function CryptoAPI_Hash_HmacSHA512(const InString, Password: TBytes): TBytes; overload;
function CryptoAPI_Hash_HmacSHA512(const InString, Password: String): String; overload;

implementation

uses
  Windows,
  JwaWinCrypt,
  JwaWinError;

const
  BLOCK_SIZE  = 128; // bytes for SHA512

type
  EHMACError = class(Exception);

function WinError(const RetVal: BOOL; const FuncName: String): BOOL;
var
  dwResult: Integer;
begin
  Result:=RetVal;
  if not RetVal then begin
    dwResult:=GetLastError();
    raise EHMACError.CreateFmt('Error [x%x]: %s failed.'#13#10'%s', [dwResult, FuncName, SysErrorMessage(dwResult)]);
  end;
end;

function hmac(AKey, AMessage: TBytes; Algid: ALG_ID): TBytes;

  function hash(const hProv: HCRYPTPROV; hData1, hData2: TBytes): TBytes;
  var
    len, len1, len2, cb: DWORD;
    hHash: HCRYPTHASH;
  begin
    SetLength(Result, 0);
    WinError(CryptCreateHash(hProv, Algid, 0, 0, hHash), 'CryptCreateHash');
    try
      len:=0;
      len1:=Length(hData1);
      len2:=Length(hData2);
      cb:=SizeOf(DWORD);
      WinError(CryptHashData(hHash, @hData1[0], len1, 0), 'CryptHashData(hData1)');
      if len2 > 0 then
        WinError(CryptHashData(hHash, @hData2[0], len2, 0), 'CryptHashData(hData1)');
      WinError(CryptGetHashParam(hHash, HP_HASHSIZE, @len, cb, 0), 'CryptGetHashParam(HP_HASHSIZE)');
      SetLength(Result, len);
      WinError(CryptGetHashParam(hHash, HP_HASHVAL, @Result[0], len, 0), 'CryptGetHashParam(HP_HASHVAL)');
    finally
      WinError(CryptDestroyHash(hHash), 'CryptDestroyHash');
    end;
  end;

var
  hProv: HCRYPTPROV;
  i_key_pad, o_key_pad: TBytes;
  data: TBytes;
  emptyArray: TBytes;
  len, i: Integer;
  c: Byte;
  ifree: Boolean;
begin
  ifree:=False;
  SetLength(Result, 0);
  SetLength(emptyArray, 0);
  SetLength(i_key_pad, BLOCK_SIZE);
  SetLength(o_key_pad, BLOCK_SIZE);
  WinError(CryptAcquireContext(hProv, Nil, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT), 'CryptAcquireContext');
  try
    data:=AKey;
    len:=Length(data);
    if len > BLOCK_SIZE then begin
      data:=hash(hProv, data, emptyArray);
      len:=Length(data);
      ifree:=True;
    end;
    //
    i:=BLOCK_SIZE-1;
    while i >= 0 do begin
      c:=0;
      if i < len then
        c:=data[i];
      i_key_pad[i]:= xor c;
      o_key_pad[i]:=c xor c;
      Dec(i);
    end;
    if ifree then
      SetLength(data, 0);
    data:=hash(hProv, i_key_pad, AMessage);
    Result:=hash(hProv, o_key_pad, data);
    SetLength(data, 0);
  finally
    SetLength(i_key_pad, 0);
    SetLength(o_key_pad, 0);
    WinError(CryptReleaseContext(hProv, 0), 'CryptReleaseContext');
  end;
end;

function TBytesToHex(const Value: TBytes): String;
const
  dictionary: Array[0..15] of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
var
  i: Integer;
begin
  Result:='';
  for i:=0 to High(Value) do
    Result:=Result + dictionary[Value[i] shr 4] + dictionary[Value[i] and [=10=]F];
end;

// source: 
function MBCSString(const s: UnicodeString; CodePage: Word): RawByteString;
var
  enc: TEncoding;
  bytes: TBytes;
begin
  enc:=TEncoding.GetEncoding(CodePage);
  try
    bytes:=enc.GetBytes(s);
    SetLength(Result, Length(bytes));
    Move(Pointer(bytes)^, Pointer(Result)^, Length(bytes));
    SetCodePage(Result, CodePage, False);
  finally
    enc.Free;
  end;
end;

function UnicodeStringToTBytes(const Value: String): TBytes;
var
  ansi: AnsiString;
begin
  ansi:=MBCSString(Value, 65001); // Unicode (UTF-8) codepage
  Result:=BytesOf(ansi);
  ansi:='';
end;

function CryptoAPI_Hash_HmacSHA512(const InString, Password: TBytes): TBytes;
begin
  SetLength(Result, 0);
  if Length(Password) = 0 then
    raise EHMACError.Create('Error: Password length must be greater then 0!');

  Result:=hmac(Password, InString, CALG_SHA_512);
end;

function CryptoAPI_Hash_HmacSHA512(const InString, Password: String): String;
var
  input_bytes, input_password: TBytes;
begin
  input_bytes:=UnicodeStringToTBytes(InString);
  input_password:=UnicodeStringToTBytes(Password);
  try
    Result:=TBytesToHex(CryptoAPI_Hash_HmacSHA512(input_bytes, input_password));
  finally
    SetLength(input_password, 0);
    SetLength(input_bytes, 0);
  end;
end;

end.