Приветствую всех!

Давным-давно я уже писал один пост про магнитные карты, где рассказывалось о том, что это вообще за технология и как оно работает. Сейчас же речь пойдёт о ней с совершенно другой стороны — как управлять магнитным считывателем с TTL-интерфейсом и что с этим всем делать.



Итак, в сегодняшней статье поговорим о встраиваемых магнитных считывателях. Разберёмся, как подключить его и как обрабатывать данные с него. Традиционно будет много интересного.

О чём это я?


Магнитные карты обещают уйти в историю, наверное, с самого появления дешёвых и массовых смарт-карт. Тем не менее, несмотря на то, что их хоронят чуть ли не каждый год, окончательный закат этой технологии ещё далёк. А раз так — самое время вспомнить их ещё раз.

Где ещё применяются магнитные карты?


Думаю, все мы прекрасно понимаем, что одними банками использование их не ограничивается.



Такие карты всё так же частенько можно встретить в качестве служебных или дисконтных в магазинах. И если скидочные и клубные всё чаще имеют на себе только штрих-код, то вот различные карты кассиров, предназначенные для входа в систему, пока ещё живы.



Другим, не менее популярным вариантом являются различные электронные замки, системы контроля доступа или учёта рабочего времени. Сейчас многие из них переводятся на RFID или биометрию, но кое-где до сих пор применяются и предметы нашего сегодняшнего обсуждения.
Вот для примера карта с магнитной полосой от номера отеля в Питере.



Ну и самым интересным применением является, пожалуй, вот это, где связываются и банки, и СКУД. Это устройство контроля доступа к банкомату. Возможно, кому-то из вас даже доводилось самому воспользоваться таким.



Обычно их ставят на круглосуточные отделения для защиты банкомёта от вандалов, спящих бомжей и прочих нежелательных личностей. Всё просто — если проведённая карта недействительна или не обслуживается в этом банке, то попасть внутрь не удастся.



В некоторых банках от таких считывателей уже отказываются, заменяя их на камеры с распознаванием лиц.

Обзор оборудования


К чему же я упомянул эти самые системы контроля доступа? Всё дело в том, что ко мне в руки попал как раз такой считыватель. Экземпляры, использующиеся в СКУД, обычно не имеют привычного интерфейса USB, PS/2 или RS-232, а выдают наружу «сырые» данные, которые надо обрабатывать. А о том, как это делать — сейчас и поговорим.



А вот и сам считыватель. Это PerCo RM-3VR. Читает вторую дорожку, имеет стандартные уровни TTL. Можно также подрубить двухцветный светодиод для индикации.
Корпус металлический, всё же считыватель уличного исполнения.



Обратная сторона. Крышка на четырёх винтах, кабель для подключения к блоку управления.



А вот и внутренности. Сам считыватель прикручен к металлическому основанию, оно же является и нижней крышкой.



Плата. Она основана на каком-то чипе LH6516G, даташит на него найти не удалось. Рядом место для ещё одного (предположу, что были экземпляры ещё и для первой дорожки).

Подключение


От считывателя идут восемь проводов, нам же достаточно всего четырёх.
На данный девайс легко ищется документация, где есть и распиновка:



Такие устройства работают на самом низком уровне, микросхема лишь преобразует F/2F кодирование в строку бит. Таким образом, обработка данных остаётся за микроконтроллером.
Считыватель имеет выводы DATA и CLOCK понятного назначения. Данные считываются при низком уровне CLOCK. Собственно, это всё, что нужно знать для подключения.
Некоторые считыватели также имеют вывод CLS, где устанавливается низкий уровень при обнаружении карты, однако у моего считывателя его официально нет. Возможно, за него отвечает недокументированный зелёный провод, но я это не проверял.

Считываем данные


Ну что же, самое время попробовать прочитать карту.

В качестве управляющего МК взял всю ту же плату Arduino Mega, просто ради совместимости с пятивольтовыми уровнями.

Я решил не использовать вывод CLS, а опираться исключительно на данные CLOCK. Всё просто — если сигнал слишком долго не меняется, значит, карты больше нет и можно пробовать распознать то, что прочиталось. Получается примерно так:

int index = 0;
uint8_t * rawData;
unsigned long limit = 0;
bool isCardReading = 0;

void getData() {
  rawData[index] = uint8_t(digitalRead(3));
  index++;
  isCardReading = 1;
  limit = millis();
}

Состояние ножки DATA меняется достаточно медленно, так что вполне возможно использовать «медленный» digitalRead.
На вывод CLOCK навешиваем внешнее прерывание. По спаду сигнала будут считываться данные. В цикле проверяем, как оно там:

void callMSR() {
  if (millis() - limit > 500 && isCardReading == 1) {
    isCardReading = 0;
    processData(index);
    index = 0;
  }
}

Если данные не приходили больше, чем полсекунды, то флаг наличия карты снимается и вызывается функция, обрабатывающая полученные значения.
Для проверки выводим считанный массив бит в порт, проводим несколько раз картой и убеждаемся, что данные приходят одни и те же.

Декодирование


Что же теперь делать с этими битами? Считыватель получает данные со второй дорожки, где используется пятибитная кодировка: четыре бита данных и один бит чётности. На концах этого ряда стоят старт- и стоп-символы. Для первой дорожки это % и ?, для второй — ; и ?. После стоп-символа идёт один контрольный — LRC. Он также имеет свой бит чётности. Сами биты в каждом символе состоят из четвёрки в порядке от младшего к старшему, за которой следует бит чётности.
Итак, для начала необходимо понять, где вообще начинаются данные. Перед началом самих данных считыватель выдаёт некоторое количество нулевых бит, которые следует пропустить. Далее отщипываем очередную пятёрку битов и запихиваем её в очередной байт массива. Вообще, эту операцию можно совершать уже на этапе считывания из порта, обходясь при этом без буфера в триста байт, но тут для наглядности сделал так. Вариант с буфером такого размера может пригодиться при чтении сразу нескольких дорожек, чтобы просто задавать разные биты в зависимости от того, CLOCK какой дорожки изменился.
Теперь у нас есть набор из последовательностей по пять бит. Перед тем, как пытаться с этим что-то сделать, проверяем чётность, складывая все биты пятёрки и убеждаясь, что получилось нечётное число:

uint8_t checkParity(uint8_t input) {
  uint8_t data = ~input;
  uint8_t test = 0;
  for (int i = 0; i < 5; i++) {
    test += data & 1;
    data >>= 1;
  }
  if (test % 2 != 0) return 0;
  else return 1;
}

Магнитный считыватель выдаёт инвертированный сигнал, поэтому для начала с данными необходимо привести биты в символе к стандартному виду.
Кодовая таблица символов здесь используется вот такая:



Легко заметить, что символы идут в том же порядке, что и в таблице ASCII, соответственно, для преобразования пятибитного кода в текст необходимо выкинуть бит чётности, а к оставшемуся четырёхбитному числу прибавить код символа 0 (30h). Вот как-то так:

char convertToSymbol(uint8_t input) {
  uint8_t data = ~input;
  data &= B00001111;
  return data + '0';
}

Теперь очередь LRC. От всех данных отбрасываем биты чётности — у LRC он свой. Далее делаем последовательный XOR всех четвёрок бит и сравниваем с последним значением. Если оно совпадает — можно выводить готовую строку.
Получилось вот так:

void processData(int maxSize) {
  int currentBit = 0;
  uint8_t * digits = new uint8_t[40];
  uint8_t countSymbols = 0;
  uint8_t lrc = 0, target = 0;
  String s;
  for (int i = 0; i < 40; i++) digits[i] = 0;
  while (currentBit < maxSize && rawData[currentBit] == 1) currentBit++;
  for (int i = 0; i < 40; i++) {
    for (int n = 0; n < 5; n++) {
      digits[i] |= rawData[currentBit] << n;
      currentBit++;
    }
    if (!checkParity(digits[i])) {
      s += convertToSymbol(digits[i]);
      countSymbols++;
    }
  }
  target = ~digits[countSymbols - 1];
  target &= B00001111;
  for (int i = 0; i < countSymbols - 1; i++) {
    lrc ^= ~digits[i];
  }
  lrc &= B00001111;
  if (lrc == target) {
    s[countSymbols - 1] = '\0';
    Serial.println(s);
  }
  delete[] digits;
}

Перед тем, как выводить строку, убираем последний символ, так как LRC используется исключительно для проверки и больше нигде не печатается.
LRC также освобождает от большинства других проверок целостности данных, например, строка без начала или конца не пройдёт именно из-за несовпадения контрольной суммы.

Индикация


У меня нет штатного блока управления, поэтому индикаторный светодиод я подрубил к МК. Вместо запрета или разрешения на проход он теперь показывает успех чтения карты.
Кстати, несмотря на то, что в подобном оборудовании индикация часто уже рассчитана на непосредственное подключение, здешний светодиод требует наличия резистора.
В итоге вся программа получилась вот такая:

#define BUFFER_SIZE 300
#define INTERVAL 500
#define RED_LED 12
#define GREEN_LED 13

int index = 0;
uint8_t * rawData;
unsigned long limit = 0;
bool isCardReading = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(3, INPUT);
  Serial.begin(115200);
  attachInterrupt(0, getData, FALLING);
  rawData = new uint8_t[BUFFER_SIZE];
  Serial.println("Swipe card");
}

void getData() {
  rawData[index] = uint8_t(digitalRead(3));
  index++;
  isCardReading = 1;
  limit = millis();
}

uint8_t checkParity(uint8_t input) {
  uint8_t data = ~input;
  uint8_t test = 0;
  for (int i = 0; i < 5; i++) {
    test += data & 1;
    data >>= 1;
  }
  if (test % 2 != 0) return 0;
  else return 1;
}

char convertToSymbol(uint8_t input) {
  uint8_t data = ~input;
  data &= B00001111;
  return data + '0';
}

void processData(int maxSize) {
  int currentBit = 0;
  uint8_t * digits = new uint8_t[40];
  uint8_t countSymbols = 0;
  uint8_t lrc = 0, target = 0;
  String s;
  for (int i = 0; i < 40; i++) digits[i] = 0;
  while (currentBit < maxSize && rawData[currentBit] == 1) currentBit++;
  for (int i = 0; i < 40; i++) {
    for (int n = 0; n < 5; n++) {
      digits[i] |= rawData[currentBit] << n;
      currentBit++;
    }
    if (!checkParity(digits[i])) {
      s += convertToSymbol(digits[i]);
      countSymbols++;
    }
  }
  target = ~digits[countSymbols - 1];
  target &= B00001111;
  for (int i = 0; i < countSymbols - 1; i++) {
    lrc ^= ~digits[i];
  }
  lrc &= B00001111;
  if (lrc == target) {
    s[countSymbols - 1] = '\0';
    Serial.println(s);
    digitalWrite(GREEN_LED, HIGH);
    delay(500);
    digitalWrite(GREEN_LED, LOW);
  }
  else {
    digitalWrite(RED_LED, HIGH);
    delay(500);
    digitalWrite(RED_LED, LOW);
  }
  delete[] digits;
}

void callMSR() {
  if (millis() - limit > 500 && isCardReading == 1) {
    isCardReading = 0;
    processData(index);
    index = 0;
  }
}

void loop() {
  callMSR();
  // put your main code here, to run repeatedly:
}

Всё успешно работает.

Вот как-то так


Как можно видеть. подключить такой считыватель не так уж и сложно. Впрочем, даже при всех преимуществах карт с магнитной полосой (которых на самом деле не так уж и много) такой девайс отдельно от его контроллера сейчас является скорее игрушкой, нежели реально применимым где-то устройством.
Кстати, немного позабавило описание данной штуки от производителя:

Считыватель магнитных карт PERCo-RM-3VR предназначен для считывания идентификационной информации с пластиковых банковских карт с магнитной полосой и передачи ее в контроллер. Устройство считывает только информацию о принадлежности к той или иной банковской системе и сроке действия карты. Никакая защищенная банковская информация с других полос карты не может быть считана, в том числе данные о владельце карты, номере счета и т.д.

Не знаю, что они там имели в виду под конфиденциальной информацией, но, как нетрудно догадаться, девайс читает всю вторую дорожку карты целиком.

Такие дела.

Возможно, захочется почитать и это:



Комментарии (19)


  1. dphilipo
    01.09.2023 10:27
    +6

    Работая в группе ресторанов постоянно мучался со стирающимися магнитными полосками карт и магнитными головками в считывателях - у официантов по умолчанию магнитные карты в качестве ключа в ресторанном ПО.

    От ваших картинок у меня ужас - десктопный считыватель установлен явно в пыльном помещении - быстрый износ карт и головок обеспечен. Когда карта плохо считывается сотрудник начинает ей "пилить" считыватель и его никак не остановишь. Из-за этого износ происходит на порядок быстрее.

    А затем восстание луддитов.

    Решил проблему так - взял arduino micro, припаял к нему считыватель меток em-marine rc522 и написал простенький скетч, который при команде из терминала записывает на метку последовательность, эмулирует usb-клавиатуру и при приближении метки к антенне отправляет нажатия клавиш записанной на метку последовательности. Получился этакий эмулятор клавиатурного считывателя магнитных карт. Ресторанное ПО модифицировать не пришлось. Официанты использовали метки вместо карт.

    Параллельно появилась возможность использовать метки у сотрудников во всяких СКУД и системе контроля времени.
    Сэкономилось время техподдержки на выдаче копий магнитных карт.
    Повысилась достоверность данных.

    Рекомендую, если есть возможность, отказываться от карт с магнитной полосой в пользу радиочастотных меток.


    1. MaFrance351 Автор
      01.09.2023 10:27
      +4

      Сейчас даже продаются такие RFID-считыватели. И тоже с эмуляцией клавиатуры и выдачей туда UID. Легко гуглится по запросу "USB HID card reader".
      Что же до считывателя моего, то да, он был весьма пыльным ввиду уличного исполнения. Пришлось полностью разобрать и почистить. Из клавиатуры тоже вытряхнул немало мусора.


      А ещё тут вспомнилось, как где-то решили вместо магнитных карт использовать штрих-коды (например, как в скидочных картах "Магнита"). Жило это всё до тех пор, пока ушлые кассиры не прочитали пароль с более высокими полномочиями обычным сканером.


      1. dphilipo
        01.09.2023 10:27

        Всё что гуглится считывает серийный номер карты. Меня это не устраивает.
        Произвольную последовательность записать нельзя.
        На em-marine можно записать довольно много данных.
        Всё зависит от типа метки.


        1. MaFrance351 Автор
          01.09.2023 10:27

          А, может, всё же на Mifare? EM-Marine не содержат ничего кроме номера. А вот на Mifare есть сектора.


          1. dphilipo
            01.09.2023 10:27

            Да да. Простите. Было это всё 5 лет назад. Подзабыл


            1. MaFrance351 Автор
              01.09.2023 10:27

              У меня была безумная идея сделать аппаратный менеджер паролей — Teensy с этим самым считывателем и карты Mifare Ultralight, где в секторах были бы записаны пароли (зашифрованные). Прикладываешь карту и вводишь PIN — агрегат, эмулируя клавиатуру, сам вводит пароль.


  1. balamutang
    01.09.2023 10:27
    +2

    Не знаю, что они там имели в виду под конфиденциальной информацией, но, как нетрудно догадаться, девайс читает всю вторую дорожку карты целиком.

    дык просто это такая же лапша маркетинговая как и "Всё просто — если проведённая карта недействительна или не обслуживается в этом банке, то попасть внутрь не удастся."

    потому что никто не будет лить в СКУДы инфу о картах, так что зона-24 открывается обычно любой магнитной картой, хоть картой скидок из соседнего лабаза.

    Но с задачей не пустить бомжей ночевать в принципе справляется и так.


    1. MaFrance351 Автор
      01.09.2023 10:27

      О как. Увы, блока управления у меня от этой системы нет.
      Хотя так-то проверять BIN и дату из магнитной дорожки не такая уж и сложная задача.


      1. balamutang
        01.09.2023 10:27

        Ну если дату с горем пополам проверять можно (и если она не сбилась после последней замены аккумуляторов в СКУДе), то бин проверять смысла нет, тк обычно любой банкомат обслуживает карты всех банков (только комиссия отличается), т.е. пускать можно всех.


        1. MaFrance351 Автор
          01.09.2023 10:27

          На плате этого контроллера есть батарейка.


    1. MaFrance351 Автор
      01.09.2023 10:27

      Кстати, если каким-то образом получить доступ к контроллеру, то можно будет собирать дампы карт. В отличие от считывателей банкоматов этот никак не защищён.
      Ну, это я так, конечно.


      1. balamutang
        01.09.2023 10:27

        Ну контроллер обычно в защищенном помещении стоит, не рядом со считываталем. Теоретически тогда проще повиснуть на проводах от считывателя и слушать то что со считывателя идет. Но пинкод все равно нужен будет к дампу.


        1. MaFrance351 Автор
          01.09.2023 10:27

          А ПИН уже можно снимать камерой на банкомате...


      1. JerleShannara
        01.09.2023 10:27

        1) Данный контроллер, как и описано, считывает одну дорожку. На банковской карте их три.
        2) В современных картах на одной из дорожек вписано послание "вставь чипом!"
        Соответственно для удачного применения такой технологии надо:
        1) Модифицировать считыватель, чтобы он читал все три дорожки
        2) Узнать пинкод (да, я в курсе, что на одной из полос как бы записан CVV, но вот незадача — для CNP транзакций он непригоден, для них надо CVV2 добыть, либо опять же — "введите ваш пинкод для входа в помещение банкомата" в виде наклейки и опять-же модифицированный контроллер.
        3) Найти место, где можно будет такие карты прокатать (искать древности в США/ЮВА)
        По факту — проще свой официант в кафе, который снабжён модифицированным терминалом и прочие шутки вида "дайте пжалста вашу карту, терминал в подсобке, я его вынести не могу", которые нынче плохо работают.


        1. MaFrance351 Автор
          01.09.2023 10:27

          Данный контроллер, как и описано, считывает одну дорожку. На банковской карте их три.

          Так-то вторая дорожка как раз и используется банками. Первая была придумана авиакомпаниями и содержит всё то же самое, что и на второй, плюс имя держателя карты. Третья же на многих банковских картах и вовсе пуста.


          В современных картах на одной из дорожек вписано послание "вставь чипом

          Эти штуки появились где-то в начале нулевых. Карты с магнитной полосой тогда вовсю были в ходу. Вот и интересно, насколько безопасным такое было.


          и прочие шутки вида "дайте пжалста вашу карту, терминал в подсобке, я его вынести не могу", которые нынче плохо работают

          В Узбекистане в некоторых местах запросто можно такое услышать. Как и произнесение ПИНа на весь зал.


        1. grishkaa
          01.09.2023 10:27

          В современных картах на одной из дорожек вписано послание «вставь чипом!»
          Найти место, где можно будет такие карты прокатать (искать древности в США/ЮВА)
          Насколько я помню, даже самые современные терминалы имеют режим «блин, чип не читается, ладно, давайте полосой». Если хочется его включить специально, можно попробовать вставить карту неправильной стороной, например. Или ещё что-нибудь с ней сделать, чтобы присутствие чипа терминалом обнаруживалось, но прочитать его не получалось.



  1. grishkaa
    01.09.2023 10:27

    Считыватели на дверях банков обычно максимально тупые. В моём личном опыте им хватает вообще любой карты, главное чтобы была магнитная полоса с хоть какими-то данными — я из любопытства пробовал скидочные, по ним тоже пускает.


    1. MaFrance351 Автор
      01.09.2023 10:27

      Теперь понятно.
      Но блока управления от этой штуки не было, а пробовать самому не довелось. Так что вот…