MAC ANSI X9.19 - ISO 9807 - ISO/IEC 9797 signature
Recently I had to implement a MAC ANSI X9.19 (MAC = Message Authentication Code) signature. And like all security related standards there is little information online.
A MAC signature of this type is usefull to verify that a message has not been tampered and that we receive it as it was sent. If there is a network problem and a bit flips it will be detected, or if someone "bad" tries to modify the message also will be detected. They can't calculate the signature for the modified payload because they don't know the key. The receiver of the message can verify it because he knows the key.
The signature MAC ANSI X9.19 is a banking standard made in USA. It's an evolution from X9.9 and it is also known as Retail-MAC. It's a cipher based in DES-CBC, here are the links to the wiki for DES that means "Data Encryption Standard" and CBC that means"Cipher Block Chaining". An international norm exists, that is very similar but a little more permisive called ISO 9807, it allows to use another cipher algorithms. ANSI X9.19 is a subset of ISO 9797, when the cipher block is 64 bits, MAC length is 32 and DES is used.
DES is a very old cipher algorithm that uses 56 bit keys but X9.19 uses 128bit keys like 3DES. Here are the process steps:
The incoming message is padded with 00. As padding will not be removed later there's no need of adding an special padding so 00 is ok. So we add 00 until the message length is multiple of 8 bytes.
The 128 bit key is divided into two sub-keys. Left part is called K, and right part K'.
Message is divided in 8 byte blocks.
Firts block is cyphered with K key.
A XOR is applied to the result with the next block (it's the CBC).
Result is ciphered with K key. If it isn't the last block return to previous step. If it is then move to the next step.
To this last result a decryption is applied with K' key (nothing coherent is obtained).
Finally encrypt with K key. The result is the MAC.
Better an image than a thousand words:
I've made two implementations in C#, one doing things by hand and the other with system libraries. Both offer the same result (I don't know which performs better). In code comments there is an example with correct values to check if it works.
publicbyte[] SubArray(byte[] data, int index, int length)
{
byte[] result = newbyte[length];
Array.Copy(data, index, result, 0, length);
return result;
}
privatevoid btnFirma_Click(object sender, EventArgs e)
{
try
{
if (!ValidarClau()) return;
NetejaTXTs();
byte[] IV = newbyte[8]; //empty byte arraybyte[] key = ConvertHexStringToByteArray(txtKey.Text);
byte[] LeftKey = SubArray(key, 0, 8);
byte[] RightKey = SubArray(key, 8, 8);
byte[] data;
if (chkHexString.Checked)
{
data = ConvertHexStringToByteArray(Neteja(txtOriginal.Text));
}
else
{
data = Encoding.ASCII.GetBytes(Neteja(txtOriginal.Text));
}
//Exemple//Dades = 4E6F77206973207468652074696D6520666F7220616C6C20//Clau = 0123456789ABCDEFFEDCBA9876543210//Firma = A1C72E74EA3FA9B6
DES DESalg = DES.Create();
DESalg.Mode = CipherMode.CBC;
DESalg.Padding = PaddingMode.None;
ICryptoTransform SimpleDES_Encriptador = DESalg.CreateEncryptor(LeftKey, IV);
ICryptoTransform SimpleDES_Desencriptador = DESalg.CreateDecryptor(RightKey, IV);
byte[] result = newbyte[8];
byte[] datablock = newbyte[8];
int remain = data.Length % 8;
int LoopCount = data.Length / 8;
/*
//Padding a 0
if (remain != 0)
{
int extra = 8 - (data.Length % 8);
int newLength = data.Length + extra;
byte[] newData = new byte[newLength];
Array.Copy(data, newData, data.Length);
data = newData;
}
result = SimpleDES_Encriptador.TransformFinalBlock(data, 0, data.Length);
byte[] block = new byte[8];
// Agafem l'ultim block
Array.Copy(result, result.Length - 8, block, 0, 8);
// Desencriptar l'ultim bloc a la K'
block = SimpleDES_Desencriptador.TransformFinalBlock(block, 0, 8);
// Encriptar altra cop el resultat amb K
block = SimpleDES_Encriptador.TransformFinalBlock(block, 0, 8);
result = block;
*///Si el que s'ha de firmar és multiple de 8...if (remain == 0)
{
LoopCount--;
remain = 8;
}
//Primer block
Array.Copy(data, 0, datablock, 0, 8);
result = EncryptBlock(SimpleDES_Encriptador, datablock);
for (int i = 1; i < LoopCount; i++)
{
datablock = newbyte[8];
Array.Copy(data, i * 8, datablock, 0, 8);
//Això fa el CBC
datablock = XorArray(datablock, result);
result = EncryptBlock(SimpleDES_Encriptador, datablock);
}
//Ultim blockbyte[] LastBlock = newbyte[8];
//Això fa padding a zeros
Array.Copy(data, data.Length - remain, LastBlock, 0, remain);
LastBlock = XorArray(LastBlock, result);
result = EncryptBlock(SimpleDES_Encriptador, LastBlock);
result = EncryptBlock(SimpleDES_Desencriptador, result);
result = EncryptBlock(SimpleDES_Encriptador, result);
if (!chkHexString.Checked) txtBase64.Text = ConvertByteArrayToHexString(data);
txtEncrypted.Text = ConvertByteArrayToHexString(result);
}
catch(Exception E)
{
MessageBox.Show(E.Message);
}
}
publicbyte[] XorArray(byte[] buffer1, byte[] buffer2)
{
for (int i = 0; i < buffer1.Length; i++)
buffer1[i] ^= buffer2[i];
return buffer1;
}
privatebyte[] EncryptBlock(ICryptoTransform crypt, byte[] toEncrypt)
{
try
{
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream,
crypt,
CryptoStreamMode.Write);
cStream.Write(toEncrypt, 0, toEncrypt.Length);
cStream.FlushFinalBlock();
byte[] ret = mStream.ToArray();
cStream.Close();
mStream.Close();
Console.WriteLine("DES OUTPUT : " + ConvertByteArrayToHexString(ret));
return ret;
}
catch (CryptographicException e)
{
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
returnnull;
}
}
#18/03/2015 18:28 Programming C# Author: Alex Canalda