One of the Most Fundamental Building Block in Cybersecurity: MACs

Prof Bill Buchanan OBE FRSE
4 min readNov 3, 2023

--

MACs (Message Authentication Codes) are one of the most useful things in cybersecurity, but still, many professionals would struggle to explain what they are and why they are so important. In fact, MACs can be at the core of the security of your organisation, and authentication tokens to provide access to services are provided with a MAC signer.

MACs can be used to authenticate a message, as a key is required to determine the hash value. With this, the sender computes the hash with a secret key and sends it to the receiver, where the receiver then calculates the hash of the message and uses the secret key. If the hash received is the same as the computed one, the message has not been tampered with. Typical methods include Gost28147, CMAC, GMAC, Poly1305 and HMAC. Bouncy Castle implements a number of hashing digests, including HMAC, Gost28147, GMAC (as used in AES GCM for authenticated encryption) and CMAC In this case, we will use Bouncy Castle and C#.

With a MAC, Bob and Alice have the same shared key, and produce a MAC using this shared key, a defined hashing method, and a hash of the message:

So, let’s code with C#/.NET and integrate with the Bouncy Castle library. Initially, we can create a Dotnet console project for .NET 8.0 with:

dotnet new console --framework net8.0

First, we install the Bouncy Castle library:

dotnet add package BouncyCastle.Cryptography

There are many ways to create a signing key, but in this case we will take a SHA-256 hash of a secret phrase (keystr):

byte[] key = System.Security.Cryptography.SHA256.Create().ComputeHash(
System.Text.Encoding.UTF8.GetBytes(keystr));

Sometimes we also need a salt value, and which can be generated as:

                byte[] iv = new byte[8];
new Random().NextBytes(iv);

and which will create a 64-bit salt value. The approach is similar for each of the MACs, such as for GOST 28147, and where we initialize with secret key, and then determine the size of the output MAC:

var mac = new  Gost28147Mac();
mac.Init(new KeyParameter(key));
byte[] result = new byte[mac.GetMacSize()];
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
mac.BlockUpdate(bytes, 0, bytes.Length);
mac.DoFinal(result, 0);
Console.WriteLine ("GOST 28147:\t{0}",Convert.ToHexString(result));

Next, we can add the code for Gost28147, GMAC (as used with AES GCM), CCM, CMAC, Poly1305 and HMAC [here]:

namespace Macs
{
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Digests;
class Program
{
static void Main(string[] args)
{
string str="The quick brown fox jumps over the lazy dog";
string keystr="Qwerty";
if (args.Length >0) str=args[0];
if (args.Length >1) keystr=args[1];
try {
// var key= Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000000");
byte[] key = System.Security.Cryptography.SHA256.Create().ComputeHash(System.Text.Encoding.UTF8.GetBytes(keystr));
byte[] key128 = System.Security.Cryptography.MD5.Create().ComputeHash(System.Text.Encoding.UTF8.GetBytes(keystr));
byte[] iv = new byte[8];
new Random().NextBytes(iv);
byte[] iv128 = new byte[16];
new Random().NextBytes(iv128);

Console.WriteLine ("Message:\t\t{0}\n\n",str);
Console.WriteLine ("Key string:\t{0}\n",keystr);
Console.WriteLine ("Key (SHA256(keystr)):\t{0}\n\n",Convert.ToHexString(key));
Console.WriteLine ("IV:\t\t{0}\n\n",Convert.ToHexString(iv));

//// GOST 28147 Mac
var mac = new Gost28147Mac();
mac.Init(new KeyParameter(key));
byte[] result = new byte[mac.GetMacSize()];
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
mac.BlockUpdate(bytes, 0, bytes.Length);
mac.DoFinal(result, 0);
Console.WriteLine ("GOST 28147:\t{0}",Convert.ToHexString(result));
//// CMAC
var mac2 = new CMac(new AesEngine());
mac2.Init(new KeyParameter(key));
result = new byte[mac2.GetMacSize()];
mac2.BlockUpdate(bytes, 0, bytes.Length);
mac2.DoFinal(result, 0);
Console.WriteLine ("CMAC:\t\t{0}",Convert.ToHexString(result));
//// GMAC
var mac3 = new GMac(new GcmBlockCipher(new AesEngine()), 96);
KeyParameter param = new KeyParameter(key);
mac3.Init(new ParametersWithIV(param,iv));
result = new byte[mac3.GetMacSize()];
mac3.BlockUpdate(bytes, 0, bytes.Length);
mac3.DoFinal(result, 0);
Console.WriteLine ("GMAC:\t\t{0}",Convert.ToHexString(result));
//// SipHash
var mac4 = new SipHash(4,8); // C - number of compression rounds. D - number of finalization rounds
mac4.Init(new KeyParameter(key128)); // 128-bit key
result = new byte[mac4.GetMacSize()];
mac4.BlockUpdate(bytes, 0, bytes.Length);
mac4.DoFinal(result, 0);
Console.WriteLine ("SipHash:\t{0}",Convert.ToHexString(result));
/*
//// Poly1305 - Not woring
var mac5 = new Poly1305();
KeyParameter param2 = new KeyParameter(key);
var z=new ParametersWithIV(param2,iv128);

mac5.Init();
result = new byte[mac5.GetMacSize()];
mac5.BlockUpdate(bytes, 0, bytes.Length);
mac5.DoFinal(result, 0);
Console.WriteLine ("Poly1305:\t\t{0}",Convert.ToHexString(result));
*/
//// HMAC
var mac6 = new HMac(new Sha256Digest());

mac6.Init(new KeyParameter(key));
result = new byte[mac6.GetMacSize()];
mac6.BlockUpdate(bytes, 0, bytes.Length);
mac6.DoFinal(result, 0);
Console.WriteLine ("HMAC:\t\t{0}",Convert.ToHexString(result));
// Skein
var mac7 = new SkeinMac(256,128); // Block size 256 bits, 128 bit output

mac7.Init(new KeyParameter(key));
result = new byte[mac7.GetMacSize()];
mac7.BlockUpdate(bytes, 0, bytes.Length);
mac7.DoFinal(result, 0);
Console.WriteLine ("Skein:\t\t{0}",Convert.ToHexString(result));

} catch (Exception e) {
Console.WriteLine("Error: {0}",e.Message);
}
}
}
}

A sample run is [here]:

Message:		The quick brown fox jumps over the lazy dog

Key string: Qwerty1234
Key (SHA256(keystr)): 3E70B949FA539AB1244FDC67168796541C0E0D8DB9E928924952FE4DE0751112

IV: AE991414BEA6F295

GOST 28147: C64A233B
CMAC: 5F627E3589F9E17D05FADE9EACA45970
GMAC: 9D3D6ED663F4725D99631E95
SipHash: 35DDB97A3A2E286C
HMAC: 2A3363A770839A258C3AA9939F4F9C466E9C6D0B39957CEA55111035895D2FFC
Skein: 4FCFEF5955695B2364C7DAC792865654

Overall, it is HMAC which is the most widely used.

Conclusions

MACs are cool, and allow us an alterative to public key signing (such as with ECDSA and RSA). They are often used to sign tokens, too.

--

--

Prof Bill Buchanan OBE FRSE

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.