Как прочитать ответ с кредитной карты через 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.

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

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, напечатанного на карте.

Другие вопросы по тегам