[Python] Implement the "Retail MAC" for BAC passport authentication

Discussão em 'Python' iniciado por Stack, Maio 17, 2025.

  1. Stack

    Stack Membro Participativo

    My goal is to extract data from a passport in which I already have the MRZ. I'm starting on a basis of a CardPeek lua script that works and trying to implement that in C++ with the CNG API.

    I 've gotten into a checkpoint that, given a K_MAC (derived from the MRZ) and the "E_IFD" (ENC(RND_IFD || RND_IC || K_IFD)) I have to implement the "retail MAC".

    At a point, the K_MAC is D6E358ECDA0015E273EEEBB5B865082F and the E_IFD is DE3737718D12260E21D6237319004D54AD7239F6F56EDD41725431B2EB4000FB.

    My code in Win32 API so far and the lua script produces the same results.

    At this point, the lua script is:

    local CBC_MAC = crypto.create_context(crypto.ALG_ISO9797_M3+crypto.PAD_ISO9797_P2,KM_MAC);
    m_ifd = crypto.mac(CBC_MAC,e_ifd)


    And this produces a value 0A2B038851996635

    Now I 'm trying to implement this in C++ but something is wrong and I can't figure out why (error handling removed).

    bool ComputeRetailMAC(const BYTE* k_mac, const BYTE* data, size_t dataLen, BYTE* mac)
    {
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_KEY_HANDLE hKey = NULL;
    NTSTATUS status;
    bool result = false;

    // 3DES key: K1 || K2 || K1 (24 bytes)
    BYTE k_mac_24[24] = {};
    memcpy(k_mac_24, k_mac, 16);
    memcpy(k_mac_24 + 16, k_mac, 8);

    status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_3DES_ALGORITHM, NULL, 0);

    // Set chaining mode to CBC
    status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC, wcslen(BCRYPT_CHAIN_MODE_CBC)*2, 0);

    // Generate symmetric key
    status = BCryptGenerateSymmetricKey(hAlg, &hKey, NULL, 0, (PUCHAR)k_mac_24, sizeof(k_mac_24), 0);

    // CBC IV = 0
    BYTE iv[8] = { 0 };

    // Pad data to 8-byte boundary with zeroes
    size_t paddedLen = (dataLen + 7) & ~7;
    if (dataLen % 8 == 0)
    paddedLen += 8;


    std::vector<BYTE> paddedData(paddedLen);
    memcpy(paddedData.data(), data, dataLen);

    paddedData[dataLen] = 0x80;
    if (paddedLen > dataLen + 1)
    memset(paddedData.data() + dataLen + 1, 0x00, paddedLen - dataLen - 1);


    // Encrypt
    DWORD cbResult = 0;
    std::vector<BYTE> encrypted(paddedLen);
    status = BCryptEncrypt(hKey, paddedData.data(), (ULONG)paddedLen, NULL, iv, sizeof(iv), encrypted.data(), (ULONG)paddedLen, &cbResult, 0);

    memcpy(mac, encrypted.data() + cbResult - 8, 8); // Last block is MAC
    ... cleanup here
    }


    I also found somewhere that the padding must add +8 bytes if already aligned. Either way I can't get the (correct) result of 0A2B038851996635. What am I doing wrong? Edit: I tried it with python and it returns the same as my Win32 version

    from Crypto.Cipher import DES3

    def pad_iso7816(data, block_size):
    padding_len = block_size - len(data) % block_size
    if padding_len == 0:
    padding_len = block_size
    padding = bytes([0x80] + [0x00] * (padding_len - 1))
    return data + padding

    def compute_retail_mac(k_mac_hex, e_ifd_hex):
    k_mac = bytes.fromhex(k_mac_hex)
    e_ifd = bytes.fromhex(e_ifd_hex)

    # Expand to 24-byte key: K1 || K2 || K1
    k_mac_24 = k_mac + k_mac[:8]

    # Pad using ISO/IEC 7816-4
    padded_data = pad_iso7816(e_ifd, 8)

    # Encrypt with 3DES CBC, IV = 0
    cipher = DES3.new(k_mac_24, DES3.MODE_CBC, iv=bytes(8))
    encrypted = cipher.encrypt(padded_data)

    # Retail MAC = last 8 bytes
    return encrypted[-8:].hex()

    # Test values
    k_mac = "D6E358ECDA0015E273EEEBB5B865082F"
    e_ifd = "DE3737718D12260E21D6237319004D54AD7239F6F56EDD41725431B2EB4000FB"

    mac = compute_retail_mac(k_mac, e_ifd)
    print("Retail MAC:", mac)


    So, it's the LUA implementation that does something weird, but what? And why it works with the passport?

    Here's the copy from cardseek

    function epass_create_session_keys()
    local sw, resp
    local rnd_icc
    local rnd_ifd
    local k_ifd = bytes.new(8,"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
    local S
    local iv = bytes.new(8,"00 00 00 00 00 00 00 00")
    local TDES_CBC = crypto.create_context(crypto.ALG_DES2_EDE_CBC+crypto.PAD_ZERO,KM_ENC);
    local e_ifd
    local m_ifd

    rnd_ifd = bytes.new(8,"00 00 00 00 00 00 00 00")
    sw, rnd_icc = card.send(bytes.new(8,"00 84 00 00 08"))

    if (sw~=0x9000) then return false end
    S = rnd_ifd .. rnd_icc .. k_ifd
    log.print(log.INFO,"***XXX*** RND_IFD " .. tostring(rnd_ifd))
    log.print(log.INFO,"***XXX*** RND_ICC " .. tostring(rnd_icc))
    log.print(log.INFO,"***XXX*** K_IFD " .. tostring(k_ifd))
    log.print(log.INFO,"***XXX*** S " .. tostring(S))
    e_ifd = crypto.encrypt(TDES_CBC,S,iv)
    local CBC_MAC = crypto.create_context(crypto.ALG_ISO9797_M3+crypto.PAD_ISO9797_P2,KM_MAC);
    m_ifd = crypto.mac(CBC_MAC,e_ifd)
    log.print(log.INFO,"***MAC*** KM_MAC " .. tostring(KM_MAC))
    log.print(log.INFO,"***MAC*** E_IFD " .. tostring(e_ifd))
    log.print(log.INFO,"***MAC*** MAC " .. tostring(m_ifd))
    cmd_data = e_ifd..m_ifd



    sw, resp = card.send(bytes.new(8,"00 82 00 00 28",cmd_data,0x28))

    if (sw==0x6300) then
    log.print(log.WARNING,"Perhaps you didn't enter the right MRZ data for this passport")
    end
    if (sw~=0x9000) then
    return false
    end
    R = crypto.decrypt(TDES_CBC,bytes.sub(resp,0,31),iv)
    k_icc = bytes.sub(R,16,31)
    k_seed = bytes.new(8)
    for i=0,15 do
    k_seed = bytes.concat(k_seed,bit.XOR(k_ifd:get(i),k_icc:get(i)))
    end
    SSC = bytes.sub(rnd_icc,4,7)..bytes.sub(rnd_ifd,4,7)
    KS_ENC, KS_MAC = epass_key_derivation(k_seed)
    return true
    end


    Best,

    Continue reading...

Compartilhe esta Página