CryptoAPI returns incorrect result for HMAC_SHA1

血红的双手。 提交于 2019-12-22 11:07:45

问题


I'm using the code below with the Crypto API and I'm not getting the results I would expect based on testing with other API's and libraries.

I'm using the key, "key" and the data is "message"

For example, using Indy's TidHMACSHA1, I get 2088df74d5f2146b48146caf4965377e9d0be3a4

I get the same result using online generators (such as http://www.freeformatter.com/hmac-generator.html, for example).

With the code I've written (see below) I get 4a52c3c0abc0a06049d1ab648bb4057e3ff5f359

The code is below, I'm using the JEDI wcrypt2.pas header

function Hashhmacsha1(const Key, Value: AnsiString): AnsiString;
var
  hCryptProvider: HCRYPTPROV;
  hHash: HCRYPTHASH;
  hKey: HCRYPTKEY;
  bHash: array[0..$7F] of Byte;
  dwHashLen: dWord;
  i: Integer;


  hHmacHash: HCRYPTHASH;
  bHmacHash: array[0..$7F] of Byte;
  dwHmacHashLen: dWord;
  hmac_info : Wcrypt2.HMAC_INFO;
begin
  dwHashLen := 32;
  dwHmacHashLen := 32;
  {get context for crypt default provider}
  if CryptAcquireContext(@hCryptProvider, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT or CRYPT_MACHINE_KEYSET) then
  begin
    {create hash-object }
    if CryptCreateHash(hCryptProvider, CALG_SHA1, 0, 0, @hHash) then
    begin

      {get hash from password}
      if CryptHashData(hHash, @Key[1], Length(Key), 0) then
      begin

        // hHash is now a hash of the provided key, (SHA1)
        // Now we derive a key for it
        if CryptDeriveKey(hCryptProvider, CALG_RC4, hHash, 0, @hKey) then
        begin

          //hkey now holds our key. So we have do the whole thing over again
          //ZeroMemory( hmac_info, SizeOf(hmac_info) );
          hmac_info.HashAlgid := CALG_SHA1;
          if CryptCreateHash(hCryptProvider, CALG_HMAC, hKey, 0, @hHmacHash) then
          begin

            {get hash from password}

              if CryptSetHashParam( hHmacHash, HP_HMAC_INFO, @hmac_info, 0) then
              begin

                if CryptHashData(hHmacHash, @Value[1], Length(Value), 0) then
                begin
                  if CryptGetHashParam(hHmacHash, HP_HASHVAL, @bHmacHash[0], @dwHmacHashLen, 0) then
                  begin
                    for i := 0 to dwHmacHashLen-1 do
                      Result := Result + IntToHex(bHmacHash[i], 2);
                  end
                  else
                   WriteLn( 'CryptGetHashParam ERROR --> ' + SysErrorMessage(GetLastError)) ;
                end
                else
                  WriteLn( 'CryptHashData ERROR --> ' + SysErrorMessage(GetLastError)) ;
                {destroy hash-object}
                CryptDestroyHash(hHmacHash);
                CryptDestroyKey(hKey);
              end
              else
                WriteLn( 'CryptSetHashParam ERROR --> ' + SysErrorMessage(GetLastError)) ;

          end
          else
            WriteLn( 'CryptCreateHash ERROR --> ' + SysErrorMessage(GetLastError)) ;
        end
        else
          WriteLn( 'CryptDeriveKey ERROR --> ' + SysErrorMessage(GetLastError)) ;

      end;
      {destroy hash-object}
      CryptDestroyHash(hHash);
    end;
    {release the context for crypt default provider}
    CryptReleaseContext(hCryptProvider, 0);
  end;
  Result := AnsiLowerCase(Result);
end;

I'm obviously doing something incorrectly, but I'm no idea what ??


回答1:


So I found a solution, which when generating an HMAC_SHA1 for the data "message" with the key "key" generates the expected hash of 2088df74d5f2146b48146caf4965377e9d0be3a4

As you can see, this code uses CryptImportKey instead of CryptDeriveKey, which seems to resolve the issue. It seems that using CryptDeriveKey is actually generating an HMAC_SHA1 hash using the data "message" and the SHA1 Hash of the key "key" encoded as RC4 instead of the plaintext key as initially thought.

The code works for keys upto 16 characters in length, any larger and only uses the first 16 characters anyway. I'm posting a second quesiton to enquire about that!!

Code is posted below.

function Hashhmacsha1(const Key, Value: AnsiString): AnsiString;
const
  KEY_LEN_MAX = 16;
var
  hCryptProvider: HCRYPTPROV;
  hHash: HCRYPTHASH;
  hKey: HCRYPTKEY;
  bHash: array[0..$7F] of Byte;
  dwHashLen: dWord;
  i: Integer;

  hPubKey : HCRYPTKey;
  hHmacHash: HCRYPTHASH;
  bHmacHash: array[0..$7F] of Byte;
  dwHmacHashLen: dWord;
  hmac_info : Wcrypt2.HMAC_INFO;

  keyBlob: record
    keyHeader: BLOBHEADER;
    keySize: DWORD;
    keyData: array[0..KEY_LEN_MAX-1] of Byte;
  end;
  keyLen : INTEGER;
begin
  dwHashLen := 32;
  dwHmacHashLen := 32;
  {get context for crypt default provider}
  if CryptAcquireContext(@hCryptProvider, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) then
  begin
    {create hash-object MD5}
    if CryptCreateHash(hCryptProvider, CALG_SHA1, 0, 0, @hHash) then
    begin

      {get hash from password}
      if CryptHashData(hHash, PByte(Key), Length(Key), 0) then
      begin

        // hHash is now a hash of the provided key, (SHA1)
        // Now we derive a key for it
        hPubKey := 0;

        FillChar(keyBlob, SizeOf(keyBlob), 0);
        keyBlob.keyHeader.bType := PLAINTEXTKEYBLOB;
        keyBlob.keyHeader.bVersion := CUR_BLOB_VERSION;
        keyBlob.keyHeader.aiKeyAlg := CALG_RC4;
        KeyBlob.keySize := KEY_LEN_MAX;

        if(Length(key) < (KEY_LEN_MAX))then
          KeyLen := Length(key)
        else
          KeyLen := KEY_LEN_MAX;
        Move(Key[1], KeyBlob.keyData[0], KeyLen );

        if CryptImportKey(hCryptProvider, @keyBlob, SizeOf(KeyBlob), hPubKey, 0, @hKey) then
        begin

          //hkey now holds our key. So we have do the whole thing over again
          ZeroMemory( @hmac_info, SizeOf(hmac_info) );
          hmac_info.HashAlgid := CALG_SHA1;
          if CryptCreateHash(hCryptProvider, CALG_HMAC, hKey, 0, @hHmacHash) then
          begin
              if CryptSetHashParam( hHmacHash, HP_HMAC_INFO, @hmac_info, 0) then
              begin

                if CryptHashData(hHmacHash, @Value[1], Length(Value), 0) then
                begin
                  if CryptGetHashParam(hHmacHash, HP_HASHVAL, @bHmacHash[0], @dwHmacHashLen, 0) then
                  begin
                    for i := 0 to dwHmacHashLen-1 do
                      Result := Result + IntToHex(bHmacHash[i], 2);
                  end
                  else
                   WriteLn( 'CryptGetHashParam ERROR --> ' + SysErrorMessage(GetLastError)) ;
                end
                else
                  WriteLn( 'CryptHashData ERROR --> ' + SysErrorMessage(GetLastError)) ;
                {destroy hash-object}
                CryptDestroyHash(hHmacHash);
                CryptDestroyKey(hKey);
              end
              else
                WriteLn( 'CryptSetHashParam ERROR --> ' + SysErrorMessage(GetLastError)) ;

          end
          else
            WriteLn( 'CryptCreateHash ERROR --> ' + SysErrorMessage(GetLastError)) ;
        end
        else
          WriteLn( 'CryptDeriveKey ERROR --> ' + SysErrorMessage(GetLastError)) ;

      end;
      {destroy hash-object}
      CryptDestroyHash(hHash);
    end;
    {release the context for crypt default provider}
    CryptReleaseContext(hCryptProvider, 0);
  end;
  Result := AnsiLowerCase(Result);
end;



回答2:


I was able to get the correct result for keys longer than 16 bytes by using OpenSLL. Instead of about 10 Win32 Crypt calls, it was done in three: init, HMAC, and cleanup.




回答3:


I do not work with your functions, so correct me if I am wrong: I do not see where you compute HMACSHA1('message', 'key').

After CryptCreateHash(hCryptProvider, CALG_HMAC, hKey, 0, @hHmacHash) I guess you are computing HMACSHA1('message', hkey), where the derived key hkey is somehow computed with RC4.

BTW: there is a misleading comment related to MD5 (artefact from old version?)



来源:https://stackoverflow.com/questions/25436416/cryptoapi-returns-incorrect-result-for-hmac-sha1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!