0x00
values are ignored between TLV items, allowing in-place deletion (historically 0xff
too)Follow along yourself at https://murdoch.is/:/emvdecode with notes at https://murdoch.is/:/emvdecodenotes and repository at https://murdoch.is/:/emvdecoderepo
## Some helpful utilities for processing hex data
from hexutils import *
## Convert hex to binary
to_bin('AA')
'10101010'
## Strip whitespace around and within hex
strip_bytes("303\n 132 ")
'303132'
## Split hex into bytes for display
split_bytes("303\n 132 ")
'30 31 32'
## Count how many bytes in a hex string
len_bytes("303\n 132 ")
3
## Covert bytes to text (using ISO8859-1)
decode_bytes("303\n 132 ")
'012'
## Format a byte into a binary table
format_bytes('aa')
0xaa = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
## Split a byte into fields of specified length
format_bytes('aa', [1,2,0,5])
0xaa = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
1 | - | - | - | - | - | - | - | |
- | 0 | 1 | - | - | - | - | - | |
- | - | - | - | - | - | - | - | |
- | - | - | 0 | 1 | 0 | 1 | 0 |
## Take a certain number of bytes from a hex string
take("303\n 132 ", 2)
'30 31'
## Take a certain number of bytes from a hex string with an offset
take("303\n 132 ", 2, 1)
'31 32'
## Reference control parameter (SFI 2)
format_bytes('14', [5,3])
0x14 = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0 | - | - | - | |
- | - | - | - | - | 1 | 0 | 0 |
## Length of record is 0x97 = 151 bytes
0x97
151
## Response as a Python string
response="7081948C219F02069F03069F1A0295055F2A029A039C019F37049F35019F45029F4C089F34038D0C910A8A0295059F37049F4C089F08020002571352AAAAAAAAAAAA47D15122011407992700000F5F20134D5552444F43482F53544556454E204A2E44525F300202019F1F183134303739303030303030303030303932373030303030309F420208269F4401029F49039F37049F470103"
Response is 7081948C219F02069F03...
## Look at the first byte of the response (a tag)
take(response, 1)
'70'
## Application class, constructed, one-byte tag
format_bytes(_, [2,1,5]) # 0x70 is a READ RECORD response message template
0x70 = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
0 | 1 | - | - | - | - | - | - | |
- | - | 1 | - | - | - | - | - | |
- | - | - | 1 | 0 | 0 | 0 | 0 |
Response is 7081948C219F02069F03...
## First byte of length is 0x81...
take(response, 1, 1)
'81'
## b8 is 1, so the actual length is in the next byte
format_bytes(_)
0x81 = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
## The actual length is 0x94...
take(response, 1, 2)
'94'
## which is 148 in decimal
int(_, 16)
148
Response is 7081948C219F02069F03...
## The tag value is 148 bytes, starting after tag (1 byte) and length (2 bytes)...
take(response, 148, 1+2)
'8c 21 9f 02 06 9f 03 06 9f 1a 02 95 05 5f 2a 02 9a 03 9c 01 9f 37 04 9f 35 01 9f 45 02 9f 4c 08 9f 34 03 8d 0c 91 0a 8a 02 95 05 9f 37 04 9f 4c 08 9f 08 02 00 02 57 13 52 aa aa aa aa aa aa 47 d1 51 22 01 14 07 99 27 00 00 0f 5f 20 13 4d 55 52 44 4f 43 48 2f 53 54 45 56 45 4e 20 4a 2e 44 52 5f 30 02 02 01 9f 1f 18 31 34 30 37 39 30 30 30 30 30 30 30 30 30 30 39 32 37 30 30 30 30 30 30 9f 42 02 08 26 9f 44 01 02 9f 49 03 9f 37 04 9f 47 01 03'
## which is the whole response from the card
len_bytes(response) - 3
148
Response is 7081948C219F02069F03...
## The value is constructed so the next byte is a tag
take(response, 1, 1+2)
'8c'
## Context-specific class, primitive, 1-byte tag
format_bytes(_, [2,1,5]) # 0x8c - CDOL1
0x8c = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
1 | 0 | - | - | - | - | - | - | |
- | - | 0 | - | - | - | - | - | |
- | - | - | 0 | 1 | 1 | 0 | 0 |
Response is 7081948C219F02069F03...
## Next byte will be the length
take(response, 1, 1+2+1)
'21'
## b8 is 0 so this is a 1 byte length (0x21)...
format_bytes(_)
0x21 = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
## which is 16 in decimal
int(_, 16)
33
Response is 7081948C219F02069F03...
## The CDOL1 is 33 bytes, skipping the tags and lengths
cdol1 = take(response, 33, 1+2+1+1)
cdol1
'9f 02 06 9f 03 06 9f 1a 02 95 05 5f 2a 02 9a 03 9c 01 9f 37 04 9f 35 01 9f 45 02 9f 4c 08 9f 34 03'
## After the CDOL1 the next tag is the CDOL2
take(response, 1, 1+2+1+1 + 33) # 0x8d - CDOL2
'8d'
## with length 0x0c (12)
take(response, 1, 1+2+1+1 + 33 + 1)
'0c'
## So the CDOL2 can be extracted
cdol2 = take(response, 0x0c, 1+2+1+1 + 33 + 1+1)
cdol2
'91 0a 8a 02 95 05 9f 37 04 9f 4c 08'
CDOL1 is 9f 02 06 9f 03 06 9f 1a 02 95 05...
## DOL objects are a list of tags and lengths that describe how to
## send data to a card possibly unable to decode TLV data
take(cdol1, 1)
'9f'
## 9f starts a context-specific class, primitive, multi-byte tag
format_bytes(_, [2,1,5])
0x9f = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
1 | 0 | - | - | - | - | - | - | |
- | - | 0 | - | - | - | - | - | |
- | - | - | 1 | 1 | 1 | 1 | 1 |
## The next byte of the tag is 0x02
take(cdol1, 1, 1)
'02'
## 0x02 is the last byte of the tag, giving 0x9f02
format_bytes(_, [1,7]) # 0x9f02 - Amount, Authorised (Numeric)
0x02 = | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
---|---|---|---|---|---|---|---|---|
0 | - | - | - | - | - | - | - | |
- | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
## Next is the length of the data expected: 0x06
take(cdol1, 1, 1 + 1)
'06'
## Going back to the response, another 2-byte tag is at offset 78...
take(response, 2, 78) # 0x5f20 – Cardholder Name
'5f 20'
## which has length 0x13 (19)
take(response, 1, 80)
'13'
## This tag is ASCII encoded
take(response, 0x13, 81)
'4d 55 52 44 4f 43 48 2f 53 54 45 56 45 4e 20 4a 2e 44 52'
decode_bytes(_)
'MURDOCH/STEVEN J.DR'
## Another 2-byte tag is at offset 100...
take(response, 2, 100) # 0x5f30 – Service Code
'5f 30'
## with length 0x02
take(response, 1, 102)
'02'
## and in binary-coded decimal format: 201
strip_bytes(take(response, 2, 103))
'0201'
## At offset 57 we have a 1-byte tag (with length 59)...
take(response, 1, 57) # 0x57 – Track 2 Equivalent Data
'57'
## which is also in binary-coded decimal
## I've removed the middle of my card number ;-)
strip_bytes(take(response, 0x13, 59))
'52aaaaaaaaaaaa47d15122011407992700000f'
More on my research – https://murdoch.is/
Research group blog – http://www.benthamsgaze.org/