Search code examples
delphidelphi-xe2indyhmacsha1

Indy HMAC-SHA1 generating unexpected values


I'm attempting to use a 3rd party service that uses OAuth 1.0a and requires HMAC-SHA1 for the signature. I wrote a working version in C# and went to try to move it over to Delphi XE2. I immediately noticed something was wrong, the server was rejecting my calls saying that my "Signature is invalid." This is my method for generating a signature:

function TOAuth1.GenerateSignature(signatureBase, key : string) : string;
var
  hmacSha1 : TIdHMACSHA1;
  keyBytes, textBytes, hashedBytes : TBytes;
begin
  if(AnsiCompareText(fSignMethod,'PLAINTEXT') = 0) then
  begin
    Result := key;
  end
  else if(AnsiCompareText(fSignMethod,'HMAC-SHA1') = 0) then
  begin
    hmacSha1 := TIdHMACSHA1.Create;
    SetLength(keyBytes,Length(key));
    Move(key[1],keyBytes[0],Length(key));
    hmacSha1.Key := keyBytes;
    SetLength(textBytes,Length(signatureBase));
    Move(signatureBase[1],textBytes[0],Length(signatureBase));
    hashedBytes := hmacSha1.HashValue(textBytes);
    Result := EncodeBase64(hashedBytes,Length(hashedBytes));
    keyBytes := nil;
    textBytes := nil;
    hashedBytes := nil;

    hmacSha1.Free;
    hmacSha1 := nil;
  end;
end;

I couldn't spot anything that looked wrong to me, so I grabbed a signatureBase and key from my C# tests

signatureBase: POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Flocalhost%252Fsign-in-with-twitter%252F%26oauth_consumer_key%3DcChZNFj6T5R0TigYB9yd1w%26oauth_nonce%3Dea9ec8429b68d6b77cd5600adbbb0456%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318467427%26oauth_version%3D1.0

key: L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg&

Using these values in C#, I was given the signature F1Li3tvehgcraF8DMJ7OyxO4w9Y=, which was the expected value that Twitter gave me. However, in Delphi, I was given the signature /kov410nJhE6PTlk0R8bjP7JQq4=

In Delphi I called my function like this:

  signature := self.GenerateSignature('POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Flocalhost%252Fsign-in-with-twitter%252F%26oauth_consumer_key%3DcChZNFj6T5R0TigYB9yd1w%26oauth_nonce%3Dea9ec8429b68d6b77cd5600adbbb0456%26oauth_signature_method'+'%3DHMAC-SHA1%26oauth_timestamp%3D1318467427%26oauth_version%3D1.0','L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg&');
  assert(AnsiCompareStr(signature,'F1Li3tvehgcraF8DMJ7OyxO4w9Y=') = 0,'Signature generated is invalid');

So my question would be this: Am I using the HMAC-SHA1 incorrectly? If so, what steps should I take to fix it? If not, then is the implementation of HMAC-SHA1 in Indy done incorrectly? If so, is there an easy to use (preferably free) unit that can handle it correctly? Or is there something else altogether wrong here?


Solution

  • Looks like the common Unicode string misconception. Since Delphi 2009, string maps to UnicodeString, encoded with UTF-16, and no longer to AnsiString. So,

    Move(key[1],keyBytes[0],Length(key));
    

    will probably only copy the first half of the string with 2 bytes per character. Use UTF8Encode to convert the key to UTF8 first, and copy that into keyBytes.