1

I need to generate MAC based on ISO 9797 Alg3 from a plaintext. I already wrote a same function in java and C# by "BouncyCastle" tool very easy as following code. But, there is not any sample for python around the internet.

using System.Text;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using System.Security.Cryptography;
using System.IO;

namespace TestGenerateMAC2
{
    class Program
    {
        public static string getMac(string text, string key)
        {
            byte[] keyBytes = StringToByteArray(key);
            byte[] data = Encoding.UTF8.GetBytes(text);
            DesEngine cipher = new DesEngine();
            ISO9797Alg3Mac mac = new ISO9797Alg3Mac(cipher);

            KeyParameter keyP = new KeyParameter(keyBytes);
            mac.Init(keyP);
            mac.BlockUpdate(data, 0, data.Length);

            byte[] outPut = new byte[8];

            mac.DoFinal(outPut, 0);

            return BytesToHex(outPut);
        }
        public static byte[] StringToByteArray(string hex)
        {
            return Enumerable.Range(0, hex.Length)
                             .Where(x => x % 2 == 0)
                             .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                             .ToArray();
        }

        public static string BytesToHex(byte[] bytes)
        {
            return String.Concat(Array.ConvertAll(bytes, delegate (byte x) { return x.ToString("X2"); })).ToLower();
        }

  }
}

Is there any same sample in python?

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Mahyar Esteki
  • 189
  • 2
  • 12

2 Answers2

1

Asking for samples is explicitly off topic, but I'll indicate how you can create the algorithm in general, using a DES CBC and DES ECB cipher.

All operations are on bytes or byte arrays.

  1. split the 16 byte key into key A and key B
  2. init DES CBC encryption with key A and an all-zero IV of 8 bytes
  3. iterate over all full blocks within the message & for each block perform CBC encryption, throwing away the result (using the same cipher instance, you need to keep the state after all)
  4. create a final block and copy the left over bytes from the message into it
  5. at the next position add the initial padding indicator byte
  6. finalize the CBC encryption by encrypting the final block, and keep the result
  7. perform DES ECB decryption over the result with key B, replacing the result
  8. peform DES ECB encryption over the result with key A, replacing the result

And then of course return the result.

... happy programming


Well, as the answer was accepted, I'll forgo the warning of not posting sample code and post my own, just to show good programming practices (as far as I can create those with my bare knuckles understanding of Python):

import sys
from Crypto.Cipher import DES
from Crypto.Cipher import DES3
from Crypto.Util.strxor import strxor
import binascii

def macIso9797_m1_alg3(key, msg):
    return macIso9797_alg3(key, msg, b"\x00")

def macIso9797_m2_alg3(key, msg):
    return macIso9797_alg3(key, msg, b"\x80")

def macIso9797_alg3(key, msg, pad_start):
    if (len(key) != 16):
        raise ValueError("Key length should be 16 bytes")

    keya = key[:8]
    keyb = key[8:]

    full_blocks = len(msg) / 8

    # b"\x00" * 8 might be slightly faster
    desa = DES.new(keya, DES.MODE_CBC, bytes(bytearray(8)))
    for i in range(0, full_blocks):
        off = i * 8
        block = msg[off:off + 8]
        # don't need the ciphertext, just the internal state
        desa.encrypt(block)

    # create padded final block
    final_block = bytearray(8)
    left = len(msg) % 8
    final_block[0:left] = msg[-left:]

    final_block[left] = pad_start

    res = desa.encrypt(bytes(final_block))

    # cipher may not *just* return the final block (but does)
    if (len(res) > 8):
        res = res[-8:]    

    desb = DES.new(keyb, DES.MODE_ECB)
    res = desb.decrypt(res)

    desc = DES.new(keya, DES.MODE_ECB)
    res = desc.encrypt(res)

    return res

macKey="EA1302AFBCCF791CB0065BFAD948B092"
message="test message"

res = macIso9797_m1_alg3(binascii.unhexlify(macKey), message)

print("MAC key: " + binascii.hexlify(res))

Note that the API documentation of the DES / cipher objects in the above code are not explicit what data is returned, and when. I therefore put in an if statement to retrieve the last block of ciphertext that is clearly not needed with the current implementation. That's probably over-cautious (but that comes with the crypto domain).

Also note that I've created a final block for the padding outside the message itself. Padding the message itself is both dangerous (you may want to use the message for other means) and / or resource intensive, as copying of a large message is a bad idea.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Brother, are you have c# version? Thanks – Ihdina Jan 31 '22 at 15:36
  • The question was about converting code from C# to Python; the C# code is **in the question**. – Maarten Bodewes Jan 31 '22 at 16:19
  • I tried to translate your Py to C# with microsoft's default library like this, but the result is still error. Can you help me to resolved my problem? Thanks https://stackoverflow.com/questions/70926858/calculate-the-mac-with-s-mac-to-sign-the-data-with-single-des-plus-final-triple – Ihdina Jan 31 '22 at 23:53
  • I tried C# in the question, but not worked with follows config: iv = 0000000000000000, s-mac key = C6713F31B8DC1F8905DFECB4065CB81E, plain = 84820000103CA4BC00FAD9D143, the mac result expected is 4F4086C4959E26B5. – Ihdina Feb 01 '22 at 00:41
  • My problem resolved, thanks – Ihdina Feb 01 '22 at 02:14
  • Funny, that one got resolved by an answer by myself, after which the code was posted :) Sometimes it does indeed require going through some posts... but resolved is resolved. – Maarten Bodewes Feb 01 '22 at 08:10
  • I get an error, what is the reason: Object type cannot be passed to C code – Master Wenom Feb 06 '23 at 09:03
1

Thank Maarten!

I found and changed a code based on your description. The following code solved my problem.

Thank you so much

import sys
from Crypto.Cipher import DES
from Crypto.Cipher import DES3
from Crypto.Util.strxor import strxor
import binascii


def macIso9797_m2_alg3(key, msg):
    return macIso9797_alg3(key, msg, "80")

def macIso9797_m1_alg3(key, msg):
    return macIso9797_alg3(key, msg, "00")

def macIso9797_alg3(key, msg, pad_start):

    key_len = int(len(key)/2)    

    if (key_len != 16):
        raise ValueError("Key length should be 16 digits")    

    # force header  padding
    msg += pad_start

    # padding with "00"
    lenRestOfData = int((len(msg)/2) % 8)
    msg += "00"*(8-lenRestOfData)

    loopNum = int((len(msg)/2) / 8)

    bufferOutput = binascii.unhexlify("00"*8)
    IV = '\x00'*8    

    keya = binascii.unhexlify(key[0:16])
    keyb = binascii.unhexlify(key[16:])

    i = 0
    for i in range (0, loopNum):
        tdesa = DES.new(keya, DES.MODE_ECB)

        data = msg[i*16:i*16+16]

        x = bufferOutput
        bufferOutput = strxor(binascii.unhexlify(data), bufferOutput)

        bufferOutput = tdesa.encrypt(bufferOutput)

    tdesb = DES.new(keyb, DES.MODE_ECB)
    bufferOutput = tdesb.decrypt(bufferOutput)

    tdesa = DES.new(keya, DES.MODE_ECB)
    bufferOutput = tdesa.encrypt(bufferOutput)

    return bufferOutput


macKey="mac key"
message="text message"

hexMessage = bytes(message, encoding='utf-8').hex()

print('MAC Key: ' + macKey)
print('MAC: ' + macIso9797_m1_alg3(macKey, hexMessage).hex())
Mahyar Esteki
  • 189
  • 2
  • 12