Does anybody know how to calculate a HMAC-SHA512 hash in Delphi 2010+ using MS CryptoAPI ?
The example from MS website, https://learn.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--creating-an-hmac generates incorrect results.
I have found this answer https://stackoverflow.com/a/41387095/2111514 to be somehow usefull (because it is manual rewrite from https://en.wikipedia.org/wiki/HMAC), but it is not in Pascal and my attempt to refactor it to Pascal was without luck. It works, but still calculates wrong results.
Can anybody help me, please?
Edit:: This is my code that I have problem with:
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 $0F];
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]:=$36 xor c;
o_key_pad[i]:=$5c 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;
...and it is called by:
Result:=hmac(Password, InString, CALG_SHA_512);
Example:
TBytesToHex(hmac('pass', 'test', CALG_SHA_512));
produces (HEX encoded)
1319bb7baefc3fbaf07824261c240cecd04a54cd83cdf0deb68e56cadff20e7c644e2e956660ab9df47a19502173090df5ec3d0b9236d59917afc4f3607cf980
whereas online HMAC calculator produces
46beca277a5fec10beba65b0c2fb3917115f352eb8b2560e9ada0a3dbafb6c7a3fc456b1e13a07c4a9c856b633b70b2403907ca89894021772393e3f97e78684
for the same input