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...
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With