Как прочитать ответ с кредитной карты через NFC

Мне нужно получить номер карты с кредитной карты с помощью NFC, а затем преобразовать его в правильную строку.

Вот что у меня есть до сих пор:

private static readonly string MASTERCARD_AID = "A0000000041010";
// ISO-DEP command HEADER for selecting an AID.
// Format: [Class | Instruction | Parameter 1 | Parameter 2]
private static readonly string SELECT_APDU_HEADER = "00A40400";
// "OK" status word sent in response to SELECT AID command (0x9000)
private static readonly byte[] SELECT_OK_SW = { (byte)0x90, (byte)0x00 };

// Weak reference to prevent retain loop. mAccountCallback is responsible for exiting
// foreground mode before it becomes invalid (e.g. during onPause() or onStop()).
private WeakReference<AccountCallback> mAccountCallback;

public interface AccountCallback
{
    void OnAccountRecieved(string account);
}

public LoyaltyCardReader(WeakReference<AccountCallback> accountCallback)
{
    mAccountCallback = accountCallback;
}
/**
 * Callback when a new tag is discovered by the system.
 *
 * <p>Communication with the card should take place here.
 *
 * @param tag Discovered tag
 */
public void OnTagDiscovered(Tag tag)
{
    IsoDep isoDep = IsoDep.Get(tag);
    if (isoDep != null)
    {
        try
        {
            // Connect to the remote NFC device
            isoDep.Connect();
            // Build SELECT AID command for our loyalty card service.
            // This command tells the remote device which service we wish to communicate with.
            byte[] command = BuildSelectApdu(MASTERCARD_AID);
            // Send command to remote device
            byte[] result = isoDep.Transceive(command);
            // If AID is successfully selected, 0x9000 is returned as the status word (last 2
            // bytes of the result) by convention. Everything before the status word is
            // optional payload, which is used here to hold the account number.
            int resultLength = result.Length; //should be 89
            byte[] statusWord = { result[resultLength - 2], result[resultLength - 1] };
            byte[] payload = new byte[resultLength - 2];
            Array.Copy(result, payload, resultLength - 2);
            bool arrayEquals = SELECT_OK_SW.Length == statusWord.Length;

            for (int i = 0; i < SELECT_OK_SW.Length && i < statusWord.Length && arrayEquals; i++)
            {
                arrayEquals = (SELECT_OK_SW[i] == statusWord[i]);
                if (!arrayEquals)
                    break;
            }
            if (arrayEquals)
            {
                //takes out cardname
                //int lengthWanted = 58;
                //byte[] newRes = new byte[resultLength - lengthWanted];
                //byte[] cardname = new byte[newRes.Length - 15]; 
                //Array.Copy(payload, newRes, resultLength - lengthWanted);
                //Array.Copy(payload, 16, cardname, 0, cardname.Length);

                // The remote NFC device will immediately respond with its stored account number
                string accountNumber = Encoding.UTF8.GetString(payload);
                //string accountNumber = ByteArrayToHexString(payload);

                // Inform CardReaderFragment of received account number
                AccountCallback accountCallback;
                if (mAccountCallback.TryGetTarget(out accountCallback))
                {
                    accountCallback.OnAccountRecieved(accountNumber);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Hmm: " + e);
            throw e;
            //Toast.MakeText(ctx, "hmmm: " + e, ToastLength.Short).Show();
        }
    }
}

/**
 * Build APDU for SELECT AID command. This command indicates which service a reader is
 * interested in communicating with. See ISO 7816-4.
 *
 * @param aid Application ID (AID) to select
 * @return APDU for SELECT AID command
 */
public static byte[] BuildSelectApdu(string aid)
{
    // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
    return HexStringToByteArray(SELECT_APDU_HEADER + (aid.Length / 2).ToString("X2") + aid);
}

/**
* Utility class to convert a byte array to a hexadecimal string.
*
* @param bytes Bytes to convert
* @return String, containing hexadecimal representation.
*/

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

/**
 * Utility class to convert a hexadecimal string to a byte string.
 *
 * <p>Behavior with input strings containing non-hexadecimal characters is undefined.
 *
 * @param s String containing hexadecimal characters to convert
 * @return Byte array generated from input
 */
private static byte[] HexStringToByteArray(string s)
{
    int len = s.Length;
    if (len % 2 == 1)
    {
        throw new ArgumentException("Hex string must have even number of characters");
    }
    byte[] data = new byte[len / 2]; //Allocate 1 byte per 2 hex characters
    for (int i = 0; i < len; i += 2)
    {
        ushort val, val2;
        // Convert each chatacter into an unsigned integer (base-16)
        try
        {
            val = (ushort)Convert.ToInt32(s[i].ToString() + "0", 16);
            val2 = (ushort)Convert.ToInt32("0" + s[i + 1].ToString(), 16);
        }
        catch (Exception)
        {
            continue;
        }

        data[i / 2] = (byte)(val + val2);
    }
    return data;
}

Я могу извлечь тип карточки, но остальная часть результата — тарабарщина, например, вопросительные знаки в коробках и так далее. Я пытался прочитать все, что мог найти на эту тему, но я просто не понимаю! :(

Я использую Xamarin в Visual Studio 2015 Enterprise.

Я не могу вспомнить, где я его взял, но большая часть кода взята из отличного примера, где автор использовал другой телефон в качестве эмулятора.


person Kalkunen    schedule 17.08.2016    source источник


Ответы (1)


Если ваша карта на самом деле является MasterCard (или практически любой платежной картой EMV), карта не вернет номер своей карты (фактически: основной номер счета, PAN) в ответ на команду выбора приложения (SELECT). Вместо этого вам нужно будет запросить у карты файлы данных и извлечь номер из этих файлов.

Таким образом, вы должны сначала ВЫБРАТЬ приложение MasterCard по его AID:

result = isoDep.Transceive(HexStringToByteArray("00A404007A000000004101000"));

Затем вы обычно вводите команду GET PROCESSING OPTIONS (см. Невозможно идентифицировать AFL на смарт-карте), чтобы обнаружить расположение записей данных. Однако вы также можете пропустить этот шаг и попытаться прочитать записи методом грубой силы.

Чтение записей методом грубой силы может выглядеть примерно так:

for (int sfi = 1; sfi < 10; ++sfi ) {
    for (int record = 1; record < 10; ++record) {
        byte[] cmd = HexStringToByteArray("00B2000400");
        cmd[2] = (byte)(record & 0x0FF)
        cmd[3] |= (byte)((sfi << 3) & 0x0F8);
        result = isoDep.Transceive(cmd);
        if ((result != null) && (result.Length >=2)) {
            if ((result[result.Length - 2] == (byte)0x90) && (result[result.Length - 1] == (byte)0x00)) {
                // file exists and contains data
                byte[] data = Arrays.CopyOf(result, result.Length - 2);
                // TODO: parse data
            }
        }
    }
}

Затем вам нужно будет выполнить поиск данных, возвращаемых для каждой записи, чтобы найти объект данных, содержащий PAN. См. этот ответ о том, как декодировать объекты данных, закодированные в TLV. Онлайн-парсер TLV можно найти здесь. PAN обычно кодируется в объекте данных с тегом 0x5A (см. здесь) .

Обратите внимание, что PAN, который вы можете прочитать с помощью NFC, может отличаться от PAN, напечатанного на карте.

person Michael Roland    schedule 17.08.2016
comment
Следует отметить, что это небезопасно (с точки зрения криптографии, поэтому существует вероятность подделки карт). Для подтвержденного распознавания карты (и ее PAN) вам необходимо выполнить полную транзакцию EMV, которая включает либо онлайн-авторизацию, либо автономную аутентификацию данных. Вы можете выполнить это как так называемую транзакцию с нулевым значением (платежную транзакцию с суммой 0), но тогда вам придется кодировать весь процесс оплаты! - person Dominik; 18.08.2016
comment
@Доминик Правильно. Для аутентификации карты определенно требуется больше, чем чтение PAN. - person Michael Roland; 18.08.2016
comment
Спасибо за вклад, я также могу добавить интересный источник, который я использовал, этот проект Java: com.github.devnied.emvnfccard!! - person Kalkunen; 24.08.2016