unique ID of a smart card revealed

How to read a MIFARE UID using PC/SC

Introduction

This is the second article on using the PC/SC Smart Card API in Windows with PC/SC card readers. The code in these articles will be in C++11 using the Unicode character set and has been tested with Microsoft Visual Studio 2015 Community. It also assumed that the reader has a grasp of C++11 and Windows development. During the course of these articles we shall be developing a simple class for handling the smart card API.

Background

The article shows how to use the PC/SC Windows API to read the unique identifier (UID) from a contactless storage card. Each card contains an integrated chip with a permanent identification number, or UID. This number is created during the manufacturing process, it is sometimes referred to as the card serial number. The UID can be 4 bytes (32bit), 7 bytes (56Bit) or 10 bytes (80bit). It is also possible for a card to produce a random UID.

This code has been tested with the following :

Readers:  HID Omnikey 5021CL, ACS ACR122 & Identive CLOUD 3700 F
Cards:  MIFARE Classic 1K, MIFARE Ultralight, MIFARE DESFire EV1

Reading the UID

The steps required in reading the UID from a contactless card requires the following steps.

1. Get context handle (SCardEstablishContext)
2. Connect to the card on the reader (SCardConnect)
3. Send the Get Data Command using SCardTransmit. (See section 3.3.5.1.3 in Part 3 of the PC/SC specification for more details on this command. Link in References).

The challenge and potential sticking point is with SCardConnect, as one of its parameters requires the name of the card reader and also for it to work; a card needs to be on the card reader. It is possible to wait for a card to be presented but we shall discuss that in a future article. So we shall use the ListReaders function from our previous article (How to list smart card readers using PC/SC) to get our list of readers and connect to the first reader in the list. Like this:


CSmartcard smartcard;
 
try
{
	// initialise the smart-card class
	smartcard.Init();
 
	// get a list of attached readers
	auto readerList = smartcard.ListReaders();
 
	// and connect to the first one and read the UID from the card on it.
	if (readerList.size() >= 1)
	{
		// connect to the card reader
		smartcard.Connect(readerList[0]);
	} 
}
catch (const CSmartcardException &ex)
{
	_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
}

The new Connect member function is implemented as follows:


// connect to card on specified reader, throws CSmartcardException
void CSmartcard::Connect(CString &reader)
{
	DWORD protocols(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1);
	m_activeProtocol = 0;
 
	LONG ret = SCardConnect(m_hSC,
		reader,
		SCARD_SHARE_SHARED,
		protocols,
		&m_hCard,
		&m_activeProtocol);
 
	if (ret != SCARD_S_SUCCESS)
	{
		throw CSmartcardException(ret);
	}
}

If a card is not present then a CSmartcardException with SCARD_W_REMOVED_CARD (0x80100069) error code will be thrown. If the Init() function has not been called prior to calling this function then a CSmartcardException with SCARD_E_NO_READERS_AVAILABLE (0x8010002E) error code will be thrown.

Here we have a new handle (m_hCard, initialised to NULL in the constructor) of type HCARDHANDLE and this handle is used with functions that communicate with the card, which we acquire using SCardConnect. SCardConnect has the following syntax:


LONG WINAPI SCardConnect(
_In_ SCARDCONTEXT hContext,
_In_ LPCTSTR szReader,
_In_ DWORD dwShareMode,
_In_ DWORD dwPreferredProtocols,
_Out_ LPSCARDHANDLE phCard,
_Out_ LPDWORD pdwActiveProtocol
);

Most of these parameters are fairly straightforward, you will notice that I used SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 for the preferred protocols as specifying both protocol options allows the PC/SC reader driver to determine which of the two possible ISO7816-3 contact smartcard protocols is appropriate. For contactless cards neither protocol is actually applicable as the commands are pseudo APDUs communicated to the reader (or IFD). The communication between reader and card is handled automatically using the appropriate contactless protocols. But to keep the PC/SC interface happy, we’ll just allow both protocols here!

An APDU (Application Protocol Data Unit) is the communication unit between a smart card reader and a smart card. The structure of the APDU is defined by ISO/IEC 7816-4.

So to read the UID we need to send a GET DATA command APDU using the SCardTransmit function. The GET DATA command APDU has the following format:

Command Class INS P1 P2 Lc Data In Le
Get Data 0xFF 0xCA 0x00
0x01
0x00 xx

The options for P1 & P2 are:

P1 P2
0x00 0x00 UID is returned
0x01 0x00 all historical bytes from the ATS of a ISO 14443 A card without CRC
are returned

An ATS (Answer To Select) can be used to identify the manufacturer, card type and application.
The length of the UID returned will depend on the card used. To handle this we can create a member function to our CSmartcard class as follows:


// gets the UID from the current card, throws CSmardcardException
// returns as unsigned 64 bit unsigned int
uint64_t CSmartcard::GetUID()
{
	uint64_t uid(0);
 
	// check that have a card handle
	if (m_hCard == NULL)
	{
		throw CSmartcardException(SCARD_E_INVALID_HANDLE);
	}
 
	// create the get data APDU
	UCHAR sendBuffer[] =
	{
		0xff,		// CLA - the instruction class
		0xCA,		// INS - the instruction code
		0x00,		// P1 - 1st parameter to the instruction
		0x00,		// P2 - 2nd parameter to the instruction
		0x00		// Le - size of the transfer
	};
 
	UCHAR receiveBuffer[32];
	DWORD sendLength();
	DWORD receiveLength(_countof(receiveBuffer));
 
	// set up the io request
	SCARD_IO_REQUEST ioRequest;
	ioRequest.dwProtocol = m_activeProtocol;
	ioRequest.cbPciLength = sizeof(ioRequest);
 
	LONG ret = SCardTransmit(m_hCard, &ioRequest,
		sendBuffer, 		// the send buffer 
		_countof(sendBuffer),	// the send buffer length
		NULL,
		receiveBuffer, 		// the receive buffer 
		&receiveLength);		// the receive buffer length
 
	if (ret == SCARD_S_SUCCESS)
	{
		// have received a response. Check that did not get an error
		if (receiveLength >= 2)
		{
			// do we have an error
			if ((receiveBuffer[receiveLength - 2] != 0x90) ||
				(receiveBuffer[receiveLength - 1] != 0x00))
			{
				throw CAPDUException(
					&receiveBuffer[receiveLength - 2]);
			}
			else if (receiveLength > 2)
			{
				for (DWORD i = 0; i != receiveLength - 2; i++)
				{
					uid <<= 8;
					uid |= static_cast<uint64_t>(receiveBuffer[i]);
				}
			}
		}
		else
		{
			// didn't get a recognisable response,
			// so throw a generic read error
			throw CSmartcardException(ERROR_READ_FAULT);
		}
	}
	else
	{
		throw CSmartcardException(ret);
	}
 
	return uid;
}

This function can also throw a new exception type, its definition & implementation is below:


// exception class for APDU errors
class CAPDUException
{
public:
	CAPDUException(const UCHAR *error)
		: CAPDUException(error[0], error[1])
	{
	}
 
	CAPDUException(UCHAR e1, UCHAR e2)
	{
		m_errorCode = (static_cast<USHORT>(e1) << 8)
			| static_cast<USHORT>(e2);
	}
 
	// get the error code
	inline USHORT GetErrorCode() const
	{
		return m_errorCode;
	}
 
	inline CString GetErrorCodeText() const
	{
		CString str;
 
		str.Format(_T("%04x"), m_errorCode);
 
		return str;
	}
 
protected:
	USHORT m_errorCode;
};

To use our GetUID function we can do the following:


int _tmain(int argc, _TCHAR* argv[])
{
	CSmartcard smartcard;
 
	try
	{
		// initialise the smart-card class
		//smartcard.Init();
 
		// get a list of attached readers
		auto readerList = smartcard.ListReaders();
 
		// and connect to the first one and read the UID from the card on it.
		if (readerList.size() >= 1)
		{
			// connect to the card reader
			smartcard.Connect(readerList[0]);
 
			uint64_t uid = smartcard.GetUID();
 
			_tprintf(_T("UID: %I64X\n"), uid);
		}
 
	}
	catch (const CSmartcardException &ex)
	{
		_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
	}
 
	return 0;
}

When I ran the program with my test MIFARE cards & readers I got the following…

 

Card Readers
HID Omnikey 5021CL ACS ACR122 Identive CLOUD 3700 F
MIFARE Standard 1K UID: FE8C8C97 UID: FE8C8C97 UID: FE8C8C97
MIFARE Ultra light UID: 4891692BF2380 UID: 4891692BF2380 UID: 4891692BF2380
MIFARE DESFire EV1 UID: 437146AB73780 UID: 437146AB73780 UID: 437146AB73780

The MIFARE Ultra light & MIFARE DESFire UIDs are 56bit long and the MIFARE Standard UID is 32bits long.

Now what

As this sample stands it is not very useful as the GetUID function only works when a card is present on the card reader at that moment in time. What would be more useful would be to be able to wait for a card to be presented at a card reader and then read the UID or other data, which will be the subject of a future article on the use of PC/SC.

References

PC/SC specifications: Part 3 http://www.pcscworkgroup.com/specifications/html/pcsc3_v2.01.09/

Comments are closed.