Hash, hash baby – Secure Hash Algorithm in NAV/365BC (and a little more)

Mostly web services offered through an API do require special ways of authorisation. Recently I’ve came across the Royal Mail API which doesn’t work without a combination of Base64-encoding and a SHA-1 encryption.

The Royal Mail Shipping API does require a security header based on the definition of the Web Service Security by OASIS. Details about the structure are published here as PDF by OASIS and here by Microsoft. Part of the definition is, that a string must be encrypted using the SHA-1 algorythm. In the first half of this article you will find examples how to encrypt a string using SHA-1 and SHA-256. In the second part you will find a code example how to encrypt login-details based on the WS-Security Standard which is used by the Royal Mail Shipping API.

I let the code speak for itself, so let me know any questions in the commments below!

Implementation of SHA into C/AL

Let’s dive into the code directly. Here are the variables:

[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Security.Cryptography.SHA1CryptoServiceProvider";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
[email protected] : OutStream;
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.IO.MemoryStream";
[email protected] : TEMPORARY Record 99008535;

And here is the code:

TempBlob.Blob.CREATEOUTSTREAM(OutStream);
SHA1CryptoServiceProvider := SHA1CryptoServiceProvider.SHA1CryptoServiceProvider;
ContentArray := ContentEncoding.UTF8.GetBytes('This string will be encrypted.');
ContentArray := SHA1CryptoServiceProvider.ComputeHash(ContentArray);
MemoryStream := MemoryStream.MemoryStream(ContentArray);
MemoryStream.WriteTo(OutStream);
TempBlob.INSERT

For my purpose it was necessary to keep the encrypted string in Byte. That’s the reason I store the result in a TempBlob.

The following code demonstrates how to store the encrypted string into a Text-Variable. Let’s start with the variables again:

[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Security.Cryptography.SHA1CryptoServiceProvider";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.BitConverter";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
[email protected] : Text

And here ist the code:

SHA1CryptoServiceProvider := SHA1CryptoServiceProvider.SHA1CryptoServiceProvider;
ContentArray := ContentEncoding.UTF8.GetBytes('This string will be encrypted.');
ContentArray := SHA1CryptoServiceProvider.ComputeHash(ContentArray);
Result := BitConverter.ToString(ContentArray);
MESSAGE(Result);

What about SHA-256?

Encryption using SHA-256 is no problem. The overall logic of above code doesn’t change. Only one variable to encrypt using SHA-256:

[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.BitConverter";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
[email protected] : DotNet "'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Security.Cryptography.SHA256CryptoServiceProvider";
[email protected] : Text

Here is the code:

SHA256 := SHA256.SHA256CryptoServiceProvider;
ContentArray := ContentEncoding.UTF8.GetBytes('This string will be encrypted.');
ContentArray := SHA256.ComputeHash(ContentArray);
Result := BitConverter.ToString(ContentArray); 
MESSAGE(Result);

WS-Security for Royal Mail API

And here is the promised “and a little more”. The following code will calculate Security Header information which is required by the Royal Mail Shipping API.

First again the variables:

[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Security.Cryptography.SHA1CryptoServiceProvider";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Text.Encoding";
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Convert";
[email protected] : OutStream;
[email protected] : InStream;
[email protected] : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.IO.MemoryStream";
[email protected] : TEMPORARY Record 99008535;

And the code:

CalculateSecurityHeader(VAR pUsername : Text;VAR pPasswordDigest : Text;VAR pNonce : Text;VAR pCreatedDateTime : Text)
pUsername := 'Tea'; // handle your username here
pPasswordDigest := 'Biscuit'; // handle your password here

pCreatedDateTime := FORMAT(CREATEDATETIME(TODAY, TIME), 0, 9);
pNonce := FORMAT(RANDOM(999999999));

TempBlob.Blob.CREATEOUTSTREAM(OutStream);
OutStream.WRITETEXT(pNonce);
OutStream.WRITETEXT(pCreatedDateTime);

SHA1CryptoServiceProvider := SHA1CryptoServiceProvider.SHA1CryptoServiceProvider;

ContentArray := ContentEncoding.UTF8.GetBytes(pPasswordDigest);
ContentArray := SHA1CryptoServiceProvider.ComputeHash(ContentArray);
MemoryStream := MemoryStream.MemoryStream(ContentArray);
MemoryStream.WriteTo(OutStream);
TempBlob.INSERT;

CLEAR(ContentArray);
CLEAR(MemoryStream);
TempBlob.CALCFIELDS(Blob);
TempBlob.Blob.CREATEINSTREAM(InStream);

ContentArray := SHA1CryptoServiceProvider.ComputeHash(InStream);
pPasswordDigest := Convert.ToBase64String(ContentArray);
 
CLEAR(ContentArray);
ContentArray := ContentEncoding.UTF8.GetBytes(pNonce);
pNonce := Convert.ToBase64String(ContentArray);

Have fun đŸ™‚

Leave a Comment

Your email address will not be published. Required fields are marked *