Урок 7. Программа карты: вызов-ответ
В картинкахВ этом уроке нет новых деталей — вся программа карты, 207 команд. Главное в ней — вызов-ответ: карта доказывает, что она настоящая, не показывая своего секрета.
7.1. Идея: секрет, который не выходит наружу
Номер и ПИН можно подсмотреть. Поэтому терминал присылает случайное число — вызов. Карта перемешивает его со своим ключом и отвечает. Банк знает ключ и считает то же самое: совпало — карта настоящая.
- ключ никогда не выходит из карты;
- подслушанный ответ бесполезен: в следующий раз вызов будет другим.
Наш шифр — учебный. Ключ 32 бита, вызов и ответ по 2 байта: ответов всего 65 536, и ключ подбирается перебором за секунды. У настоящих карт ключ 128 бит и больше, шифр — 3DES или AES, вызов 4 байта, ответ 8 байт; длинные вычисления считает отдельный криптосопроцессор в чипе. 8 бит процессора длину ключа не ограничивают: ключ обрабатывают по байту за раз, петлёй — как наша программа обходит 4 байта ключа. Принцип у нас настоящий: ключ не выходит из карты.
7.2. Идея: длинный ответ — двумя командами
Ответ — 2 байта, а на лампах тестера виден один. Поэтому A отвечает первым байтом, R — вторым. Настоящие карты делают так же: длинный ответ терминал забирает отдельной командой GET RESPONSE.
7.3. Программа
Тот же проект, что в уроке 6. Новых деталей нет — только программа: вся программа карты, 207 команд.
Шаг 1. Программа в ПЗУ
⚙ у ROM → кнопка ⬆ → файл 07-card.rom.json. Сброс RST — карта шлёт приветствие.
Из чего она состоит:
| Часть | Метки | Что делает |
|---|---|---|
| включение | start | OK = 0 (ПИН не введён), линию отпустить, приветствие $3B |
| разбор команды | loop | принять байт, сравнить CMP с N, P, A, R — переход на свою часть |
| номер | cmdN | ответить байтом $10 — первая строка матрицы |
| ПИН | cmdP, wrong, blocked | принять 2 байта, сравнить с $11–$12; счётчик TRY: 3 — блок |
| вызов-ответ | cmdA, deny | только если OK: 4 раунда шифра с ключом $13–$16, ответить H |
| остаток ответа | cmdR | ответить L (как GET RESPONSE) |
| приём байта | rx, rxbit, rxg | ждать старт-бит, 8 бит по 16 тактов, переждать стоп |
| передача байта | tx, txbit, dly | старт, 8 бит по 16 тактов, стоп, защитная пауза |
Шифр — 4 раунда над двумя байтами вызова H, L: L = L + K (байт ключа), H = H XOR L, потом пара H L поворачивается влево на 1 бит, как одно 16-битное число. Вызов A5 3C, ключ карты 1 5A C3 17 8E:
| Раунд | Ключ K | L + K | H XOR L | повернуть: H | повернуть: L |
|---|---|---|---|---|---|
| 1 | 5A | 96 | 33 | 67 | 2C |
| 2 | C3 | EF | 88 | 11 | DF |
| 3 | 17 | F6 | E7 | CF | ED |
| 4 | 8E | 7B | B4 | 68 | F7 |
Ответ — 68 F7. Чуть другой вызов — совсем другой ответ; другой ключ — тоже.
; Программа платёжной карты (Ф0). Одна на все карты; личное — в постоянной памяти $10–$17.; Каждая команда — ровно один такт, поэтому длительность бита на линии IO считаем командами: P = 16 тактов.;; Разговор с терминалом:; сброс → $3B (приветствие, как первый байт ATR настоящих карт); 'N' → номер счёта; 'P' ПИН1 ПИН2 → $90 верно · $62/$61 неверно (осталось попыток) · $60 карта заблокирована; 'A' В1 В2 → О1 = первый байт шифра(вызов, ключ) — только после верного ПИН; иначе $69; 'R' → О2 = второй байт ответа (как GET RESPONSE настоящих карт: ответ длиннее байта —; терминал забирает остаток отдельной командой; на лампах переходника виден каждый байт); другое → $6D (нет такой команды) ; --- ОЗУ (8 байт) ---.equ B $00 ; байт: принимаемый / отправляемый.equ N $01 ; счётчик бит.equ D $02 ; счётчик задержки.equ H $03 ; первый байт аргумента / старший байт шифра.equ L $04 ; второй байт аргумента / младший байт шифра.equ OK $05 ; 1 = ПИН верен в этом сеансе; --- постоянная память (на транзисторах) ---.equ ACC $10 ; номер счёта.equ PINH $11 ; ПИН, цифры 1–2.equ PINL $12 ; ПИН, цифры 3–4.equ K0 $13 ; ключ, 4 байта.equ K1 $14.equ K2 $15.equ K3 $16; --- регистр попыток и порт ---.equ TRY $18 ; неверные попытки подряд (3 = заблокирована).equ IN $1C ; бит 0 = линия IO.equ OUT $1D ; бит 0 = 1 → прижать линию к 0 start: LD #0 ST OK ; после сброса ПИН снова не введён ST OUT ; линию отпустить LD #$3B ST B CALL tx ; приветствие loop: CALL rx ; команда LD B CMP #'N' JZ cmdN CMP #'P' JZ cmdP CMP #'A' JZ cmdA CMP #'R' JZ cmdR LD #$6D JMP reply cmdN: LD ACC JMP reply cmdR: LD L ; второй байт последнего ответа JMP reply ; --- проверка ПИН ---cmdP: CALL rx LD B ST H CALL rx LD B ST L LD #0 ST OK ; новая попытка — прежний верный ПИН не в счёт LD TRY CMP #3 JZ blocked LD H CMP PINH JNZ wrong LD L CMP PINL JNZ wrong LD #0 ST TRY ; верно: счётчик попыток — в ноль LD #1 ST OK LD #$90 JMP replywrong: LD TRY ADD #1 ST TRY LD #3 SUB TRY ; сколько попыток осталось OR #$60 JMP replyblocked: LD #$60 JMP reply ; --- вызов-ответ ---cmdA: CALL rx LD B ST H CALL rx LD B ST L LD OK JZ deny ; 4 раунда: L += K, H ^= L, (H, L) повернуть влево на 1 бит LD L ADD K0 ST L LD H XOR L ST H LD L ADD L ST L LD H ADC H ST H LD L ADC #0 ST L LD L ADD K1 ST L LD H XOR L ST H LD L ADD L ST L LD H ADC H ST H LD L ADC #0 ST L LD L ADD K2 ST L LD H XOR L ST H LD L ADD L ST L LD H ADC H ST H LD L ADC #0 ST L LD L ADD K3 ST L LD H XOR L ST H LD L ADD L ST L LD H ADC H ST H LD L ADC #0 ST L LD H ; первый байт; второй — командой 'R'reply: ST B CALL tx JMP loopdeny: LD #$69 JMP reply ; --- приём байта: ждать старт-бит, потом 8 бит старшим вперёд, по одному каждые 16 тактов ---rx: LD IN AND #1 JNZ rx ; линия 1 — ждём LD #8 ST N .rept 10 ; дойти до середины первого бита NOP .endrrxbit: NOP NOP NOP NOP NOP LD B ADD B ; B сдвинуть влево ST B LD IN ; ← здесь читаем бит AND #1 ADD B ST B LD N SUB #1 ST N JNZ rxbit ; петля — ровно 16 тактов LD #6 ST Drxg: LD D ; защита: переждать стоп-бит терминала (2 + 6 × 4 = 26 тактов) SUB #1 ST D JNZ rxg RET ; --- передача байта B: старт 0, 8 бит старшим вперёд, стоп 1 ---tx: LD #1 ST OUT ; старт-бит .rept 7 NOP .endr LD #8 ST Ntxbit: LD B ADD B ; старший бит → C ST B LD #0 ADC #0 ; A = C XOR #1 ; бит 1 → не прижимать, 0 → прижать ST OUT ; ← здесь бит выходит на линию NOP NOP NOP NOP NOP LD N SUB #1 ST N JNZ txbit ; петля — ровно 16 тактов NOP NOP NOP NOP NOP LD #0 ST OUT ; стоп-бит LD #8 ST Ddly: LD D ; защитный интервал: 2 + 8 × 4 = 34 такта SUB #1 ST D JNZ dly RET7.4. Проверка
Иди по строкам сверху вниз: одна строка — одно действие. После SEND жди, пока погаснет лампа «обмен» (lamp).
| № | Что сделать | Лампы (● горит) | Что это |
|---|---|---|---|
| 1 | Clock — MAX | ||
| 2 | RST включить и выключить | ○○●●●○●● 59 | приветствие |
| 3 | тумблеры T7…T0 = 0100 0001, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 4 | тумблеры T7…T0 = 1010 0101, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 5 | тумблеры T7…T0 = 0011 1100, нажать SEND | ○●●○●○○● 105 | A A5 3C без ПИН — отказ ($69) |
| 6 | тумблеры T7…T0 = 0101 0000, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 7 | тумблеры T7…T0 = 0001 0010, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 8 | тумблеры T7…T0 = 0011 0100, нажать SEND | ●○○●○○○○ 144 | P 12 34 — ПИН верный |
| 9 | тумблеры T7…T0 = 0100 0001, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 10 | тумблеры T7…T0 = 1010 0101, нажать SEND | ○○○○○○○○ гаснут | байт ушёл, карта ждёт следующий |
| 11 | тумблеры T7…T0 = 0011 1100, нажать SEND | ○●●○●○○○ 104 | A A5 3C — первый байт ответа ($68) |
| 12 | тумблеры T7…T0 = 0101 0010, нажать SEND | ●●●●○●●● 247 | R — второй байт ответа ($F7) |
AиR— две команды, потому что на лампах переходника виден один байт. Настоящие карты делают так же: длинный ответ терминал забирает отдельной командой (GET RESPONSE).- После сброса (
RST) «ПИН введён» забывается:Aснова отвечает$69. - Программа длинная (207 команд), ответ на
Aприходит чуть позже — карта считает 4 раунда.
Не получается? Что-то не работает →