8
votes

I am trying to read information off of a smartcard, using a contact-less OmniKey 5321 card reader.

Edit: Added a bounty.

I'm writing a C# 3 in .NET 3.5 program, so this is a Windows application.

The card has some information stamped onto it, that I would assume, in some way, is present in the card data (there is a photo of the card and reader below.)

The card has the following information stamped onto it:

1* 00447   21091328-32

When using the Diagnostics application that comes with the OmniKey card reader, I get the following information:

Smart Card Name: iCLASS 16KS UID:EE 74 0E 00 FB FF 12 E0
ATR            : 3B 8F 80 01 80 4F 0C A0 00 00 03 06 0A 00 1A 00 00 00 00 78
Protocol       : ISO 15693 (Part 2)

Now, here's some conversions I've considered:

  • 447 decimal = 1BF hexadecimal (not found)
  • 447 octal = 295 decimal (not found as BCD-type encoding)
  • 447 octal = 127 hexadecimal (not found)
  • 447 hexadecimal is not found

Here's my questions:

  • Is the "UID" number a unique number that I can rely on? I don't really care about the 447 number, all I need to know is that the information I pick from this card will uniquely identify it later, so that I can link it to the owner of the card
  • How would I go about reading the UID number? Using WINSCARD.DLL in Windows I can see that I get the "ATR" data, every single byte, but the UID is apparently not present in that part.

Here's the photo, if that gives you any information.

OmniKey reader back with card

6
have you found the way to get the card ID ? i am having the same problem, the reader returned Hex Serial number which is nothing about the written card number. Thanks :)user840575
@Abfam Sorry, we did not find a way to find that particular number stamped on the card, and hat to use the data actually retrieved from the card. This meant adding another swipe of the card during registration, to read the data on it, instead of just typing in a number like we hoped.Lasse V. Karlsen
The ATR is made by PCSC and it is a "standard" format for contactless ATR of a wired-logic PICC/VICC. This is determined from the initial ATR bytes: 3B 8F 80 01 (TS,T0,TD1-2). The meaning of most of the packet is "fixed"; the only "data" fields are at byte offset 12 (PIX.SS) and 13-4 (PIX.NAME) which don't help with obtaining UID or any of the other fields you want.escape-llc
From your ATR packet, bytes 12-14: PIX.SS 0A = ISO 15693, part 2 PIX.NN 00 1A = PicoPass 16Ksescape-llc
For what it's worth (and I know I'm really late to this party), the sample code here got me started on this.Onorio Catenacci

6 Answers

12
votes

I recently spend too many hours searching for an complete example of how to get the ATR from and OMNIKEY proximity card...

Now that I have my code working, I would like to share so others can benefit.

The best code that I found was from SpringCard. (thanks!)

Other examples I found were time-wasting and misleading, and did not work because the DllImport was wrong ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace Test
{
    public delegate void VoidDelegate();
    public delegate void CardPresented(string reader, byte[] cardData);

    public class ReaderList : IDisposable, IEnumerable<string>
    {
        public ReaderList()
        { }

        public void Dispose()
        {
            StopThread();
        }

        private Thread thread;
        private void StartThread()
        {
            if (thread != null)
                StopThread();

            thread = new Thread(Run);
            thread.IsBackground = true;
            bStopThread = false;
            thread.Start();
        }

        private void StopThread()
        {
            if (thread != null)
            {
                bStopThread = true;
                Thread.Sleep(50);
            }
            if (thread != null)
                thread.Abort();
            if (thread != null)
                thread.Join();
            thread = null;
        }

        private List<string> readerNames = new List<string>();
        private Dictionary<string, string> lastCardFound = new Dictionary<string, string>();
        public int ReaderCount
        { get { return readerNames.Count; } }

        public void Refresh()
        {
            if (thread == null)
                StartThread();
        }

        public event VoidDelegate ListChanged;
        public event CardPresented CardPresented;

        private bool bStopThread = true;
        private void Run()
        {
            IntPtr hContext = IntPtr.Zero;

            try
            {
                uint result = SCARD.EstablishContext(SCARD.SCOPE_SYSTEM, IntPtr.Zero, IntPtr.Zero, ref hContext);
                if (result != SCARD.S_SUCCESS)
                {
                    thread = null;
                    return;
                }

                uint notification_state = SCARD.STATE_UNAWARE;

                while (!bStopThread)    // loop 1 - build list, then iterate
                {
                    SCARD.ReaderState[] states = new SCARD.ReaderState[ReaderCount + 1];
                    states[0] = new SCARD.ReaderState(@"\\?PNP?\NOTIFICATION");
                    states[0].dwCurrentState = notification_state;

                    int iState = 0;
                    if (readerNames != null)
                        foreach (string s in readerNames)
                        {
                            iState++;
                            states[iState] = new SCARD.ReaderState(s);
                            states[iState].dwCurrentState = SCARD.STATE_UNAWARE;
                        }

                    while (!bStopThread)    // loop 2 - iterate over list built above
                    {
                        result = SCARD.GetStatusChange(hContext, 250, states, (uint)states.Length);
                        if (result == SCARD.E_TIMEOUT)
                            continue;
                        if (result != SCARD.S_SUCCESS)
                            break;

                        bool bReaderListChanged = false;
                        for (int i = 0; i < states.Length; i++)
                            if ((states[i].dwEventState & SCARD.STATE_CHANGED) != 0)
                                if (i == 0)
                                {
                                    // reader added or removed
                                    notification_state = states[0].dwEventState;

                                    // we want to replace the member in one step, rather than modifying it...
                                    List<string> tmp = GetReaderList(hContext, SCARD.GROUP_ALL_READERS);
                                    if (tmp == null)
                                        readerNames.Clear();
                                    else
                                        readerNames = tmp;

                                    if (ListChanged != null)
                                        ListChanged();
                                    bReaderListChanged = true;
                                }
                                else
                                {
                                    // card added or removed
                                    states[i].dwCurrentState = states[i].dwEventState;

                                    if ((states[i].dwEventState & SCARD.STATE_PRESENT) != 0)
                                    {
                                        byte[] cardData = new byte[states[i].cbATR];
                                        for (int j=0; j<cardData.Length; j++)
                                            cardData[j] = states[i].rgbATR[j];
                                        string thisCard = SCARD.ToHex(cardData, "");
                                        string lastCard;
                                        lastCardFound.TryGetValue(states[i].szReader, out lastCard);
                                        if (thisCard != lastCard)
                                        {
                                            lastCardFound[states[i].szReader] = thisCard;
                                            if (CardPresented != null)
                                                CardPresented(states[i].szReader, cardData);
                                        }
                                    }
                                    else
                                        lastCardFound[states[i].szReader] = "";                                        
                                }

                        if (bReaderListChanged)
                            break;  // break out of loop 2, and re-build our 'states' list

                    } // end loop 2
                } // end loop 1
            }
            catch (Exception ex)
            {
                //TODO: error logging
            }
            finally
            {
                if (hContext != IntPtr.Zero)
                    SCARD.ReleaseContext(hContext);
                thread = null;
            }
        }

        private List<string> GetReaderList(IntPtr hContext, string sGroup)
        {
            uint nStringLength = 0;
            uint result = SCARD.ListReaders(hContext, sGroup, null, ref nStringLength);
            if (result != SCARD.S_SUCCESS)
                return null;

            string sReaders = new string(' ', (int)nStringLength);
            result = SCARD.ListReaders(hContext, sGroup, sReaders, ref nStringLength);
            if (result != SCARD.S_SUCCESS)
                return null;
            List<string> list = new List<string> (sReaders.Split('\0'));
            for (int i = 0; i < list.Count; )
                if (list[i].Trim().Length > 0)
                    i++;
                else
                    list.RemoveAt(i);
            return list;
        }

        public IEnumerator<string> GetEnumerator()
        { return readerNames.GetEnumerator(); }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        { return readerNames.GetEnumerator(); }

    }

    public class SCARD
    {
        [DllImport("WinScard.dll", EntryPoint = "SCardEstablishContext")]
        public static extern uint EstablishContext(
            uint dwScope,
            IntPtr nNotUsed1,
            IntPtr nNotUsed2,
            ref IntPtr phContext);

        [DllImport("WinScard.dll", EntryPoint = "SCardReleaseContext")]
        public static extern uint ReleaseContext(
            IntPtr hContext);

        [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
        public static extern uint GetStatusChange(
            IntPtr hContext,
            uint dwTimeout,
            [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
                SCARD.ReaderState[] rgReaderState,
            uint cReaders);

        [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
        public static extern uint ListReaders(
            IntPtr hContext,
            string groups,
            string readers,
            ref uint size);

        #region Error codes
        public const uint S_SUCCESS = 0x00000000;
        public const uint F_INTERNAL_ERROR = 0x80100001;
        public const uint E_CANCELLED = 0x80100002;
        public const uint E_INVALID_HANDLE = 0x80100003;
        public const uint E_INVALID_PARAMETER = 0x80100004;
        public const uint E_INVALID_TARGET = 0x80100005;
        public const uint E_NO_MEMORY = 0x80100006;
        public const uint F_WAITED_TOO_LONG = 0x80100007;
        public const uint E_INSUFFICIENT_BUFFER = 0x80100008;
        public const uint E_UNKNOWN_READER = 0x80100009;
        public const uint E_TIMEOUT = 0x8010000A;
        public const uint E_SHARING_VIOLATION = 0x8010000B;
        public const uint E_NO_SMARTCARD = 0x8010000C;
        public const uint E_UNKNOWN_CARD = 0x8010000D;
        public const uint E_CANT_DISPOSE = 0x8010000E;
        public const uint E_PROTO_MISMATCH = 0x8010000F;
        public const uint E_NOT_READY = 0x80100010;
        public const uint E_INVALID_VALUE = 0x80100011;
        public const uint E_SYSTEM_CANCELLED = 0x80100012;
        public const uint F_COMM_ERROR = 0x80100013;
        public const uint F_UNKNOWN_ERROR = 0x80100014;
        public const uint E_INVALID_ATR = 0x80100015;
        public const uint E_NOT_TRANSACTED = 0x80100016;
        public const uint E_READER_UNAVAILABLE = 0x80100017;
        public const uint P_SHUTDOWN = 0x80100018;
        public const uint E_PCI_TOO_SMALL = 0x80100019;
        public const uint E_READER_UNSUPPORTED = 0x8010001A;
        public const uint E_DUPLICATE_READER = 0x8010001B;
        public const uint E_CARD_UNSUPPORTED = 0x8010001C;
        public const uint E_NO_SERVICE = 0x8010001D;
        public const uint E_SERVICE_STOPPED = 0x8010001E;
        public const uint E_UNEXPECTED = 0x8010001F;
        public const uint E_ICC_INSTALLATION = 0x80100020;
        public const uint E_ICC_CREATEORDER = 0x80100021;
        public const uint E_UNSUPPORTED_FEATURE = 0x80100022;
        public const uint E_DIR_NOT_FOUND = 0x80100023;
        public const uint E_FILE_NOT_FOUND = 0x80100024;
        public const uint E_NO_DIR = 0x80100025;
        public const uint E_NO_FILE = 0x80100026;
        public const uint E_NO_ACCESS = 0x80100027;
        public const uint E_WRITE_TOO_MANY = 0x80100028;
        public const uint E_BAD_SEEK = 0x80100029;
        public const uint E_INVALID_CHV = 0x8010002A;
        public const uint E_UNKNOWN_RES_MNG = 0x8010002B;
        public const uint E_NO_SUCH_CERTIFICATE = 0x8010002C;
        public const uint E_CERTIFICATE_UNAVAILABLE = 0x8010002D;
        public const uint E_NO_READERS_AVAILABLE = 0x8010002E;
        public const uint E_COMM_DATA_LOST = 0x8010002F;
        public const uint E_NO_KEY_CONTAINER = 0x80100030;
        public const uint W_UNSUPPORTED_CARD = 0x80100065;
        public const uint W_UNRESPONSIVE_CARD = 0x80100066;
        public const uint W_UNPOWERED_CARD = 0x80100067;
        public const uint W_RESET_CARD = 0x80100068;
        public const uint W_REMOVED_CARD = 0x80100069;
        public const uint W_SECURITY_VIOLATION = 0x8010006A;
        public const uint W_WRONG_CHV = 0x8010006B;
        public const uint W_CHV_BLOCKED = 0x8010006C;
        public const uint W_EOF = 0x8010006D;
        public const uint W_CANCELLED_BY_USER = 0x8010006E;
        public const uint W_CARD_NOT_AUTHENTICATED = 0x8010006F;
        #endregion

        public const uint SCOPE_USER = 0;
        public const uint SCOPE_TERMINAL = 1;
        public const uint SCOPE_SYSTEM = 2;

        public const string GROUP_ALL_READERS = "SCard$AllReaders\0\0";
        public const string GROUP_DEFAULT_READERS = "SCard$DefaultReaders\0\0";
        public const string GROUP_LOCAL_READERS = "SCard$LocalReaders\0\0";
        public const string GROUP_SYSTEM_READERS = "SCard$SystemReaders\0\0";

        public const uint STATE_UNAWARE = 0x00000000;
        public const uint STATE_IGNORE = 0x00000001;
        public const uint STATE_CHANGED = 0x00000002;
        public const uint STATE_UNKNOWN = 0x00000004;
        public const uint STATE_UNAVAILABLE = 0x00000008;
        public const uint STATE_EMPTY = 0x00000010;
        public const uint STATE_PRESENT = 0x00000020;
        public const uint STATE_ATRMATCH = 0x00000040;
        public const uint STATE_EXCLUSIVE = 0x00000080;
        public const uint STATE_INUSE = 0x00000100;
        public const uint STATE_MUTE = 0x00000200;
        public const uint STATE_UNPOWERED = 0x00000400;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct ReaderState
        {
            public ReaderState(string sName)
            {
                szReader = sName;
                pvUserData = IntPtr.Zero;
                dwCurrentState = 0;
                dwEventState = 0;
                cbATR = 0;
                rgbATR = null;
            }

            internal string szReader;
            internal IntPtr pvUserData;
            internal uint dwCurrentState;
            internal uint dwEventState;
            internal uint cbATR;    // count of bytes in rgbATR
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24, ArraySubType = UnmanagedType.U1)]
            internal byte[] rgbATR;
        }

        public static string ToHex(byte[] ab, string sDelim)
        {
            if (ab == null) return "<NULL>";
            return ToHex(ab, 0, ab.Length, sDelim);
        }

        public static string ToHex(byte[] ab, int offset, int len, string sDelim)
        {
            if (ab == null) return "<NULL>";
            StringBuilder sb = new StringBuilder();
            len = Math.Min(offset + len, ab.Length);
            for (int i = offset; i < len; i++)
                sb.Append(String.Format("{0:x02}", ab[i]).ToUpper() + sDelim);
            return sb.ToString();
        }

    }
}
4
votes

UID can be read via PC/SC 2.01 compliant function call against winscard.dll. For iCLASS cards it is typically 8 bytes long. Other cards return a 4 to 8 byte UID (it acts like a telephone number to identify multiple cards in the field and ultimately select one). The APDU can be exchanged via standard SCardTransmit() with CLA=0xFF to indicate access to the contactless storage card.

Marc

http://www.smartcard-api.com

NOTE: The UID does NOT reflect the card number printed on the card. The card number is part of the Wiegand data stored in the HID PAC application of application 1 on page 0 of that card.

2
votes

You can rely on UID but it seems truncated in your case :

UID:EE 74 0E 00 FB FF 12 E0

Uid are usually 16 bytes long.

You can read this Unique Identifier (UID): All ISO-compliant smart cards are provided with a UID number (akin to a VIN number on a vehicle). For interoperability purposes, a card’s UID is open and available to be read by all compliant readers. Since this unique number is not secured by keys, reading the UID of a smart card is comparable to reading a proximity card, mag stripe card or other technology that utilizes open, unsecured numbers.

http://www.xceedid.com/pdf/XC5937_Smart_whitepaper.pdf

0
votes
  1. Unique Identifier (UID): All ISO-compliant smart cards are provided with a UID number (akin to a VIN number on a vehicle). Source: http://www.accentalarms.com/specsheets/xceed/XceedIDSmartcardOverview.pdf
  2. About reading UID: check http://www.hidglobal.com/faqs.php?techCat=19, it seems to show how to do it.
0
votes

Even if the UID number is part of the standard and the standard says it should be unique worldwide you have to keep in mind that there are often manufacturers producing non-compliant (but working) cards.

Conclusion: As long you don't put something unique on the card you can not be sure that the link from card to a card owner is satisfied.

0
votes

The ATR (Answer To Reset) from a card only states what the card format is, the protocol, and checksums of that data. See ATR Parsing online

Now you know what card it is you then have to apply the appropriate (RS232/RS485/ZPL II/APDU - Smart card Application Protocol Data Unit as defined in ISO/IEC 7816-4) commands to get the UID (depending on whether it is cascade 1, 2 or 3, see ACG HF Multi ISO RFID Reader v1.0 page 50 aka Omnikey5553 RS232) - for 14443 it is up to 14 bytes long, occupying the first two blocks in the first 16 byte sector (sector 0), and has checksums embedded. (See the ISO14443 protocols or NXP Semiconductors MF1ICS50 specifications page 8).

For 15693, the RS232/RS485 commands will return a full 16 byte UID on (S)elect (Omnikey5553 RS232 and USB) but on APDU will only return the last 8 bytes (0xFF 0xCA 0x00 0x00 0x00 on a Omnikey5022) despite being the standard, as for the 15693 the card only responds with 8 bytes at a time. For 14443 you can read a sector at a time (32 bytes of 4 blocks of 8 bytes, logging into the sector prior to read/write for the S50) but for 15693 you can only read/write a block of 8 bytes at a time, and has other data in the returned buffer. You have to code whether you 'block' the data at 32 or at 8. This is using the standard SCardTransmit smart card protocol for Windows API. As the diagnostic application has returned 16 bytes, that is the UID of the card. In addition, some propriety 15693 use the 14443 inverted key to obfuscate and prevent modification, others hold a micro-program that performs the validation - either on the PC or on the card itself.

In all cases, this bears no relationship to what is printed ON the card - we use CODE128C barcodes printed on EV1 plastic cards that contain a GUID which then refers to the corresponding UID on a database for transactions. Others (like Wiegand etc) print other data such as area codes, key sets etc.

Additional problems occur when you attempt to write data to the 15693 - make sure you end your data at the 4 char interface else you have problems with prior data when you attempt to write a null '0x00' over an existing character in a block - so code for belt, braces and a piece of string after updating/writing to a card to make sure the data is as needed. If the whole block is of 'null's, no problem, as the data is written as a block of 4 characters. DO NOT blank the card user area unnecessarily, as they are NAND based and have finite writes. (After write read the card again to ensure the written data is as written!)

The UID should be unique within the card issue set, but it also depends on the card quantity order concerned - which is why the serial number has been expanded 2 times (the cascade number) and for once you should moderate your trust in the UID from an Apple phone as it violates the U in UID - it can be set to emulate another UID.

NDEF is another aspect of cards that should be understood - it is well explained in NFC NDEF standards, just be careful with the OTP and LOCK areas of 14443, as they are one-way only once set.

You could always use an Android phone with NFC and the TagData app to verify.