He tingut que implementar la firma MAC ANSI X9.19 (MAC = Message Authentication Code) i com tots els temes de seguretat és curiós la poca informació que hi ha.
Una firma (MAC) d'aquest tipus serveix per verificar que un missatge no ha estat manipulat i ens arriba tal com es va enviar. Si hi ha un error a la xarxa es detectarà, o si un "algú" dolent modifica el missatge no podrà regenerar la firma per que no coneix la clau a partir de la que es genera la MAC. El receptor com sí té aquesta clau pot verificar el contingut del missatge.
Aquesta firma MAC ANSI X9.19 és un estàndard bancari "made in USA". És una evolució del X9.9 i també es coneix com a Retail-MAC. És un xifrat dels anomenats DES-CBC, on DES vol dir "Data Encryption Standard" i CBC és el "Cipher Block Chaining". Existeix una norma internacional semblant, una mica més permissiva, la ISO 9807 ja que permet fer servir altres algoritmes de xifrat. Diguem que la ANSI X9.19 és una possible implementació de la ISO 9797 quan la mida del bloc a xifrar és de 64 bits, la longitud de la MAC 32 i es fa servir el DES com algoritme de xifrat.
El DES es un algoritme de xifrat ja força antic que fa servir claus de 56 bits però amb la X9.19 també s'aplica un xifrat extra en l'últim pas fent servir una clau de 128bits com si fos un 3DES normal. Els passos a seguir són els següents:
Al missatge d'entrada s'ha de posar padding (relleno). En aquest cas com no cal treure'l posteriorment es posen els bytes a 00 que facin falta fins que la longitud del missatge és múltiple de 8 bytes.
Es divideix la clau de 128bits en dues sub-claus de 64, la part esquerra s'anomena K, la part dreta K'.
Es divideix el missatge en blocs de 8 bytes.
Es xifra amb DES el primer bloc amb la clau K.
Al resultat es fa una XOR byte a byte amb el següent bloc (és el CBC).
Es xifra el resultat de la XOR amb la clau K. Si NO és l'últim bloc tornem al pas anterior. Si és l'últim bloc anem al pas següent.
Aquest últim resultat es desxifra amb la clau K'. Òbviament encara que sigui un desxifrat no s'obté res coherent.
Es xifra altra cop amb la clau K. És com si fos un 3DES en aquest últim pas. El resultat és la MAC.
Però millor una imatge:
He fet dues implementacions en C#, una fent les coses manuals i l'altra amb llibreries de sistema, ambdues ofereixen el mateix resultat. Al codi hi ha un exemple que he trobat amb els valors calculats.
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;
}
}
#17/03/2015 18:28 Programació C# Autor: Alex Canalda