Lesson 7. The card program: challenge-response
In picturesThere are no new parts in this lesson — the card's whole program, 207 commands. The main thing in it is challenge-response: the card proves that it is genuine without showing its secret.
7.1. The idea: a secret that never leaves
The number and the PIN can be spied on. So the terminal sends a random number — a challenge. The card mixes it with its key and answers. The bank knows the key and computes the same thing: if it matches, the card is genuine.
- the key never leaves the card;
- an overheard answer is useless: next time the challenge will be different.
Our cipher is a teaching one. The key is 32 bits, the challenge and the answer are 2 bytes each: there are only 65,536 answers, and the key can be found by brute force in seconds. Real cards have a key of 128 bits or more, the cipher is 3DES or AES, the challenge is 4 bytes, the answer 8 bytes; the long computations are done by a separate crypto coprocessor in the chip. An 8-bit processor does not limit the key length: the key is processed a byte at a time, in a loop — the way our program walks through the 4 key bytes. Our principle is the real one: the key never leaves the card.
7.2. The idea: a long answer in two commands
The answer is 2 bytes, but the tester lamps show one. So A answers with the first byte and R with the second. Real cards do the same: the terminal takes a long answer with a separate command, GET RESPONSE.
7.3. The program
The same project as in lesson 6. No new parts — only the program: the card's whole program, 207 commands.
Step 1. The program in the ROM
⚙ on ROM → button ⬆ → file 07-card.rom.json. Reset RST — the card sends the greeting.
What it consists of:
| Part | Labels | What it does |
|---|---|---|
| power-on | start | OK = 0 (PIN not entered), let go of the line, greeting $3B |
| parsing the command | loop | receive a byte, compare (CMP) with N, P, A, R — jump to its part |
| number | cmdN | answer with byte $10 — the first row of the matrix |
| PIN | cmdP, wrong, blocked | receive 2 bytes, compare with $11–$12; try counter TRY: 3 — blocked |
| challenge-response | cmdA, deny | only if OK: 4 rounds of the cipher with key $13–$16, answer H |
| rest of the answer | cmdR | answer L (like GET RESPONSE) |
| receiving a byte | rx, rxbit, rxg | wait for the start bit, 8 bits of 16 clocks, wait out the stop |
| sending a byte | tx, txbit, dly | start, 8 bits of 16 clocks, stop, a guard pause |
The cipher is 4 rounds over the two challenge bytes H, L: L = L + K (a key byte), H = H XOR L, then the pair H L is rotated left by 1 bit, as one 16-bit number. Challenge A5 3C, the key of card 1 5A C3 17 8E:
| Round | Key K | L + K | H XOR L | rotate: H | rotate: 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 |
The answer is 68 F7. A slightly different challenge — a completely different answer; a different key — too.
; The payment card program. One for all cards; the personal part is in the permanent memory $10–$17.; Every command is exactly one clock, so the bit length on the IO line is counted in commands: P = 16 clocks.;; Talking to the terminal:; reset → $3B (a greeting, like the first ATR byte of real cards); 'N' → account number; 'P' PIN1 PIN2 → $90 correct · $62/$61 wrong (tries left) · $60 card blocked; 'A' C1 C2 → R1 = first byte of cipher(challenge, key) — only after a correct PIN; otherwise $69; 'R' → R2 = second byte of the answer (like GET RESPONSE of real cards: an answer longer than a byte —; the terminal takes the rest with a separate command; each byte is visible on the adapter lamps); anything else → $6D (no such command) ; --- RAM (8 bytes) ---.equ B $00 ; byte: received / sent.equ N $01 ; bit counter.equ D $02 ; delay counter.equ H $03 ; first argument byte / high byte of the cipher.equ L $04 ; second argument byte / low byte of the cipher.equ OK $05 ; 1 = PIN is correct in this session; --- permanent memory (on transistors) ---.equ ACC $10 ; account number.equ PINH $11 ; PIN, digits 1–2.equ PINL $12 ; PIN, digits 3–4.equ K0 $13 ; key, 4 bytes.equ K1 $14.equ K2 $15.equ K3 $16; --- try register and port ---.equ TRY $18 ; wrong tries in a row (3 = blocked).equ IN $1C ; bit 0 = the IO line.equ OUT $1D ; bit 0 = 1 → pull the line to 0 start: LD #0 ST OK ; after a reset the PIN is not entered again ST OUT ; let go of the line LD #$3B ST B CALL tx ; greeting loop: CALL rx ; command 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 ; the second byte of the last answer JMP reply ; --- PIN check ---cmdP: CALL rx LD B ST H CALL rx LD B ST L LD #0 ST OK ; a new try — the earlier correct PIN does not count LD TRY CMP #3 JZ blocked LD H CMP PINH JNZ wrong LD L CMP PINL JNZ wrong LD #0 ST TRY ; correct: the try counter goes to zero LD #1 ST OK LD #$90 JMP replywrong: LD TRY ADD #1 ST TRY LD #3 SUB TRY ; how many tries are left OR #$60 JMP replyblocked: LD #$60 JMP reply ; --- challenge-response ---cmdA: CALL rx LD B ST H CALL rx LD B ST L LD OK JZ deny ; 4 rounds: L += K, H ^= L, rotate (H, L) left by 1 bit 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 ; the first byte; the second one by command 'R'reply: ST B CALL tx JMP loopdeny: LD #$69 JMP reply ; --- receiving a byte: wait for the start bit, then 8 bits top first, one every 16 clocks ---rx: LD IN AND #1 JNZ rx ; line is 1 — wait LD #8 ST N .rept 10 ; get to the middle of the first bit NOP .endrrxbit: NOP NOP NOP NOP NOP LD B ADD B ; shift B left ST B LD IN ; ← here we read the bit AND #1 ADD B ST B LD N SUB #1 ST N JNZ rxbit ; the loop is exactly 16 clocks LD #6 ST Drxg: LD D ; guard: wait out the terminal's stop bit (2 + 6 × 4 = 26 clocks) SUB #1 ST D JNZ rxg RET ; --- sending byte B: start 0, 8 bits top first, stop 1 ---tx: LD #1 ST OUT ; start bit .rept 7 NOP .endr LD #8 ST Ntxbit: LD B ADD B ; top bit → C ST B LD #0 ADC #0 ; A = C XOR #1 ; bit 1 → do not pull, 0 → pull ST OUT ; ← here the bit goes out on the line NOP NOP NOP NOP NOP LD N SUB #1 ST N JNZ txbit ; the loop is exactly 16 clocks NOP NOP NOP NOP NOP LD #0 ST OUT ; stop bit LD #8 ST Ddly: LD D ; guard interval: 2 + 8 × 4 = 34 clocks SUB #1 ST D JNZ dly RET7.4. Checking
Go down the rows from top to bottom: one row is one action. After SEND wait until the "exchange" lamp goes off (lamp).
| № | What to do | Lamps (● lit) | What it is |
|---|---|---|---|
| 1 | Clock — MAX | ||
| 2 | RST on and off | ○○●●●○●● 59 | greeting |
| 3 | switches T7…T0 = 0100 0001, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 4 | switches T7…T0 = 1010 0101, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 5 | switches T7…T0 = 0011 1100, press SEND | ○●●○●○○● 105 | A A5 3C without a PIN — refused ($69) |
| 6 | switches T7…T0 = 0101 0000, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 7 | switches T7…T0 = 0001 0010, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 8 | switches T7…T0 = 0011 0100, press SEND | ●○○●○○○○ 144 | P 12 34 — PIN correct |
| 9 | switches T7…T0 = 0100 0001, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 10 | switches T7…T0 = 1010 0101, press SEND | ○○○○○○○○ go off | the byte went, the card waits for the next one |
| 11 | switches T7…T0 = 0011 1100, press SEND | ○●●○●○○○ 104 | A A5 3C — first byte of the answer ($68) |
| 12 | switches T7…T0 = 0101 0010, press SEND | ●●●●○●●● 247 | R — second byte of the answer ($F7) |
AandRare two commands because the adapter lamps show one byte. Real cards do the same: the terminal takes a long answer with a separate command (GET RESPONSE).- After a reset (
RST) "PIN entered" is forgotten:Aanswers$69again. - The program is long (207 commands), the answer to
Acomes a little later — the card computes 4 rounds.
¿No te sale? Algo no funciona →