Determine Facility Code and Card Number from ATR in C#

6.8k views Asked by At

I have the following card reader HID Omnikey 5325. I have a contact-less card named HIS Proximity.

The number written on this card is 133593 42101044091-3.

By reading the card, I get the following ATR hex: 3B050002F10673

Using the folowing applications I have managed to see the following information.

PACS Probe Hello Prox

I need to extract the Facility Code and the Code Number in order to identify the card.

I have managed to find the following code, but I am missing a few pieces:

/// <summary>
    /// Extract a data item from the Wiegand raw data. The data item usually is something like a card 
    /// number or facility code.
    /// </summary>
    /// <param name="format"></param>
    /// <param name="identifier"></param>
    /// <returns></returns>
    public int getData(int format, int identifier)
    {
        int byteOffset = 0;
        if (m_rawWiegandData == null)
        {
            throw new Exception("no raw Wiegand data available");
        }
        //SELF-TEST
        byte[] testData_H10301 = {0x02,0x02,0x00,0x7a}; //CN=61, FC=1
        byteOffset = 0;
        int bitOffset = 1; // starting to count from right
        int numberOfBits = 16;
        int cn_h10301 = CardHex.FromByteArray(testData_H10301, byteOffset, bitOffset, numberOfBits);
        if (cn_h10301 != 61) throw new Exception("CardHex::FromArray(): System integrity error.");



        if (format == PacsDataFormat.HID_H10301 && identifier == PacsDataIdentifier.CARD_NUMBER) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 1, 16);
        //if (format == PacsDataFormat.HID_H10301 && identifier == PacsDataIdentifier.CARD_NUMBER) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 15, 16); //assuming 4-byte input, 6 leading 0 bits
        if (format == PacsDataFormat.HID_H10301 && identifier == PacsDataIdentifier.FACILITY_CODE) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 17, 8);
        //if (format == PacsDataFormat.HID_H10301 && identifier == PacsDataIdentifier.FACILITY_CODE) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 1, 8);
        if (format == PacsDataFormat.HID_H10302 && identifier == PacsDataIdentifier.CARD_NUMBER) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 1, 24);
        if (format == PacsDataFormat.HID_H10304 && identifier == PacsDataIdentifier.CARD_NUMBER) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 1, 19);
        if (format == PacsDataFormat.HID_H10304 && identifier == PacsDataIdentifier.FACILITY_CODE) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 20, 16);
        if (format == PacsDataFormat.HID_H10320 && identifier == PacsDataIdentifier.CARD_NUMBER)
        {
            long result = 0;
            // convert BCD encoded raw Wiegand skipping the least significant nibble
            //TODO: create new method in CardHex to convert from BCD to int 
            for (int k = 0; k < m_rawWiegandData.Length; k++)
            {
                int high = (int)(m_rawWiegandData[k] >> 4);
                int low = (int)(m_rawWiegandData[k] & 0x0F);
                result *= 10;
                result += high;
                if (k < m_rawWiegandData.Length - 1) // skip last digit i.e. nibble
                {
                    result *= 10;
                    result += low;
                }
            }
            return (int)result;     
        }

        // H10320 CN=12345678 dec=                      101111000110000101001110
        //        ATR_HIST(5125)=100100011010001010110011110001100 

        // H10320 CN=1 dec =     1
        //        ATR_HIST(5125)=10101

        // H10320 CN=99999999 dec =       101111101011110000011111111      1
        //        ATR_HIST(5125)=100110011001100110011001100110010100


        if (format == PacsDataFormat.HID_CORP1000 && identifier == PacsDataIdentifier.CARD_NUMBER) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 1, 20);
        if (format == PacsDataFormat.HID_CORP1000 && identifier == PacsDataIdentifier.FACILITY_CODE) return CardHex.FromByteArray(m_rawWiegandData, byteOffset, 21, 12);
        return 0;
    }
public static class PacsDataFormat
    {
        public const int UNKNOWN      = 0;
        public const int HID_H10301   = 1; // 26-bit, FAC,CN
        public const int HID_H10302   = 2; // 37-bit, CN
        public const int HID_H10304   = 3;
        public const int HID_H10320   = 4; // 32-bit, Clock-and-Data card; CN is BCD encoded
        public const int HID_CORP1000 = 5; // 35-bit, CIC,CN 
        public const int INDALA_FLEXPASS26 = 6; // 26-bit, FAC,CN
    }

    public static class PacsDataIdentifier
    {
        public const int UNKNOWN       = 0;
        public const int WEIGAND_RAW   = 1; // raw Weigand data
        public const int CARD_NUMBER   = 2; // card number as printed on card
        public const int FACILITY_CODE = 3; // facility code
        public const int CIC           = 4; // CIC
    }

I can't figure out the FromByteArray function. I have found a description here on page 69: http://www.intraproc.com/downloads/Fargo/HDP5000%20OLD/Omnikey%20CardMan%205121/Manuals/ok_contactless_developer_guide_an_en.pdf

EDIT: SOLUTION I have created the FromByteArray function that goes with the code thanks to @Chris Haas

    /// call example: FromByteArray(atrByteArray, byteOffset: 0, int bitOffset: 1, int numberOfBits: 16)
    /// call example: FromByteArray(atrByteArray, byteOffset: 0, int bitOffset: 17, int numberOfBits: 8)
    public static long FromByteArray(byte[] atrByteArray, int byteOffset, int bitOffset, int numberOfBits)
    {
        var hexString = ByteArrayToString(atrByteArray);

        var start_number = Int64.Parse( hexString, NumberStyles.HexNumber );

        Int64 a_26_only = start_number & 0x3FFFFFF; //26 bits, 11 1111 1111 1111 1111 1111 1111             



        Int64 result = (a_26_only >> bitOffset) & (long)(Math.Pow(2,numberOfBits)-1);       

        return result;
    }

    public static string ByteArrayToString(byte[] ba)
    {
      StringBuilder hex = new StringBuilder(ba.Length * 2);
      foreach (byte b in ba)
        hex.AppendFormat("{0:x2}", b);
      return hex.ToString();
    }

And usage:

        byte[] atrByteArray = new byte[] {59, 5, 0, 2 , 241, 6, 115};
        var cardNumber = FromByteArray(atrByteArray, 0, 1, 16);
        var facilityCode = FromByteArray(atrByteArray, 0, 17, 8);

        Console.WriteLine(string.Format("Card number is: {0} and facility code is: {1}", cardNumber, facilityCode));
1

There are 1 answers

1
Chris Haas On BEST ANSWER

I'm not sure what the full-length number 3B050002F10673 is but, per the spec, you're only interested in the right-most 26 bits of it.

Int64 start = 0x3B050002F10673;

Int64 a_26_only = start & 0x3FFFFFF; //26 bits, 11 1111 1111 1111 1111 1111 1111

Then, per the spec, the right-most bit is a parity bit, so after checking it you can discard it:

Int64 a_without_parity = a_26_only >> 1;

Finally, the card number is the right-most 16 bits:

Int64 card_number = a_without_parity & 0xffff;

And the facility code is the next 8 bits:

Int64 facility_code = (a_without_parity >> 16 ) & 0xff;