9

I need to generate HMAC-SHA256 signatures for the Amazon web services API. The old DCPcrypt library has sha256 routines but does not do HMAC signing. Anyone know of a free hashing library I could use?

Jacob
  • 7,741
  • 4
  • 30
  • 24

9 Answers9

7

Delphi ships with Indy installed, and Indy has a TIdHMACSHA256 class:

uses
  IdGlobal, IdHashSHA, IdHMAC, IdHMACSHA1, IdSSLOpenSSL;

function CalculateHMACSHA256(const value, salt: String): String;
var
  hmac: TIdHMACSHA256;
  hash: TIdBytes;
begin
  LoadOpenSSLLibrary;
  if not TIdHashSHA256.IsAvailable then
    raise Exception.Create('SHA256 hashing is not available!');
  hmac := TIdHMACSHA256.Create;
  try
    hmac.Key := IndyTextEncoding_UTF8.GetBytes(salt);
    hash := hmac.HashValue(IndyTextEncoding_UTF8.GetBytes(value));
    Result := ToHex(hash);
  finally
    hmac.Free;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • @MillieSmith yes, fixed – Remy Lebeau May 25 '18 at 14:21
  • Be sure to call "IdOpenSSLSetLibPath(yourlibeaypathhere);" as well, as loadOpenSSLLibrary may not be able to find the required dll otherwise. – T.S Dec 11 '19 at 12:33
  • @T.S that is only necessary if the OpenSSL binaries are not in the same folder as your app executable – Remy Lebeau Dec 11 '19 at 13:07
  • Indeed, though on should not assume it is :) – T.S Dec 11 '19 at 13:42
  • @T.S yet, it does, because that is way most OSes handle loading of external binaries via relative paths. The system defined search path checks the folder of the executable of the calling process first. Using `IdOpenSSLSetLibPath()` changes the loading to use absolute paths instead of relative paths – Remy Lebeau Dec 11 '19 at 15:23
  • One may always add a parameter, check if it's set, then call the function - at least it's important to note that the option is there for those who need it. – T.S Dec 12 '19 at 11:29
  • I'm finding it is giving different results from: var result_obj = CryptoJS.HmacSHA256(message_to_encode, salt);, which is unexpected however. – T.S Dec 20 '19 at 10:37
  • @T.S typically, when two hashing libraries use the same hash algorithm but produce different hashes for the same string inputs, they are likely using different byte encodings for the strings. Hashes operate on bytes, not characters, so strings have to be encoded first. My example above uses UTF-8 for that. Maybe CrypyoJS is using something else – Remy Lebeau Dec 20 '19 at 16:08
  • Hey i know its been a while since this was active but what do i do if i get : '#$D#$A' in between my result (base64 string of hash). So the string has 4 " ' " and idk what to do.like i Looked it up and its a new line thing but my string will be invalid if i use it as it is. Thanks for help in advance. – BrianOrion Nov 03 '22 at 07:05
  • @BrianOrion simply use a base64 encoder that doesn't break up long strings into multiple lines. Embarcadero's does (IIRC). Indy's doesn't. – Remy Lebeau Nov 03 '22 at 07:14
  • @RemyLebeau But i get my hash like this: THashSHA2.GetHMACAsBytes(data, key, SHA512). So i cant use TIdEncoderMIME.EncodeBytes since its not TIdBytes. Or can i convert it without loosing data ? – BrianOrion Nov 03 '22 at 07:24
  • 1
    @BrianOrion `GetHMACAsBytes()` returns a `TBytes`, which is an alias for `array of Byte`. `TIdBytes` is also an alias for `array of Byte`. The two types are [distinct and not *assignment* compatible](https://docwiki.embarcadero.com/RADStudio/en/Type_Compatibility_and_Identity_(Delphi)), but they are identical in memory layout, so you can *typecast* between them before assigning. There will be no data loss. – Remy Lebeau Nov 03 '22 at 14:42
4

After a little more searching I found OpenStreamSec - which looks like it was abandoned a few years ago but still compiles in D2007.

http://sourceforge.net/projects/openstrsecii/

Generating a HMAC-256 for Amazon is really simple:

StrToMime64(HMACString(haSHA256, SecretKey, 32, DataToHash));
Jacob
  • 7,741
  • 4
  • 30
  • 24
  • I got this work, but find I need to truncate some characters to get a proper signature. For example, here is a signature I get: "t1mYmkG73To5eyukq1lbDrBYGPcAAAAAAAAAAAAAAAA=". But it is actually that without the "A": t1mYmkG73To5eyukq1lbDrBYGPc=. Is there a setting I need to change? If I change the "32" parameter that does not seem to change things. – M Schenkel Apr 06 '12 at 02:05
3

My favourite answer - I would use the OpenSSL libraries, the HMAC function. I've successfully used the OpenSSL libraries in Delphi by adopting and adapting work from M Ferrante http://www.disi.unige.it/person/FerranteM/delphiopenssl/
For other OpenSSL signing etc see this link
In D2010 it's something like this (libeay32 is the unit taken from the web site and slightly modified for unicode/D2010):

uses libeay32;

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

function EVP_sha256: pEVP_MD; cdecl; external LIBEAY_DLL_NAME;
function HMAC(evp: pEVP_MD; key: PByte; key_len: integer; 
              data: PByte; data_len: integer; 
              md: PByte; var md_len: integer): PByte; cdecl; external LIBEAY_DLL_NAME;

function GetHMAC(const AKey, AData: string): TBytes;
var
  key, data: TBytes;
  md_len: integer;
  res: PByte;
begin
  OpenSSL_add_all_algorithms;
  // Seed the pseudo-random number generator
  // This should be something a little more "random"!
  RAND_load_file('c:\windows\paint.exe', 512);

  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
  begin
    SetLength(result, md_len);
  end;
end;

Then call it with a key phrase and data string. The result is a TBytes which can be converted as required eg to Base64 using something like JclMime or a simple HexToString type function.
For older version of Delphi you'll have to do a bit of changing of PBytes to PChars or something similar.
Disclaimer: I've got no reference data to test this on but it seems to work ok!

Community
  • 1
  • 1
shunty
  • 3,699
  • 1
  • 22
  • 27
2

Have you looked at the answers to this SO question?

Community
  • 1
  • 1
Francesca
  • 21,452
  • 4
  • 49
  • 90
2

HMAC is just a function that uses SHA256 to calculate a hash according to some defined rules. If you look at Wikipedia it has a pseudocode example.

You could also call into .NET's HMAC Class in System.Security.Cryptography via COM interrop.

Jim McKeeth
  • 38,225
  • 23
  • 120
  • 194
0

When searching the web I also found this, compiling correctly under Delphi10:

result:= (TNetEncoding.Base64.EncodeBytesToString(THashSHA2.GetHMACAsBytes(TEncoding.UTF8.GetBytes(StringToSign),TNetEncoding.Base64.DecodeStringToBytes(Key))));

Though I do have troubles in the ongoing process - not sure if it is related to this function..

Wolfgang Bures
  • 509
  • 4
  • 12
0

The GetHMAC function is also available in class THashSHA2, in module System.Hash.

I am not sure from which version of Delphi this is available. It is definitely in Delphi 10 Seattle and later.

0

Regarding the answer from Jacob: OpenStrSecII is a branch of StreamSec Tools 2.1, which is sold under a commercial no-nonsense license and today (Feb 8, 2012) has support for Delphi Win32 up to and including Delphi XE2. StreamSec Tools 4.0 has support for Win64 as well.

Henrick Hellström
  • 2,556
  • 16
  • 18
-1

Everything is much simpler with the latest versions of Delphi...

uses system.hash;
 
var s, sst:string;
bt:TBytes;

HMAC 256:

s:=THashSHA2.GetHMAC(DATA, KEY, SHA256);

base64 HMAC 256:

bt:= THashSHA2.GetHMACAsBytes(DATA, KEY, SHA256);
sst:= TNetEncoding.Base64.EncodeBytesToString(bt);
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 14 '22 at 21:42