10

It seems that there're 6 variations to CBC-MAC algorithm. I've been trying to match the MAC algorithm on the PINPad 1000SE [which per manual is ISO 9797-1 Algorithm 1].

I got an excellent start from here.

And I coded the algorithm as below:

public static byte[] CalculateMAC(this IPinPad pinpad, byte[] message, byte[] key)
{
    //Divide the key with Key1[ first 64 bits] and key2 [last 64 bits]
    var key1 = new byte[8];
    Array.Copy(key, 0, key1, 0, 8);

    var key2 = new byte[8];
    Array.Copy(key, 8, key2, 0, 8); //64 bits

    //divide the message into 8 bytes blocks
    //pad the last block with "80" and "00","00","00" until it reaches 8 bytes
    //if the message already can be divided by 8, then add 
    //another block "80 00 00 00 00 00 00 00"
    Action<byte[], int> prepArray = (bArr, offset) =>
                                     {
                                         bArr[offset] = 0; //80
                                         for (var i = offset + 1; i < bArr.Length; i++)
                                             bArr[i] = 0;
                                     };
    var length = message.Length;
    var mod = length > 8? length % 8: length - 8;

    var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 0);
    //var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 8);
    Debug.Assert(newLength % 8 == 0);

    var arr = new byte[newLength];
    Array.Copy(message, 0, arr, 0, length);
    //Encoding.ASCII.GetBytes(message, 0, length, arr, 0);
    prepArray(arr, length);
    //use initial vector {0,0,0,0,0,0,0,0} 
    var vector = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };

    //encrypt by DES CBC algorith with the first key KEY 1
    var des = new DESCryptoServiceProvider { Mode = CipherMode.CBC };
    var cryptor = des.CreateEncryptor(key1, vector);
    var outputBuffer = new byte[arr.Length];
    cryptor.TransformBlock(arr, 0, arr.Length, outputBuffer, 0);

    //Decrypt the result by DES ECB with the second key KEY2 [Original suggestion]
    //Now I'm Encrypting
    var decOutputBuffer = new byte[outputBuffer.Length];
    des.Mode = CipherMode.ECB;
    var decryptor = des.CreateEncryptor(key2, vector);
    //var decryptor = des.CreateDecryptor(key2, vector);
    decryptor.TransformBlock(outputBuffer, 0, outputBuffer.Length, decOutputBuffer, 0);

    //Encrypt the result by DES ECB with the first key KEY1
    var finalOutputBuffer = new byte[decOutputBuffer.Length];
    var cryptor2 = des.CreateEncryptor(key1, vector);
    cryptor2.TransformBlock(decOutputBuffer, 0, decOutputBuffer.Length, finalOutputBuffer, 0);

    //take the first 4 bytes as the MAC
    var rval = new byte[4];
    Array.Copy(finalOutputBuffer, 0, rval, 0, 4);
    return rval;
}

Then I discovered there're 3 padding schemes and the one that gave me a start may not necessarily be right. The manual came to my rescue again. It seems the device only pads with 0s. Additional block is also nowhere mentioned so I made the below changes:

    Action<byte[], int> prepArray = (bArr, offset) =>
                                     {
                                         bArr[offset] = 0; ... }

No additional block (if mod 0 [divisible by 8] do not change array length)

var newLength = length + ((mod < 0) ? -mod : (mod > 0) ? 8 - mod : 0);

The original suggestion wanted me to decrypt at the second step... but Valery here suggests that it's encrypt all the way. So I changed Decrypt to Encrypt. But still I'm unable to get the requisite MAC...

Manual says for key "6AC292FAA1315B4D8234B3A3D7D5933A" [since the key should be 16 bytes, I figured the key here's hex string so I took byte values of 6A, C2, 92, FA... new byte[] { 106, 194, 146, ...] the MAC should be 7B,40,BA,95 [4 bytes] if the message is [0x1a + byte array of MENTERODOMETER]

Can someone help? Please?


Since Pinpad requires that the first character in message is a 0x1a...

public static byte[] CalculateAugmentedMAC(this IPinPad pinpad, string message, byte[] key)
{
    var arr = new byte[message.Length + 1];
    var source = Encoding.ASCII.GetBytes(message);
    arr[0] = 0x1a; //ClearScreenIndicator
    Array.Copy(source, 0, arr, 1, source.Length);
    return CalculateMAC(pinpad, arr, key);
}

I'm calling the code above with this input:

var result = pad.CalculateAugmentedMAC("MENTERODOMETER", new byte[] { 106, 194, 146, 250, 161, 49, 91, 77, 130, 52, 179, 163, 215, 213, 147, 58 });
Svante
  • 50,694
  • 11
  • 78
  • 122
Vyas Bharghava
  • 6,372
  • 9
  • 39
  • 59
  • 2
    Curious. Why would you need to get involved in en/decrypting PINPad data? The PINPad workflow is designed to send encrypted data directly to the merchant's bank using the DUKPT keys generated by the bank and installed in the PINPad by the PINPad Vendor. Even getting to the point where we were writing software to directly control various PINPads' UI and other behavior, we were never able to, nor did we need to encrypt/decrypt the encrypted PIN data envelope provided as part of the process. – Bill Sep 18 '09 at 13:50
  • Bill, you're right... But if you were to write a loyalty management application that allows patrons a PIN, you need to use it without these pre-programmed keys.... PINPad 1000SE's earlier model - can't remember on top of my head - did allow you to get a PIN and send that as clear text. The later models encrypt the PIN and we can't get at it... – Vyas Bharghava Sep 28 '09 at 15:37

3 Answers3

2

Most CBC MAC algorithms are implemented in BouncyCastle's JCE provider.

Look at: BouncyCastleProvider.java

You're probably looking for DESEDEISO9797ALG1MACWITHISO7816-4PADDING, which is an alias for DESEDEMAC64WITHISO7816-4PADDING, implemented here (well, it's a specific configuration of CBCBlockCipherMac using the DESedeEngine and ISO7816d4Padding, you'll have to jump between some classes to get the full picture): JCEMac.java

Also, have a look at jPos:

JCESecurityModule.java

and their contributed retail MAC algorithm implementation:

retail-mac-contributed-by-vsalaman.zip

0

I am pretty sure (IIRC) that you need to call TransformFinalBlock at the end (per encryptor).

leppie
  • 115,091
  • 17
  • 196
  • 297
0

Can't answer to your specific terminal, but I use this to test MACs.

public static byte[] GenerateMAC(byte[] key, byte[] data)
{
    using (MACTripleDES mac = new MACTripleDES(key))
        return mac.ComputeHash(data);
}
David Chappelle
  • 1,243
  • 5
  • 13
  • 29