DotNet Example: Digital Hash

Description

Digital hash functions are often used to validate executable files and digital documents. One important use of a digital hash is to sign e-commerce documents; this is required in order to do business on Amazon, for example.

The .NET Framework has a full set of encryption functions, so it was relatively easy to create a .NET assembly and Xbasic wrapper function for the purpose of implementing an HMAC (hash-based message authentication code).

The value returned for the HMAC hash is in Base64, which is what Amazon wants for e-commerce. If you try to compare Base64 with an online hash calculator that returns hexadecimal, they will not appear to match.

See the Plain SHA-1 Hash section below if you only want a simple SHA1 hash, rather than an encrypted HMAC hash based on SHA1 or another hash algorithm.

The C# code used to create the DLL, "DigitalHash.dll":

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
 
namespace HashLib
{
    public class HMAC
    {
        /// <summary>
        /// Computes RFC 2104-compliant HMAC signature
        /// </summary>
        /// <param name="data"></param>
        /// <param name="key"></param>
        /// <param name="algorithm"></param>
        /// <returns>Signature</returns>
        public String Sign(String data, String key, KeyedHashAlgorithm algorithm)
        {
            Encoding encoding = new UTF8Encoding();
            algorithm.Key = encoding.GetBytes(key);
            return Convert.ToBase64String(algorithm.ComputeHash(
            encoding.GetBytes(data.ToCharArray())));
        }
        
        /// <summary>
        /// Computes RFC 2104-compliant HMAC signature using SHA1
        /// </summary>
        /// <param name="data"></param>
        /// <param name="key"></param>
        /// <returns>Signature</returns>
        public String Sign1(String data, String key)
        {
            KeyedHashAlgorithm algorithm = new HMACSHA1();
            return Sign(data, key, algorithm);
        }
        
        /// <summary>
        /// Computes RFC 2104-compliant HMAC signature using SHA256
        /// </summary>
        /// <param name="data"></param>
        /// <param name="key"></param>
        /// <returns>Signature</returns>
        public String Sign256(String data, String key)
        {
            KeyedHashAlgorithm algorithm = new HMACSHA256();
            return Sign(data, key, algorithm);
        }
    
        /// <summary>
        /// Computes RFC 2104-compliant HMAC signature using specified signature method
        /// </summary>
        /// <param name="data"></param>
        /// <param name="key"></param>
        /// <param name="SignatureMethod"></param>
        /// <returns>Signature</returns>
        public String SignUsing(String data, String key, String SignatureMethod)
        {
            KeyedHashAlgorithm algorithm = KeyedHashAlgorithm.Create(SignatureMethod.ToUpper());
            return Sign(data, key, algorithm);
        }
    }
}

The Xbasic module that contains the wrapper function:

FUNCTION HMAC_HASH as C(data as C, key as C, assembly_path as C, algorithm as C = "HMACSHA1")
  'debug(1)
  dim alg as C = upper(algorithm)
 
  'register .NET class
  dim sv as DotNet::Services
  dim assembly as DotNet::AssemblyReference
  assembly.FileName = assembly_path+"DigitalHash.dll"
 
  IF .not. file.exists(assembly.FileName)
    ui_msg_box("File not found",assembly.FileName)
    end
  END IF
 
  IF sv.RegisterClass("","","HashLib.HMAC",assembly) THEN
  'Call appropriate .NET method if registration succeeds
    dim hash as DotNet::HashLib::HMAC
    SELECT
      CASE alg="HMACSHA1"
        HMAC_HASH = hash.Sign1(data,key)
 
      CASE alg="HMACSHA256"
        HMAC_HASH = hash.Sign256(data,key)
 
      CASE else
        HMAC_HASH = hash.SignUsing(data,key,alg)
    END SELECT
  END IF
END FUNCTION

exports.HMAC_HASH = HMAC_HASH

An interactive session to test the function:

dim xbm as p
xbm = require("hmac_hash")

path = "c:\path\to\DigitalHash\DLL\\"
? xbm.HMAC_HASH("my data", "my key", path)
= "Wnw05dPAEo44+bw1luJqAWksvhE="

? xbm.HMAC_HASH("my data", "my key", path, "HMACSHA1")
= "Wnw05dPAEo44+bw1luJqAWksvhE="

? xbm.HMAC_HASH("my data", "my key", path, "HMACSHA256")
= "kBhEzgLKNjSjjzQw7s240hvoY62kDG/wHDjYXry++nA="

? xbm.HMAC_HASH("my data", "my key", path, "HMACSHA384")
= "1819IdbuGcIweTIhYBwIK1mOmNrlpgRKK98gnDlVJyXug36wQoDWuBoGlB/GfMqc"

? xbm.HMAC_HASH("my data", "my key", path, "MACTripleDES")
ERROR: command: HMAC_HASH = hash.SignUsing(data,key,alg)
Exception has been thrown by the target of an invocation.
Specified key is not a valid size for this algorithm.

? xbm.HMAC_HASH("my data", "my key", path, "hMACmd5")
= "6YeAf6EEZBgdF4BqkAQe/w=="

? xbm.HMAC_HASH("my data", "my key", path, "garbage")
ERROR: command: HMAC_HASH = hash.SignUsing(data,key,alg)
Exception has been thrown by the target of an invocation.
Object reference not set to an instance of an object.
Try it out! Click here to download the DigitalHash.dll and hmac_hash Xbasic module used in this example.

A modified version of the hmac_hash() function and the required .NET DLL are shipped with Alpha Anywhere.

Plain SHA-1 Hash

Calculating a plain SHA-1 hash is a simpler problem. The following script will accomplish the purpose.

dim data as b
dim result as b
data = "HMACSHA256"

dim sham as ::System::Security::Cryptography::SHA1Managed = new ::System::Security::Cryptography::SHA1Managed()
result = sham.ComputeHash(data)

showvar(*to_hex(result))

This gives:

57568b047095c4679873f2ad36d9a76fe92fbc0c
The blob type in Xbasic (dim data as b) corresponds to the byte[] type in the .NET framework.

See Also