HMAC SHA256

 
Hey guys! I need to send a signature as HMAC SHA256 to an online service.
I’ve created a class with the help of this and that (https://stackoverflow.com/a/21498946).

It uses CryptEncode(CRYPT_HASH_SHA256,...) to create the hash. You can see it below! It is still very basic without error handling etc.

//+------------------------------------------------------------------+
//|                                                   HMACSHA256.mqh |
//|                                                   Copyright 2018 |
//| https://stackoverflow.com/a/21498946                             |
//| https://en.wikipedia.org/wiki/HMAC                               |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018"
#property link      "https://stackoverflow.com/a/21498946"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Arrays\ArrayChar.mqh>
#include <Arrays\List.mqh>
//+------------------------------------------------------------------+
//| Class CHMACSHA256                                                |
//+------------------------------------------------------------------+
class CHMACSHA256
{
private:
   
   CArrayChar        *m_key;
   CList             *m_pointer;
   CArrayChar        *GetHash(const CArrayChar *in);
   CArrayChar        *ByteConcat(CArrayChar *in1, CArrayChar *in2);
   
public:
                      CHMACSHA256(const uchar &in[]);
                     ~CHMACSHA256(void);
   void               ComputeHash(const uchar &in[], uchar &out[]);
};
//+------------------------------------------------------------------+
//| class constructor                                                |
//+------------------------------------------------------------------+
void CHMACSHA256::CHMACSHA256(const uchar &key[]) : m_key(new CArrayChar),
                                                    m_pointer(new CList)
{
   m_key.AssignArray(key);
}
//+------------------------------------------------------------------+
//| class deconstructor                                              |
//+------------------------------------------------------------------+
void CHMACSHA256::~CHMACSHA256(void)
{
   delete m_key;
   delete m_pointer;
}
//+------------------------------------------------------------------+
//| ComputeHash                                                      |
//+------------------------------------------------------------------+
void CHMACSHA256::ComputeHash(const uchar &message[], uchar &out[])
{
   //Reference http://en.wikipedia.org/wiki/Secure_Hash_Algorithm
   //SHA256 block size is 512 bits => 64 bytes.
   const int HashBlockSize = 64;

   CArrayChar *opad = new CArrayChar();
   CArrayChar *ipad = new CArrayChar();
   CArrayChar *msg  = new CArrayChar();
   
   opad.Resize(HashBlockSize);
   ipad.Resize(HashBlockSize);
   
   msg.AssignArray(message);
   
   if (m_key.Total() > HashBlockSize)
   {
      m_key = GetHash(m_key);
   }
   
   // This condition is independent of previous
   // condition. If previous was true
   // we still need to execute this to make key same length
   // as blocksize with 0 padded if its less than block size
   if (m_key.Total() < HashBlockSize)
   {
      CArrayChar newKey;
      //newKey.Resize(HashBlockSize); // same output with or without
      newKey.AssignArray(m_key);
      //newKey.InsertArray(m_key,0);  // output w/ AssignArray() <=> output w/ InsertArray()
      m_key = newKey;
   }
   
   for (int i=0; i<m_key.Total(); i++)
   {
      opad.Insert((uchar)(m_key.At(i)^0x5C), i);
      ipad.Insert((uchar)(m_key.At(i)^0x36), i);
   }
   
   CArrayChar hash = GetHash(ByteConcat(opad,GetHash(ByteConcat(ipad,msg))));
   
   delete opad;
   delete ipad;
   delete msg;
   m_pointer.Clear();
   
   const int HashSize = hash.Total();
   ArrayResize(out, HashSize);
   for (int i=0; i<HashSize; i++)
      out[i] = hash.At(i);
}
//+------------------------------------------------------------------+
//| GetHash                                                          |
//+------------------------------------------------------------------+
CArrayChar* CHMACSHA256::GetHash(const CArrayChar *dataArray)
{
   uchar data[], key[], result[];
   
   CArrayChar *out = new CArrayChar();
   m_pointer.Add(out);
   
   const int DataSize = dataArray.Total();
   const int KeySize  = m_key.Total();

   ArrayResize(data, DataSize);
   ArrayResize(key, KeySize);
   
   for (int i=0; i<DataSize; i++)
      data[i] = dataArray.At(i);
   for (int i=0; i<KeySize; i++)
      key[i] = m_key.At(i);
   
   CryptEncode(CRYPT_HASH_SHA256, data, key, result);
   out.AssignArray(result);
   
   return(out);
}
//+------------------------------------------------------------------+
//| ByteConcat                                                       |
//+------------------------------------------------------------------+
CArrayChar* CHMACSHA256::ByteConcat(CArrayChar *left, CArrayChar *right)
{
   if (CheckPointer(left)==POINTER_INVALID)
   {
      return(right);
   }
   if (CheckPointer(right)==POINTER_INVALID)
   {
      return(left);
   }
   CArrayChar *out = new CArrayChar();
   m_pointer.Add(out);
   
   //out.Resize(left.Total()+right.Total()); // same output with or without
   out.AssignArray(left);
   //out.InsertArray(left,0);
   out.AddArray(right);
   
   return(out);
}
//+------------------------------------------------------------------+

Usage is like this:

uchar key[], msg[], result[];
CString hash;

const string keystr = "12345";
const string msgstr = "Test";

StringToCharArray(keystr,key,0,StringLen(keystr),CP_UTF8);
StringToCharArray(msgstr,msg,0,StringLen(msgstr),CP_UTF8);

CHMACSHA256 hmac(key);
hmac.ComputeHash(msg, result);

for (int i=0; i<ArraySize(result); i++)
   hash.Append(StringFormat("%02x",result[i]));
   
Print("Hash: "+hash.Str());

Unfortunately the result is different to online HMAC generators like https://www.freeformatter.com/hmac-generator.html or https://www.liavaag.org/English/SHA-Generator/HMAC/

Where is my mistake?

As you can see, I am using the build-in library CArrayChar. The methods AssignArray() and InsertArray() yield different outputs. Maybe I am using them the wrong way? Should I better use native uchar arrays everywhere instead?

Or do I have to call CryptEncode() without key somehow like in this thread (https://stackoverflow.com/a/21498946):

public static byte[] GetHash(byte[] bytes)
{
    using (var hash = new SHA256Managed())
    {
        return hash.ComputeHash(bytes);
    }
}

(No key there)

Or can I not use CryptEncode() then and I have to code the whole encryption logic myself like here? BTW: Amazing work! That would be a nightmare with my somewhat limited coding skills!

Or am I missing something fundamentally? (high probable chance 😉)

I really need your help guys. Thank you so much!
Best regards
HMAC - Wikipedia
HMAC - Wikipedia
  • en.wikipedia.org
In cryptography, an HMAC (sometimes disabbreviated as either keyed-hash message authentication code or hash-based message authentication code) is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key. It may be used to simultaneously verify both the data integrity and the...
 

Bitmex ?

What hash are you expecting for this key/value ?

I got : C86B946CF9FBB4003B3CE68C14BB101F6366886155AF7098FBD4C0F49192270D

 
Alain Verleyen:

Bitmex ?

What hash are you expecting for this key/value ?

I got : C86B946CF9FBB4003B3CE68C14BB101F6366886155AF7098FBD4C0F49192270D

Hey Alain! Yeah, LOL. How did you know? Creepy! :D

I am expecting C86B946CF9FBB4003B3CE68C14BB101F6366886155AF7098FBD4C0F49192270D, as this is what the online generators say.
Instead I get 94dc01f15ba71f29aa6ad5facb2c056050d5f61e1ff276d3f1ec0d71fe08cebf (with AssignArray() in the second if-clause of ComputeHash()) or c60907e990745f7d91c4423713764d2724571269d3db4856d37c6302792c59a6 with InsertArray().

 

When you hash a string that represents a hexadecimal then there is still a difference between uppercase and lowercase so in that case it's usually necessary to first up convert the string to uppercase and then hash it. 

.to_upper(data)

 

I found my mistakes. They were in ComputeHash(). It should be:

if (m_key.Total() < HashBlockSize)
{
   CArrayChar newKey;
   newKey.Resize(HashBlockSize);
   for (int i=0; i<HashBlockSize; i++)
      newKey.Update(i,0); newKey.Insert(0,i);
   newKey.AssignArray(m_key); newKey.InsertArray(m_key,0);
   m_key = newKey;
}

And:

for (int i=0; i<m_key.Total()HashBlockSize; i++)
{
   opad.Insert((uchar)(m_key.At(i)^0x5C), i);
   ipad.Insert((uchar)(m_key.At(i)^0x36), i);
}
 
Domp:

I found my mistakes. They were in ComputeHash(). It should be:

And:

Friend, many thanks, is an excellent solution, congratulations, very good code :) :) :)

Reason: