【问题标题】:CryptoAPI returns incorrect result for HMAC_SHA1CryptoAPI 为 HMAC_SHA1 返回不正确的结果
【发布时间】:2014-08-21 21:41:21
【问题描述】:

我将下面的代码与 Crypto API 一起使用,但我没有得到基于对其他 API 和库的测试所期望的结果。

我正在使用密钥“key”,数据为“message

例如,使用 Indy 的 TidHMACSHA1,我得到 2088df74d5f2146b48146caf4965377e9d0be3a4

我使用在线生成器(例如http://www.freeformatter.com/hmac-generator.html)得到了相同的结果。

使用我编写的代码(见下文),我得到 4a52c3c0abc0a06049d1ab648bb4057e3ff5f359

代码如下,我使用的是 JEDI wcrypt2.pas 标头

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;

我显然做错了什么,但我不知道是什么??

【问题讨论】:

  • 看到这样的问题,我首先想到的是“因为Unicode问题”。但看起来你正在使用 AnsiStrings,所以这实际上并不是问题的根源......
  • 是的,我考虑到了这一点,这就是我使用 AnsiStrings 的原因——所以我有一个比较有效的比较。好像没用!!
  • 我不确定这是否会有所帮助,但所有散列算法都适用于二进制数据。在您的情况下,您为它提供了一个以空字符结尾的字符串(Delphi 字符串是以空字符结尾的字符串)。这可能意味着它在计算哈希时也会在字符串末尾使用那个 nul 字符。因此,您可能想使用 ansi 字符数组作为输入值,而不是默认的 Delphi ansi 字符串。
  • @SilverWarior 感谢您的建议,但我已经发现这不是问题所在。我正在发布一个有效的答案,有点过......
  • 您应该停止将字符串缓冲区视为二进制。将 Unicode 字符串转换为 utf8 字节并对其进行哈希处理。

标签: delphi delphi-xe5 cryptoapi hmacsha1


【解决方案1】:

所以我找到了一个解决方案,当使用密钥“key”为数据“消息”生成 HMAC_SHA1 时,会生成预期的哈希值 2088df74d5f2146b48146caf4965377e9d0be3a4

如您所见,此代码使用CryptImportKey 而不是CryptDeriveKey,这似乎可以解决问题。似乎使用 CryptDeriveKey 实际上是使用数据“消息”和编码为 RC4 的密钥“密钥”的 SHA1 哈希而不是最初认为的明文密钥生成 HMAC_SHA1 哈希。

该代码适用于长度不超过 16 个字符的键,任何更大的字符都只使用前 16 个字符。我正在发布第二个问题来询问!

代码贴在下面。

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;

【讨论】:

  • 感谢分享这个@DFriend!你能找到大于 16 个字符的解决方案吗?
  • 我找不到您关于 >16 个字符的第二个问题,请您提供该链接。非常感谢您分享如此详细的答案!
  • 最后我没有发布新问题,因为 16 个字符满足了我有限的需求(抱歉)。
  • 我确实得到了一些 openssl 代码来处理另一个项目的任何大小的密钥。如果有帮助,我可以发布它,但我会出去玩几个小时。
  • 谢谢@dfriend 那太棒了!我实际上也尝试过openssl,但一直试图让visual studio识别它,如果你有时间可以帮助我,如果你有时间的话会很棒! - stackoverflow.com/questions/41357374/visual-studio-2015-openssl
【解决方案2】:

通过使用 OpenSLL,我能够为长度超过 16 个字节的键获得正确的结果。它不是大约 10 个 Win32 Crypt 调用,而是分三个完成:init、HMAC 和 cleanup。

【讨论】:

  • 你能提供一些示例代码吗?即使是伪代码也可以。非常感谢。
  • 是的,请您提供一些示例代码。胜利的东西让我发疯了。
【解决方案3】:

我不使用你的函数,如果我错了,请纠正我:我看不到你在哪里计算 HMACSHA1('message','key')。

CryptCreateHash(hCryptProvider, CALG_HMAC, hKey, 0, @hHmacHash) 之后,我猜你正在计算 HMACSHA1('message', hkey),其中派生密钥 hkey 是用 RC4 以某种方式计算的。

顺便说一句:有一个与 MD5 相关的误导性评论(旧版本的人工制品?)

【讨论】:

  • 嗨,上面的代码示例只是我调用来执行实际拥有的函数。我将其称为如下 WriteLn( Format('HMACSHA1 of "message" using "key" = [%s]', [Hashhmacsha1( 'key', msg)] ) );误导性评论确实来自以前的版本,我将在上面的代码示例中修复它 - 感谢您指出!!
  • 再次:你确定CryptHashData(hHmacHash, @Value[1], Length(Value), 0) 真的使用'key' 而not hkey 来计算HMAC?我猜它使用 hkey。
  • 是的,它使用 hKey 来计算哈希 - hKey 包含对 CryptDeriveKey 调用生成的密钥的句柄。我不确定我是否遵循您的观点,所以请您扩展您所说的内容吗?代码示例基于 MSDN HMAC 代码示例,link 似乎部分问题是密钥在使用之前是 RC4 编码的,我在其他实现中看不到这样做。
  • 这就是我的假设。给定的测试向量(以及更多通过其他实现确认)使用纯字符串“key”。您必须找到一种方法直接使用“密钥”或字节数组($6B、$65、$79)作为 HMAC 密钥,否则结果将不匹配。如何做到这一点,我不能告诉你,因为我不使用这些 MS 功能。
  • 我设法找到了一种使用纯文本生成 hKey 句柄的替代方法,但它将密钥的大小限制为 16 个字节。然而,我能找到的所有其他实现都允许更长的键......想法?
猜你喜欢
  • 2012-04-11
  • 2016-02-06
  • 2017-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-28
  • 2014-11-03
相关资源
最近更新 更多