Generating a SHA1 hash

AitorEGAitorEG Member Posts: 342
Hi everyoe,

I'm trying to connect with a webService, and I must encode a part of the header with SHA1 encoding. I have an example in .NET, and I'm trying to convert it into C/AL, but I'm receiving an error from the webService. The code in .NET is this:
byte[] key = Encoding.UTF8.GetBytes(secret);
byte[] hmac_encode = Sha1Hash_Raw(concat, key);
string hmac_encode_base64 = System.Convert.ToBase64String(hmac_encode);

private static byte[] Sha1Hash_Raw(string input, byte[] key)
{
    HMACSHA1 sha1 = new HMACSHA1(key);
    byte[] byteArray = Encoding.UTF8.GetBytes(input);

    MemoryStream stream = new MemoryStream(byteArray);
    return sha1.ComputeHash(stream);
}

The value I must include into the header of the HTTP request is "hmac_encode_base64"

This is how I'm trying to generate the hash in NAV:
key := Encoding.UTF8.GetBytes(secret);
hmacSHA1 := hmacSHA1.Create;
hmacSHA1.Key  := key;
hmac_encode := hmacSHA1.ComputeHash(Encoding.UTF8.GetBytes(concat));
hmac_encode_base64 := Convert.ToBase64String(hmac_encode);
This are the variables:

secret Text
key DotNet System.Array.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
hmac_encode_base64 Text
hmacSHA1 DotNet System.Security.Cryptography.HMACSHA1.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
concat Text
hmac_encode DotNet System.Array.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

Afer this try, I realizes that "ComputeHash" method in C# was receiving a MemoryStream. So I change my code to this:
key := Encoding.UTF8.GetBytes(secret);
hmacSHA1 := hmacSHA1.Create;
hmacSHA1.Key  := key;
ConcatByte := Encoding.UTF8.GetBytes(concat);
Instr.READ(ConcatByte);
COPYSTREAM(MemoryStream,Instr);
hmac_encode := hmacSHA1.ComputeHash(MemoryStream);
hmac_encode_base64 := Convert.ToBase64String(hmac_encode);

But I'm havieng a "DotNEt type is not compatible" error in the "Instr.READ(ConcatByte);" sentence.

Any hint please?

Answers

  • jurica_bogunovicjurica_bogunovic Member Posts: 1
    There are ready-made functions in D365BC which can be easily copied to NAV 2016 or higher (tested). You may be able to downgrade them further, but I haven't tested that.

    Look for functions GenerateHash/GenerateKeyedHash in codeunit 1266 Encryption Management, I found them useful.

    Note that GenerateKeyedHash works fine when generating just one hash from secret key.

    If you need to loop the hash generation (e.g. as required by AWS), the Bytes value must not be converted to String between the steps.
    To handle this I ended up writing my own function:
        PROCEDURE GenerateKeyedHashLooped@1000000007(DateStamp@1000 : Text;RegionName@1000000000 : Text;ServiceName@1000000001 : Text;Terminator@1000000002 : Text;BodyToSign@1000000007 : Text;Key@1005 : Text;HashAlgorithmType@1001 : 'HMACMD5,HMACSHA1,HMACSHA256,HMACSHA384,HMACSHA512') : Text;
        VAR
          Encoding@1002 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
          DateHashBytes@1000000003 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          RegionHashBytes@1000000004 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          ServiceHashBytes@1000000005 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          TerminatorHashBytes@1000000006 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          FinalHashBytes@1000000008 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
        BEGIN
          GenerateKeyedHashBytes(DateHashBytes,DateStamp,Encoding.UTF8.GetBytes(Key),HashAlgorithmType);
          GenerateKeyedHashBytes(RegionHashBytes,RegionName,DateHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(ServiceHashBytes,ServiceName,RegionHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(TerminatorHashBytes,Terminator,ServiceHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(FinalHashBytes,BodyToSign,TerminatorHashBytes,HashAlgorithmType);
          EXIT(ConvertByteHashToString(FinalHashBytes));
        END;
    
    
  • AitorEGAitorEG Member Posts: 342
    There are ready-made functions in D365BC which can be easily copied to NAV 2016 or higher (tested). You may be able to downgrade them further, but I haven't tested that.

    Look for functions GenerateHash/GenerateKeyedHash in codeunit 1266 Encryption Management, I found them useful.

    Note that GenerateKeyedHash works fine when generating just one hash from secret key.

    If you need to loop the hash generation (e.g. as required by AWS), the Bytes value must not be converted to String between the steps.
    To handle this I ended up writing my own function:
        PROCEDURE GenerateKeyedHashLooped@1000000007(DateStamp@1000 : Text;RegionName@1000000000 : Text;ServiceName@1000000001 : Text;Terminator@1000000002 : Text;BodyToSign@1000000007 : Text;Key@1005 : Text;HashAlgorithmType@1001 : 'HMACMD5,HMACSHA1,HMACSHA256,HMACSHA384,HMACSHA512') : Text;
        VAR
          Encoding@1002 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
          DateHashBytes@1000000003 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          RegionHashBytes@1000000004 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          ServiceHashBytes@1000000005 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          TerminatorHashBytes@1000000006 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
          FinalHashBytes@1000000008 : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
        BEGIN
          GenerateKeyedHashBytes(DateHashBytes,DateStamp,Encoding.UTF8.GetBytes(Key),HashAlgorithmType);
          GenerateKeyedHashBytes(RegionHashBytes,RegionName,DateHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(ServiceHashBytes,ServiceName,RegionHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(TerminatorHashBytes,Terminator,ServiceHashBytes,HashAlgorithmType);
          GenerateKeyedHashBytes(FinalHashBytes,BodyToSign,TerminatorHashBytes,HashAlgorithmType);
          EXIT(ConvertByteHashToString(FinalHashBytes));
        END;
    
    

    First of all, thnaks for your answer jurica.
    I've read that this was easier as you said in BC365, but I didn't understand well. I've copied from the 1266 CU to my CU the methods GenerateKeyedHashBytes and TryGenerateKeyedHash, and I changed my code in this way:
    key := Encoding.UTF8.GetBytes(secret);
     //hmacSHA1 := hmacSHA1.Create;
     //hmacSHA1.Key  := key;
     //hmac_encode := hmacSHA1.ComputeHash(Encoding.UTF8.GetBytes(concat));
     TryGenerateKeyedHash(hmac_encode,concat,key,'HMACSHA1');
     hmac_encode_base64 := Convert.ToBase64String(hmac_encode);
    

    But I get the same value in "hmac_encode_base64". Am I loosing something? Our somethin that I'm using wrong?

    THnak you very much again, really appreciate your help
  • ftorneroftornero Member Posts: 524
    Hello @AitorEG ,

    You can use the codeunit 1266 from NAV2018, there is this function that return the base64 already, you could need the table 1805

    6iwzlhbeov5u.png

    Regards
  • AitorEGAitorEG Member Posts: 342
    ftornero wrote: »
    Hello @AitorEG ,

    You can use the codeunit 1266 from NAV2018, there is this function that return the base64 already, you could need the table 1805

    6iwzlhbeov5u.png

    Regards

    Muchas gracias!
    I've used the function you hace told me, but aniway, the result it's being the same. I'm starting to think that the problem is in other field of the request header, but that is not the answer I'm receiveing from the aplication support...
  • ftorneroftornero Member Posts: 524
    Hello @AitorEG ,

    Well this function return the base64 code from the passed text after the encryption.

    What's "the result being the same" that you said ?

    Regards.

  • AitorEGAitorEG Member Posts: 342
    Hi @ftornero
    As you can see in my frist message, I have an example in C# that I must translate it into C A/L.
    My first try was this:
    key := Encoding.UTF8.GetBytes(secret);
    hmacSHA1 := hmacSHA1.Create;
    hmacSHA1.Key  := key;
    hmac_encode := hmacSHA1.ComputeHash(Encoding.UTF8.GetBytes(concat));
    hmac_encode_base64 := Convert.ToBase64String(hmac_encode);
    

    And after the discover of the 1266 Cu in BC, I'eve done this:
    key := Encoding.UTF8.GetBytes(secret);
     TryGenerateKeyedHash(hmac_encode,concat,key,'HMACSHA1');
     hmac_encode_base64 := Convert.ToBase64String(hmac_encode);
    

    And weith the function you've shown me:
    hmac_encode_base64 := GenerateKeyedHashAsBase64String(concat,secret,1);
    

    In all the cases, I ahve the same value into hmac_encode_base64 , but they are etlling me that it is incorrect...
  • ftorneroftornero Member Posts: 524
    Hello @AitorEG ,

    Then I think that should be other issue in the call to the web service, because the last function that you are using give back the encrypted value

    You can run this example in Python
    import hmac
    from hashlib import sha1
    
    def hmac_encrypt(key, msg):
        return hmac.HMAC(key, msg, sha1)
    
    if __name__ == "__main__":
     
        h = hmac_encrypt(b"Jefe", b"what do ya want for nothing?")
        print(h.hexdigest()) # effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
    

    And do the same in NAV and you get the same result.
    MESSAGE(CU1266.GenerateKeyedHash('what do ya want for nothing?', 'Jefe', 1));
    

    It's not in base64 but does not matter.

    Regards
  • AitorEGAitorEG Member Posts: 342
    ftornero wrote: »
    Hello @AitorEG ,

    Then I think that should be other issue in the call to the web service, because the last function that you are using give back the encrypted value

    You can run this example in Python
    import hmac
    from hashlib import sha1
    
    def hmac_encrypt(key, msg):
        return hmac.HMAC(key, msg, sha1)
    
    if __name__ == "__main__":
     
        h = hmac_encrypt(b"Jefe", b"what do ya want for nothing?")
        print(h.hexdigest()) # effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
    

    And do the same in NAV and you get the same result.
    MESSAGE(CU1266.GenerateKeyedHash('what do ya want for nothing?', 'Jefe', 1));
    

    It's not in base64 but does not matter.

    Regards

    That is what I think, ansd I'm trying to find or demostrate that the error is in another variable....
    Thank you anyway, really appreciate.
    Gracias!!
  • AitorEGAitorEG Member Posts: 342
    Hi everyone,
    I'm still having problems, so I will try to start from the beginnig, checking the different steps. My first goal is to get the MD5 hash of a string.

    In C# is this part of the code:
    byte[] md5 = Md5Hash_Raw(json);
    string md5_base64 = System.Convert.ToBase64String(md5);
    


    And I'm trying to convert into CA/L like this:
    MD5 := MD5.Create;
    Hash := MD5.ComputeHash(Encoding.UTF8.GetBytes(JsonString));
    md5_base64 := Convert.ToBase64String(Hash);
    

    variables:


    JsonString BigText
    Hash DotNet System.Array.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
    md5_base64 Text
    MD5 DotNet System.Security.Cryptography.MD5.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

    From support they are giving me a valuea for md5_base64 different to the one I'm getting...

    Am I loosing something?

    Thank you very much
Sign In or Register to comment.