Now that 3DES is phasing out, a new signature is need to replace the veteran X9.19. So here comes the brand new (from 2006!) AES CMAC signature. It uses AES instead of 3DES, but externally it uses the same 128bit long keys. I've programmed, with help from StackOverflow, a version in C#, and another version using the BouncyCastle library. Both ways return the same result, and I don't know which one performs better. The program is easy to understand, the only difficult step lays in the subkey generation. I've cut&pasted the RFC part that explains it to be sure I've done the correct thing.
byte[] AESEncrypt(byte[] key, byte[] iv, byte[] data)
{
using (MemoryStream ms = new MemoryStream())
{
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
}
/// <summary>/// This function does:/// x << 1/// Left-shift of the string x by 1 bit./// The most significant bit disappears, and a zero/// comes into the least significant bit./// 10010001 << 1 is 00100010./// </summary>/// <param name="b">Array de bytes a fer shift</param>/// <returns>El byte shiftejat</returns>byte[] Rol(byte[] b)
{
byte[] r = newbyte[b.Length];
byte carry = 0;
for (int i = b.Length - 1; i >= 0; i--)
{
ushort u = (ushort)(b[i] << 1);
r[i] = (byte)((u & 0xff) + carry);
carry = (byte)((u & 0xff00) >> 8);
}
return r;
}
byte[] AESCMAC(byte[] key, byte[] data)
{
/*
// SubKey generation
Figure 2.2 specifies the subkey generation algorithm.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +
+Algorithm Generate_Subkey +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ +
+Input : K(128 - bit key) +
+Output : K1(128 - bit first subkey) +
+K2(128 - bit second subkey) +
+-------------------------------------------------------------------+
+ +
+Constants: const_Zero is 0x00000000000000000000000000000000 +
+const_Rb is 0x00000000000000000000000000000087 +
+Variables: L for output of AES - 128 applied to 0 ^ 128 +
+ +
+Step 1.L := AES - 128(K, const_Zero); +
+Step 2. if MSB(L) is equal to 0 +
+then K1:= L << 1; +
+ else K1:= (L << 1) XOR const_Rb; +
+Step 3. if MSB(K1) is equal to 0 +
+then K2:= K1 << 1; +
+ else K2:= (K1 << 1) XOR const_Rb; +
+Step 4. return K1, K2; +
+ +
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Figure 2.2.Algorithm Generate_Subkey
In step 1, AES - 128 with key K is applied to an all-zero input block.
In step 2, K1 is derived through the following operation:
If the most significant bit of L is equal to 0, K1 is the left - shift
of L by 1 bit.
Otherwise, K1 is the exclusive - OR of const_Rb and the left - shift of L
by 1 bit.
In step 3, K2 is derived through the following operation:
If the most significant bit of K1 is equal to 0, K2 is the left - shift
of K1 by 1 bit.
Otherwise, K2 is the exclusive - OR of const_Rb and the left - shift of
K1 by 1 bit.
In step 4, (K1, K2) := Generate_Subkey(K) is returned.
*/// step 1, AES-128 with key K is applied to an all-zero input block.byte[] L = AESEncrypt(key, newbyte[16], newbyte[16]);
// step 2, K1 is derived through the following operation:byte[] FirstSubkey = Rol(L); //If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit.if ((L[0] & 0x80) == 0x80)
FirstSubkey[15] ^= 0x87; // Otherwise, K1 is the exclusive-OR of const_Rb and the left-shift of L by 1 bit.// step 3, K2 is derived through the following operation:byte[] SecondSubkey = Rol(FirstSubkey); // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit.if ((FirstSubkey[0] & 0x80) == 0x80)
SecondSubkey[15] ^= 0x87; // Otherwise, K2 is the exclusive-OR of const_Rb and the left-shift of K1 by 1 bit.// MAC computingif (((data.Length != 0) && (data.Length % 16 == 0)) == true)
{
// If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits),// the last block shall be exclusive-OR'ed with K1 before processingfor (int j = 0; j < FirstSubkey.Length; j++)
data[data.Length - 16 + j] ^= FirstSubkey[j];
}
else
{
//S'afegeix padding a l'ultim bloc, però en realitat és afegir padding a les dades
data = AddPaddingAES(data);
/*
// Otherwise, the last block shall be padded with 10^i
byte[] padding = new byte[16 - data.Length % 16];
padding[0] = 0x80;
data = data.Concat<byte>(padding.AsEnumerable()).ToArray();
*/// and exclusive-OR'ed with K2, la SecondSubkey té 16 de longitudfor (int j = 0; j < 16; j++) data[data.Length - 16 + j] ^= SecondSubkey[j];
}
// The result of the previous process will be the input of the last encryption.byte[] encResult = AESEncrypt(key, newbyte[16], data);
byte[] HashValue = newbyte[16];
Array.Copy(encResult, encResult.Length - HashValue.Length, HashValue, 0, HashValue.Length);
return HashValue;
}
All that code from above, that seems very complex, can be reduced to a few lines using BouncyCastle. But you know, in the code lays the truth, and with the library you don't see it...
//Using BouncyCastle
Org.BouncyCastle.Crypto.Engines.AesEngine aes = new Org.BouncyCastle.Crypto.Engines.AesEngine();
Org.BouncyCastle.Crypto.Macs.CMac cMac = new Org.BouncyCastle.Crypto.Macs.CMac(aes, 64);
Org.BouncyCastle.Crypto.Parameters.KeyParameter kp = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(key);
cMac.Init(kp);
int MacLenght = cMac.GetMacSize();
byte[] output = newbyte[MacLenght];
cMac.BlockUpdate(data, 0, data.Length);
cMac.DoFinal(output, 0);
txtBase64.Text = ConvertByteArrayToHexString(output);
According to biologists, the term venomous is applied to organisms that bite (or sting) to inject their toxins, whereas the term poisonous applies to organisms that unload toxins when you eat them. This means that very few snakes are truly poisonous. ... Along with snakes, dangerous spiders are also generally venomous.
Today's scientists have substituted mathematics for experiments, and they wander off through equation after equation, and eventually build a structure which has no relation to reality.
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
The class clsData contains all methods needed to invoque all the stored procedures (SP) and the declaration of all the variables used by those methods. On the other hand a derived class contains only the initialization of those variables but no declarations. The program that generates the clsData only creates those derived classes filling the initializations automatically. So the next piece of code is generated by a program, not hand-made. For explanation purposes I put here an example.
Writing all this code by hand is error prone and very boring (a lethal task), that's the reason why a program generates it. SP parameters are obtained from the meta information of the SP that is stored in the database, but this means that the SPs must be in place when this class is generated. The other parameters are obtained from the user interface of the generator software. If a column is checked to serialize then it will appear in the list "Serialitzar", and so on. From this example one can see that the database access layer is formed by the clsData and a ton of derived classes (one for each table).
#23/04/2014 16:15 Programming C# Author: Alex Canalda
clsData is a class that covers a lot of functionality. There are parts that deal with web projects, but cause some troubles when used in other kind of project (WinForms for example). For that reason I use conditional compilation.
This conditional compilation affects to serialization and deserialization because those to features are not used in projects that are not web. For this reason I define a symbol in Project -> Properties -> Build -> Conditional Compilation symbols. The symbol must be defined in all configurations (Debug, Release...).
#09/04/2014 18:23 Programming C# Author: Alex Canalda
clsData holds the declaration of all variables involved in stored procedure calling and some more. Most of them are arrays of clsColumn. We can review the code to see the purpose of each variable. Classes that inherite from clsData initialices all those variables with the correct values, need to invoke a SP. An inherited class usually belongs to a one table.
publicclass clsData
{
/// <summary>/// Prefix used in stored procedures parameters, /// used to differentiate them from variables deffined inside SP./// </summary>publicstring Prefix = "p";
/// <summary>/// GET SP parameters, are the same of GETTOP./// </summary>public clsColumn[] ParamsGET;
/// <summary>/// INS SP parameters/// </summary>public clsColumn[] ParamsINS;
/// <summary>/// UPD SP parameters/// </summary>public clsColumn[] ParamsUPD;
/// <summary>/// DEL SP parameters/// </summary>public clsColumn[] ParamsDEL;
/// <summary>/// QUERY SP parameters/// </summary>public clsColumn[] ParamsQUERY;
/// <summary>/// Columns returned by GET SP as a result of the SELECT/// </summary>public clsColumn[] ResultColumns;
/// <summary>/// Connection string/// </summary>publicstring CadConnBBDD;
/// <summary>/// GET SP full name, p. ex: CLIENTS_GET/// </summary>publicstring spGET;
/// <summary>/// INS SP full name, p. ex: CLIENTS_INS/// </summary>publicstring spINS;
/// <summary>/// UPD SP full name, p. ex: CLIENTS_UPD/// </summary>publicstring spUPD;
/// <summary>/// DEL SP full name, p. ex: CLIENTS_DEL/// </summary>publicstring spDEL;
/// <summary>/// QUERY SP full name, p. ex: CLIENTS_QUERY/// </summary>publicstring spQUERY;
/// <summary>/// COUNT SP full name, p. ex: CLIENTS_COUNT/// </summary>publicstring spCOUNT;
/// <summary>/// SqlConnection object. this is used if the programmer wants/// to use the same connection all time, without closing and opening it every time./// </summary>publicSqlConnection SQLConn = null;
/// <summary>/// Transaction object. Optional, if null is not used./// </summary>publicSqlTransaction SQLTrans = null;
/// <summary>/// Boolean that indicates if the table has an identity column/// </summary>publicbool HasIdentity = false;
/// <summary>/// Boolean that indicates if the table uses a the cache system./// </summary>publicbool UseCache = false;
/// <summary>/// Boolean that indicates if the cache is automanaged./// </summary>publicbool AutoManageCache = true;
/// <summary>/// Table name/// </summary>publicstring TableName = "";
/// <summary>/// Name of the identity column/// </summary>publicstring Identity = "";
/// <summary>/// List of columns that are serialized and send to the browser/// </summary>public clsColumn[] Serialitzar = null;
/// <summary>/// List of columns that are deserialized when values arrive from the browser/// </summary>public clsColumn[] Deserialitzar = null;
//SP Cache name, usually changes from project to project.//It must have the following parameters: Tablename varchar(50) and URL varchar(500)privatestring SP_Cache = "DI_EXP_CACHE_DEL";
}
#27/03/2014 17:56 Programming C# Author: Alex Canalda