КАК CЧИТАТЬ MIFARE UID С ПОМОЩЬЮ ПК / SC

Lord777

Professional
Messages
2,583
Reputation
15
Reaction score
1,283
Points
113
ВСТУПЛЕНИЕ
Это вторая статья об использовании API смарт-карт PC / SC в Windows с кард-ридерами PC / SC. Код в этих статьях будет написан на C ++ 11 с использованием набора символов Unicode и протестирован в сообществе Microsoft Visual Studio 2015. Также предполагалось, что читатель знаком с разработкой на C ++ 11 и Windows. В ходе этих статей мы будем разрабатывать простой класс для работы с API смарт-карт.

ФОН
В статье показано, как использовать Windows API PC / SC для чтения уникального идентификатора (UID) с бесконтактной карты памяти. Каждая карта содержит встроенный чип с постоянным идентификационным номером или UID. Этот номер создается в процессе производства, иногда его называют серийным номером карты. UID может быть 4 байта (32 бит), 7 байтов (56 бит) или 10 байтов (80 бит). Карта также может выдавать случайный UID.

Этот код был протестирован со следующим:
Считыватели:
HID Omnikey 5021CL, ACS ACR122 и Identive CLOUD 3700 F
Карты: MIFARE Classic 1K, MIFARE Ultralight, MIFARE DESFire EV1

ЧТЕНИЕ UID
Действия, необходимые для чтения UID с бесконтактной карты, требуют следующих шагов.

1. Получите дескриптор контекста (SCardEstablishContext)
2. Подключитесь к карте на считывателе (SCardConnect)
3. Отправьте команду получения данных с помощью SCardTransmit. (См. Раздел 3.3.5.1.3 в Части 3 спецификации PC / SC для получения более подробной информации об этой команде. Ссылка в Справочниках).

Проблемой и потенциальным камнем преткновения является SCardConnect, поскольку для одного из его параметров требуется имя устройства чтения карт, а также для его работы; карта должна быть на кардридере. Можно дождаться предъявления карты, но мы обсудим это в следующей статье. Поэтому мы будем использовать функцию ListReaders из нашей предыдущей статьи (Как составить список устройств чтения смарт-карт с помощью PC / SC), чтобы получить наш список считывателей и подключиться к первому считывателю в списке. Нравится:
Code:
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());
}

Новая функция-член Connect реализована следующим образом:
Code:
// 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);
    }
}

Если карта отсутствует, будет выдано исключение CSmartcardException с кодом ошибки SCARD_W_REMOVED_CARD (0x80100069). Если функция Init () не была вызвана до вызова этой функции, будет выдано исключение CSmartcardException с кодом ошибки SCARD_E_NO_READERS_AVAILABLE (0x8010002E).

Здесь у нас есть новый дескриптор (m_hCard, инициализированный значением NULL в конструкторе) типа HCARDHANDLE, и этот дескриптор используется с функциями, которые взаимодействуют с картой, которую мы получаем с помощью SCardConnect. SCardConnect имеет следующий синтаксис:

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

Большинство этих параметров довольно просты, вы заметите, что я использовал SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 для предпочтительных протоколов, поскольку указание обоих вариантов протокола позволяет драйверу считывателя ПК / SC определять, какой из двух возможных протоколов контактных смарт-карт ISO7816-3 является подходящим. Для бесконтактных карт ни один протокол фактически не применим, поскольку команды представляют собой псевдо APDU, передаваемые считывателю (или IFD). Связь между считывателем и картой осуществляется автоматически с использованием соответствующих бесконтактных протоколов. Но чтобы интерфейс PC / SC оставался довольным, мы просто разрешим здесь оба протокола!

APDU (Application Protocol Data Unit) - это блок связи между устройством чтения смарт-карт и смарт-картой. Структура APDU определяется ISO / IEC 7816-4.

Итак, чтобы прочитать UID, нам нужно отправить APDU команды GET DATA, используя функцию SCardTransmit. APDU команды GET DATA имеет следующий формат:
КомандованиеКлассВP1P2LcДанные вто
Получить данные0xFF0xCA0x00
0x01
0x00хх

Варианты P1 и P2:
P1P2
0x000x00UID возвращается
0x010x00все исторические байты из ATS карты ISO 14443 A без CRC возвращаются

ATS (ответ на выбор) может использоваться для определения производителя, типа карты и приложения.
Длина возвращаемого UID будет зависеть от используемой карты. Чтобы справиться с этим, мы можем создать функцию-член для нашего класса CSmartcard следующим образом:
Code:
// 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;
}

Эта функция также может генерировать новый тип исключения, ее определение и реализация приведены ниже:
Code:
// 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;
};

Чтобы использовать нашу функцию GetUID, мы можем сделать следующее:
Code:
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;
}

Когда я запустил программу с моими тестовыми картами и считывающими устройствами MIFARE, я получил следующее…
КартаРидеры
HID Omnikey 5021CLСАУ ACR122Идентификационное ОБЛАКО 3700 F
MIFARE Standard 1KUID: FE8C8C97UID: FE8C8C97UID: FE8C8C97
MIFARE Ультра легкийUID: 4891692BF2380UID: 4891692BF2380UID: 4891692BF2380
MIFARE DESFire EV1UID: 437146AB73780UID: 437146AB73780UID: 437146AB73780

UID MIFARE Ultra light и MIFARE DESFire имеют длину 56 бит, а стандартный UID MIFARE - 32 бита.

ЧТО ТЕПЕРЬ
В этом примере он не очень полезен, поскольку функция GetUID работает только в том случае, если в данный момент карта присутствует в устройстве чтения карт. Что было бы более полезным, так это иметь возможность дождаться, пока карта будет представлена в устройстве чтения карт, а затем прочитать UID или другие данные, которые будут предметом будущей статьи об использовании PC / SC.

ИСПОЛЬЗОВАННАЯ ЛИТЕРАТУРА
Технические характеристики ПК / ПК: часть 3 http://www.pcscworkgroup.com/specifications/html/pcsc3_v2.01.09/
 
Top