Search code examples
javac#cryptographysmartcardapdu

C# Secure Messaging decryption method


I'm trying to implement this decryption method from java to c#: https://github.com/ecsec/open-ecard/blob/f66ae48e7bbb2bb27a524e12d3febabf162c17c7/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L198C3-L198C90

/**
     * Decrypt the APDU.
     *
     * @param response the response
     * @param secureMessagingSSC the secure messaging ssc
     * @return the byte[]
     * @throws Exception the exception
     */
     private byte[] decrypt(byte[] response, byte[] secureMessagingSSC) throws Exception {
    ByteArrayInputStream bais = new ByteArrayInputStream(response);
    ByteArrayOutputStream baos = new ByteArrayOutputStream(response.length - 10);

    // Status bytes of the response APDU. MUST be 2 bytes.
    byte[] statusBytes = new byte[2];
    // Padding-content indicator followed by cryptogram 0x87.
    byte[] dataObject = null;
    // Cryptographic checksum 0x8E. MUST be 8 bytes.
    byte[] macObject = new byte[8];

    /*
     * Read APDU structure
     * Case 1: DO99|DO8E|SW1SW2
     * Case 2: DO87|DO99|DO8E|SW1SW2
     * Case 3: DO99|DO8E|SW1SW2
     * Case 4: DO87|DO99|DO8E|SW1SW2
     */
    byte tag = (byte) bais.read();

    // Read data object (OPTIONAL)
    if (tag == (byte) 0x87) {
        int size = bais.read();
        if (size > 0x80) {
        byte[] sizeBytes = new byte[size & 0x0F];
        bais.read(sizeBytes, 0, sizeBytes.length);
        size = new BigInteger(1, sizeBytes).intValue();
        }
        bais.skip(1); // Skip encryption header
        dataObject = new byte[size - 1];
        bais.read(dataObject, 0, dataObject.length);

        tag = (byte) bais.read();
    }

    // Read processing status (REQUIRED)
    if (tag == (byte) 0x99) {
        if (bais.read() == (byte) 0x02) {
        bais.read(statusBytes, 0, 2);
        tag = (byte) bais.read();
        }
    } else {
        throw new IOException("Malformed Secure Messaging APDU");
    }

    // Read MAC (REQUIRED)
    if (tag == (byte) 0x8E) {
        if (bais.read() == (byte) 0x08) {
        bais.read(macObject, 0, 8);
        }
    } else {
        throw new IOException("Malformed Secure Messaging APDU");
    }

    // Only 2 bytes status should remain
    if (bais.available() != 2) {
        throw new IOException("Malformed Secure Messaging APDU");
    }

    // Calculate MAC for verification
    CMac cmac = getCMAC(secureMessagingSSC);
    byte[] mac = new byte[16];

    synchronized (cmac) {
        ByteArrayOutputStream macData = new ByteArrayOutputStream();

        // Write padding-content
        if (dataObject != null) {
        TLV paddedDataObject = new TLV();
        paddedDataObject.setTagNumWithClass((byte) 0x87);
        paddedDataObject.setValue(ByteUtils.concatenate((byte) 0x01, dataObject));
        macData.write(paddedDataObject.toBER());
        }
        // Write status bytes
        TLV statusBytesObject = new TLV();
        statusBytesObject.setTagNumWithClass((byte) 0x99);
        statusBytesObject.setValue(statusBytes);
        macData.write(statusBytesObject.toBER());

        byte[] paddedData = pad(macData.toByteArray(), 16);
        cmac.update(paddedData, 0, paddedData.length);

        cmac.doFinal(mac, 0);
        mac = ByteUtils.copy(mac, 0, 8);
    }

    // Verify MAC
    if (!ByteUtils.compare(mac, macObject)) {
        throw new GeneralSecurityException("Secure Messaging MAC verification failed");
    }

    // Decrypt data
    if (dataObject != null) {
        Cipher c = getCipher(secureMessagingSSC, Cipher.DECRYPT_MODE);
        byte[] data_decrypted = c.doFinal(dataObject);
        baos.write(unpad(data_decrypted));
    }

    // Add status code
    baos.write(statusBytes);

    return baos.toByteArray();
    }

Here is my method i dont know how to implement the commented part:

       public byte[] decrypt(byte[] response, byte[] secureMessagingSSC)
        {
            using (MemoryStream bais = new MemoryStream(response))
            using (MemoryStream baos = new MemoryStream(response.Length - 10))
            {
                byte[] statusBytes = new byte[2];
                byte[] dataObject = null;
                byte[] macObject = new byte[8];
                byte tag = (byte)bais.ReadByte();

                if (tag == 0x87)
                {                
                    int size = bais.ReadByte();
                    if (size > 0x80)
                    {
                        byte[] sizeBytes = new byte[size & 0x0F];
                        bais.Read(sizeBytes, 0, sizeBytes.Length);
                        size = new BigInteger(1, sizeBytes).IntValue;
                    }
                    bais.Seek(1, SeekOrigin.Current); // Skip encryption header
                    dataObject = new byte[size - 1];
                    bais.Read(dataObject, 0, dataObject.Length);

                    tag = (byte)bais.ReadByte();
                }

                if (tag == 0x99)
                {

                    if (bais.ReadByte() == 0x02)
                    {
                        bais.Read(statusBytes, 0, 2);
                        tag = (byte)bais.ReadByte();
                    }
                }
                else
                {
                    throw new IOException("Malformed Secure Messaging APDU");
                }

                if (tag == 0x8E)
                {

                    if (bais.ReadByte() == 0x08)
                    {
                        bais.Read(macObject, 0, 8);
                    }
                }
                else
                {
                    throw new IOException("Malformed Secure Messaging APDU");
                }

                // Only 2 bytes status should remain
                if (bais.Length - bais.Position != 2)
                {
                    throw new IOException("Malformed Secure Messaging APDU");
                }

        // Calculate MAC for verification
        CMac cmac = getCMAC(secureMessagingSSC);
        byte[] mac = new byte[16];

        lock (cmac)
        {
            MemoryStream macData = new MemoryStream();

            // Write padding-content
            if (dataObject != null)
            {
                TLV paddedDataObject = new TLV();
                paddedDataObject.SetTagNumWithClass(0x87);
                paddedDataObject.SetValue(ByteUtils.Concatenate((byte)0x01, dataObject));
                macData.Write(paddedDataObject.ToBER(), 0, paddedDataObject.ToBER().Length);
            }
            // Write status bytes
            TLV statusBytesObject = new TLV();
            statusBytesObject.SetTagNumWithClass(0x99);
            statusBytesObject.SetValue(statusBytes);
            macData.Write(statusBytesObject.ToBER(), 0, statusBytesObject.ToBER().Length);

            byte[] paddedData = pad(macData.ToArray(), 16);
            cmac.BlockUpdate(paddedData, 0, paddedData.Length);

            cmac.DoFinal(mac, 0);
            mac = ByteUtils.Copy(mac, 0, 8);
        }

        // Verify MAC
        if (!ByteUtils.Compare(mac, macObject))
        {
            throw new GeneralSecurityException("Secure Messaging MAC verification failed");
        }
                baos.Write(statusBytes, 0, statusBytes.Length);


                return baos.ToArray();
            }
        }

I'm having always having 900 when i decrypt the encrypted value : You can test the code here : https://dotnetfiddle.net/33V9Bl


Solution

  • There are two issues in the code, one with authentication and one with decryption:

    Authentication issue:

    Authentication fails because your implementation of the TLV class and/or the associated classes is flawed. More prcisely, the bug lies in the porting of the enum TagClass. While enum types can contain static methods in Java, this is not possible in C# (see here). During porting, the static method TagClass.getTagClass() was simply not ported.

    A possible fix is to implement the missing method in the Tag class, e.g.:

    public static TagClass GetTagClass(byte octet)
    {
        byte classByte = (byte)((octet >> 6) & 0x03);
        switch (classByte)
        {
            case 0: return TagClass.UNIVERSAL;
            case 1: return TagClass.APPLICATION;
            case 2: return TagClass.CONTEXT;
            case 3: return TagClass.PRIVATE;
            default: throw new GeneralSecurityException("Unknown tag class: " + classByte);
        }
    }
    

    Additionally, in method Tag.FromBer(), replace line:

    TagClass tagClass = (TagClass)data[0];
    

    with line:

    TagClass tagClass = GetTagClass(data[0]);  
    

    With this change, authentication is successful.

    Decryption issue:

    The missing decryption can be implemented compactly, e.g. with Aes#DecryptCbc():

    using System.Security.Cryptography;
    ...
    using (Aes aes = Aes.Create())
    {
        aes.Key = keyENC;
        byte[] decyptedPadded = aes.DecryptCbc(dataObject, getCipherIV(secureMessagingSSC), PaddingMode.None);
        byte[] decrypted = Unpad(decyptedPadded);
        baos.Write(decrypted);
    } 
    

    With this change, the ciphertext is successfully decrypted.


    Edit: If, as noted in the comment, DecryptCbc() is not available in the .NET version used, it can alternatively be decrypted as follows:

    using (Aes aes = Aes.Create())
    {
        aes.Key = keyENC;
        aes.IV = getCipherIV(secureMessagingSSC);
        aes.Padding = PaddingMode.None;
        using (ICryptoTransform decryptor = aes.CreateDecryptor())
        {
            byte[] decryptedPadded = decryptor.TransformFinalBlock(dataObject, 0, dataObject.Length);
            byte[] decrypted = Unpad(decryptedPadded);
            baos.Write(decrypted, 0, decrypted.Length);
        }
    }