Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi - Can't get HMAC-SHA256 to pass RFC 4231 test vectors

I need to access Amazon REST services like a previous question at " HMAC-SHA256 in Delphi " asked. Since this has to be in D2010 I'm trying to use the latest libeay32.dll to pass the test vectors in RFC 4231:

https://www.rfc-editor.org/rfc/rfc4231

Does anyone have a method that passes these tests in Delphi using this library? The code posted by shunty in the post I referred to passes the first two test vectors as well as the fifth, but it fails in the third and fourth. Those vectors are over 64 bytes and since all of the url's that I need to sign for Amazon are over 64 bytes this is a problem. I haven't been able to figure out if I'm doing something wrong. The OpenSSL test is in hmactest.c, but it only checks EVP_md5 and the test vectors aren't all the same as in the RFC. I need this to work with SHA256 so I can verify against the test vectors in the RFC. I'm using the following constants for the tests (constants now updated for future viewers to fix my copy and paste errors mentioned in the comments below):

const
  LIBEAY_DLL_NAME = 'libeay32.dll';
  EVP_MAX_MD_SIZE = 64;

  //RFC 4231 Test case 1
  TEST1_KEY: string = '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b';
  TEST1_DATA: string = '4869205468657265';
  TEST1_DIGEST: string = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7';

  //RFC 4231 Test case 2
  TEST2_KEY = '4a656665';
  TEST2_DATA = '7768617420646f2079612077616e7420666f72206e6f7468696e673f';
  TEST2_DIGEST = '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843';

  //RFC 4231 Test case 3
  TEST3_KEY = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
  TEST3_DATA = 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd';
  TEST3_DIGEST = '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe';

  //RFC 4231 Test case 4
  TEST4_KEY = '0102030405060708090a0b0c0d0e0f10111213141516171819';
  TEST4_DATA = 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd';
  TEST4_DIGEST = '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b';

  //RFC 4231 Test case 5
  TEST5_KEY = '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c';
  TEST5_DATA = '546573742057697468205472756e636174696f6e';
  TEST5_DIGEST = 'a3b6167473100ee06e0c796c2955552b';

I don't know how this code by shunty will look pasted in because it looks terrible here (I'm a stackoverflow novice). I used RAND_seed rather than RAND_load_file like he did, but otherwise it's the same:

function TForm1.GetHMAC(const AKey, AData: string): TBytes;
var
  key, data: TBytes;
  md_len: integer;
  res: PByte;
  buf: PInteger;
  rand_val: Integer;
begin
  OpenSSL_add_all_algorithms;

  Randomize;
  rand_val := Random(100);
  GetMem(buf, rand_val);
  try
    RAND_seed(buf, rand_val);

    key := TEncoding.UTF8.GetBytes(AKey);
    data := TEncoding.UTF8.GetBytes(AData);
    md_len := EVP_MAX_MD_SIZE;
    SetLength(Result, md_len);
    res := HMAC(EVP_sha256, @key[0], Length(key), @data[0], Length(data), @result[0], md_len);
    if (res <> nil) then
      SetLength(Result, md_len);

  finally
    FreeMem(buf);
  end;
end;

The code I use to test looks like this. This particular method is for test 3 which fails. The result is bb861233f283aef2ef7aea09785245c9f3c62720c9d04e0c232789f27a586e44, but it should be equal to the constant hex value for TEST3_DIGEST:

procedure TForm1.btnTestCase3Click(Sender: TObject);
var
  LBytesDigest: TBytes;
  LHashString: string;
  LHexDigest: string;
begin
  LBytesDigest := GetHMAC(HexToStr(TEST3_KEY), HexToStr(TEST3_DATA));

  LHexDigest := LowerCase(BytesToHex(LBytesDigest));

  if LHexDigest = TEST3_DIGEST then begin
    Memo1.Lines.Add('SUCCESS: Matches test case');
    Memo1.Lines.Add(LHexDigest);
  end else begin
    Memo1.Lines.Add('ERROR: Does not match test case');
    Memo1.Lines.Add('Result:    ' + LHexDigest);
    Memo1.Lines.Add('Test Case: ' + TEST3_DIGEST);
  end;
end;

Any ideas? I'm about to give up and just create a .NET app using the library they provide...

like image 362
Ron Grove Avatar asked Apr 03 '12 00:04

Ron Grove


1 Answers

You are using D2009+ (as evident by your use of TEncoding), which mean you are dealing with UnicodeString, but you are not taking Unicode into account in your logic. The RFC does not operate on characters, it operates on bytes. Your test data contains hex-encoded strings. When you decode them into (Unicode)String values, many of the resulting characters are outside of the ASCII range of characters, which means they have to be interpretted by Ansi codepages before you can convert them to UTF-8 correctly (which you should not be using in this situation anyway).

You need to change your implementation to decode your hex strings straight to TBytes instead (you can use Classes.HexToBin() for that) so the correct byte values are preserved and passed to HMAC, and get rid of TEncoding.UTF8.GetBytes() completely:

function TForm1.GetHMAC(const AKey, AData: TBytes): TBytes; 
var 
  md_len: integer; 
  res: PByte; 
  buf: PInteger; 
  rand_val: Integer; 
begin 
  OpenSSL_add_all_algorithms; 

  Randomize; 
  rand_val := Random(100); 
  GetMem(buf, rand_val); 
  try 
    RAND_seed(buf, rand_val); 
    md_len := EVP_MAX_MD_SIZE; 
    SetLength(Result, md_len); 
    res := HMAC(EVP_sha256, Pointer(AKey), Length(AKey), Pointer(AData), Length(AData), @Result[0], md_len); 
    if (res <> nil) then 
      SetLength(Result, md_len); 
  finally 
    FreeMem(buf); 
  end; 
end; 

function HexToBytes(const S: String): TBytes;
begin
  SetLength(Result, Length(S) div 2);
  SetLength(Result, HexToBin(PChar(S), Pointer(Result), Length(Result)));
en;

procedure TForm1.btnTestCase3Click(Sender: TObject);  
var  
  LBytesDigest: TBytes;  
  LHashString: string;  
  LHexDigest: string;  
begin  
  LBytesDigest := GetHMAC(HexToBytes(TEST3_KEY), HexToBytes(TEST3_DATA));  
  LHexDigest := LowerCase(BytesToHex(LBytesDigest));  
  if LHexDigest = TEST3_DIGEST then begin  
    Memo1.Lines.Add('SUCCESS: Matches test case');  
    Memo1.Lines.Add(LHexDigest);  
  end else begin  
    Memo1.Lines.Add('ERROR: Does not match test case');  
    Memo1.Lines.Add('Result:    ' + LHexDigest);  
    Memo1.Lines.Add('Test Case: ' + TEST3_DIGEST);  
  end;  
end;  
like image 60
Remy Lebeau Avatar answered Sep 30 '22 16:09

Remy Lebeau