Reading Magnetic Card Data

Magnetic cards have been around for a while. It feels only recently in the past few years that point of sale systems in the USA primarily switched to NFC and chipped cards for payments. But magnetic cards still have lots of usages, such as gift cards or door entry cards. While cleaning up, I found a magnetic card reader that was used for a project a long time ago. I happen to find this at a time where I’m already writing about NFC cards, just wrote about the Luhn’s Check algorithm used in credit card entry, and looking at how to read smart cards. I think it’s fitting to take a glance at reading information from magnetic cards. There are multiple standards for encoding information onto magnetic cards.

The physical unit I found was purchased for part of the functionality that was used in a demo for a concept in a past NRF Conference. I still have some video from the event.

This particular reader appears as an HID device to the computer. When a card is scanned, it generates keystrokes to the computer. For demonstration, I am using a gift card from “Raising Cane’s.” I’ve selected this card because I believe the card is cancelled and thus has no funds on it. But even if it does have funds on it and someone uses it, I suffer no lost since I am not positioned to suffer a lost. I also found an old American Express gift card whose funds have long been exhausted.

Encoding

The data on track 1 of the cards is encoded in 7 bits; 6 bits are data, 1 bit is for parity. This means that only 64 possible characters could be in the encoding. This isn’t UTF-8 or ASCII like you may be acustomed to. The reader does translate from these encoding to the equivalent keystrokes. Data on tracks 2 and 3 (if they exists) have 5 bits per character. Track 1 can have up to 79 characters, track 2 up to 40, and track 3 up to 107 characters.

I’ve got a tables at the end of this post that shows the actual encodings and their classifications. The reader will translate these to ASCII encodings for you. These are primarily included as a curiosity. There is more than one standard for encoding data on magnetic cards. With some of the hotel key cards that I have, I found some of them don’t work with this reader. This is somewhat expected, as a hotel might want to use a format that isn’t as easy to replicate. The varied gift cards, membership cards, and payment cards I tried generally worked fine.

Error Response

I want to start off talking about the error response since people often overlook those. If you were to use a card that the reader cannot process, it will return the string +E?. If you receive this response, the reading failed, possibly because the card is using an encoding that isn’t understood.

Sample Card Scan

Here’s the data that comes back when I read the “Raising Cane’s” gift card. I will refer to it in the following sections.

%B6000205500145033524^GIFT/RAISINGCANES^4211?;6000205500145033524=4211101685485?

Start and End Sentinel

When a card is read, the first character in the data will be a %. The last character will be a ?. If you were making something that were reading card data, you could use these characters to let your program know that it is receiving card data or that the data. Fields on the card are separated with the ^. Though not part of the card data, when my specific card reader has finished reading data, it will also send an enter keystroke. All of the data between the % and ? comes from the card. But there are some more delimiters in that block of data.

Track Delimiters and Field Delimiters

A magnetic card could have multiple tracks in parallel. The card reader returns all three tracks in a single stream of data. But the tracks are delimited with a semicolon(;). A single track could have multiple fields of data. Fields are separated with the caret (^) character on track 1 or an equal (=) character on tracks 2 and 3. Parsing out the sample card read I provided above, we end up with the following. The exact purpose of each of these fields could vary by card.

Track 1 Field 1:B6000205500145033524
Track 1 Field 2:GIFT/RAISINGCANES
Track 1 Field 3:4211?
Track 2 Field 1:6000205500145033524
Track 2 Field 2:4211101685485

Card Class

The very first character read on the card indicates the class/type of card being read. For all of the payment cards (both gift cards and credit cards) that I’ve encountered, this character is a ‘B’. From my readings and reading other cards that I have, I have found the following.

PrefixAssociation
BPayment or Gift Card
GGift Card
MMembership Card
GCGift Card
Card class based on the first character

Credit Card Format

While I found the way data is structured on a card to be variable, when I tried several credit cards, the construction of the data is consistent. Here is a modified data stream from a gift card.

%B377936453080000^THANK YOU                 ^2806521190729520                ?;377936453080000=280652119072952000000?

Breaking it out into fields, we have the following.

FieldPurposeData
Track 1 Field 1Primary Account Number377936453080000
Track 1 Field 2NameTHANK YOU
Track 2 Field 3Expiration Date (2028-06)
Service code (521)
Discretionary Data(190729520)
2806521190729520
Track 2 Field 1Credit Card Number377936453080000
Track 2 Field 2Expiration Date (2028-06)
Service code (521)
Discretionary Data (190729520)
280652119072952000000

You’ll notice that some of the data is on the card twice. I’m not quite sure of the reason for this. Is that for verifying the integrity of the data? To provide an alternative method of reading data for cheaper devices, allowing them to only read one track? This, I don’t know.

Reading the Data in Code

As I said the post on Luhn’s Check, don’t enter a real credit card number on the site where I have sample code posted. Though the site doesn’t actually communicate any data back to any site, I think it is still better to advise you not to enter data. But the site is there for you to examine the code. Feel free to download it to run in a local sandbox (where you can prohibit Internet communication) or view the source code to see how it works. You can find the code at https://j2inet.github.io/apps/magreader. If you would like to use a magnetic card reader that is similar to what I have, you can find it here (affiliate link):

Encoding Tables

BCD Data Format

CharacterHexFunction
00x00DATA
10x01DATA
20x02DATA
30x03DATA
40x04DATA
50x05DATA
60x06DATA
70x07DATA
80x08DATA
90x09DATA
:0x0AControl
;0x0BStart Sentinel
<0x0CControl
=0x0DField Separator
>0x0EControl
?0x0FEnd Sentinel

Alpha Encoded Data

CharacterHexFunction
[space]0x00Special
!0x01Special
0x02Special
#0x03Special
$0x04Special
%0x05Start Sentinel
&0x06Special
0x07Special
(0x08Special
)0x09Special
*0x0ASpecial
+0x0BSpecial
0x0CSpecial
0x0DSpecial
.0x0ESpecial
/0x0FSpecial
00x10DATA
10x11DATA
20x12DATA
30x13DATA
40x14DATA
50x15DATA
60x16DATA
70x17DATA
80x18DATA
90x19DATA
:0x1ASpecial
;0x1BSpecial
<0x1CSpecial
=0x1DSpecial
>0x1ESpecial
?0x1FEnd Sentinel
@0x20Special
A0x21DATA
B0x22DATA
C0x23DATA
D0x24DATA
E0x25DATA
F0x26DATA
G0x27DATA
H0x28DATA
I0x29DATA
J0x2ADATA
K0x2BDATA
L0x2CDATA
M0x2DDATA
N0x2EDATA
O0x2FDATA
P0x30DATA
Q0x31DATA
R0x32DATA
S0x33DATA
T0x3fDATA
U0x35DATA
V0x36DATA
W0x37DATA
X0x38DATA
Y0x39DATA
Z0x3ADATA
[0x3BSpecial
\0x3CSpecial
]0x3DSpecial
^0x3EField Separator
_0x3FSpecial

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.