Урок 7. Программа карты: вызов-ответ

В картинках

В этом уроке нет новых деталей — вся программа карты, 207 команд. Главное в ней — вызов-ответ: карта доказывает, что она настоящая, не показывая своего секрета.


7.1. Идея: секрет, который не выходит наружу

Номер и ПИН можно подсмотреть. Поэтому терминал присылает случайное число — вызов. Карта перемешивает его со своим ключом и отвечает. Банк знает ключ и считает то же самое: совпало — карта настоящая.

  • ключ никогда не выходит из карты;
  • подслушанный ответ бесполезен: в следующий раз вызов будет другим.
Hбайт 1 вызоваLбайт 2 вызоваKбайт ключаADDXORHLповернуть пару влево на 1 битстарший бит H уходит в младший L× 4 раунда, каждый со своим байтом ключа K0…K3
Один раунд шифра карты: L прибавляет байт ключа, H смешивается с L через XOR, и пара H L поворачивается как одно 16-битное число. Четыре раунда — и вызов A5 3C превращается в 68 F7. Шифр учебный, а принцип настоящий: без ключа ответ не посчитать.
Наш шифр — учебный. Ключ 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 — карта шлёт приветствие.

Из чего она состоит:

ЧастьМеткиЧто делает
включениеstartOK = 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:

РаундКлюч KL + KH XOR Lповернуть: Hповернуть: L
15A9633672C
2C3EF8811DF
317F6E7CFED
48E7BB468F7

Ответ — 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        RET

7.4. Проверка

Иди по строкам сверху вниз: одна строка — одно действие. После SEND жди, пока погаснет лампа «обмен» (lamp).

№Что сделатьЛампы (● горит)Что это
1Clock — MAX
2RST включить и выключить○○●●●○●● 59приветствие
3тумблеры T7…T0 = 0100 0001, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
4тумблеры T7…T0 = 1010 0101, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
5тумблеры T7…T0 = 0011 1100, нажать SEND○●●○●○○● 105A A5 3C без ПИН — отказ ($69)
6тумблеры T7…T0 = 0101 0000, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
7тумблеры T7…T0 = 0001 0010, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
8тумблеры T7…T0 = 0011 0100, нажать SEND●○○●○○○○ 144P 12 34 — ПИН верный
9тумблеры T7…T0 = 0100 0001, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
10тумблеры T7…T0 = 1010 0101, нажать SEND○○○○○○○○ гаснутбайт ушёл, карта ждёт следующий
11тумблеры T7…T0 = 0011 1100, нажать SEND○●●○●○○○ 104A A5 3C — первый байт ответа ($68)
12тумблеры T7…T0 = 0101 0010, нажать SEND●●●●○●●● 247R — второй байт ответа ($F7)
  • A и R — две команды, потому что на лампах переходника виден один байт. Настоящие карты делают так же: длинный ответ терминал забирает отдельной командой (GET RESPONSE).
  • После сброса (RST) «ПИН введён» забывается: A снова отвечает $69.
  • Программа длинная (207 команд), ответ на A приходит чуть позже — карта считает 4 раунда.

Не получается? Что-то не работает →