Log in

No account? Create an account

Dewi Morgan

Decoding magnetic strips

Decoding magnetic strips

Previous Entry Share Next Entry
[Edit: much kudos to anfractuosity for taking this idea and running with it, effectively making OCR software to read dusted magstripes :D]

I've recently become interested in how magnetic strips are encoded.

Most credit for this should go to an Australian chap going by the name anaglyph, who made a post on his blog pointing out that rust particles could be used to view magstripes.

He was investigating claims by a company that "tags", with magstripes on them, could be used to repel insect pests. I noticed that some pattern was available in his photo of the rust-dusted magstripe, and the data itself could be decoded with the naked eye.

I leave it to him to document the specifics of the tags, but what follows is a summary of my discoveries about decoding magstripes. Mostly so that I've got it in one place and can reproduce it in years to come if I need to :)

Converting magnetism to bits

Normally, data is stored by "magnetic flux reversal". Sure, there are other ways: sometimes it's stored as an analog sound wave like on audio tape, or some such. But pretty much every single card you're ever likely to see in use will use some variation of the following.

A completely blank card contains no data at all, not even zeroes. It has the magstripe magnetised as if a magnet had swiped past it (which is probably exactly what happened), so one end of the stripe is north, the other is south, and it's like a single large weak magnet.

That is, the strip's magnetisation will look something like:

Dusting a blank card with rust would give a fairly even covering, with slightly more clumped at the ends of the strip, because the magnetic flux is strongest at discontinuities (so I used capitals there).

A card encoded card with all zeroes, however, has the magnetisation of the card swapped once for each bit, so five consecutive bits would be encoded as five small sections of magnet with similar poles together:
  NnnnnsssS SssssnnnN NnnnnsssS SssssnnnN NnnnnsssS
      0         0         0         0         0

Ones are just like zeroes, but they change polarity exactly twice as often:
  NnsS SsnN NnsS SsnN NnsS SsnN NnsS SsnN NnsS SsnN
      1         1         1         1         1

That is, the little magnets for "1" are half the size of the ones for "0", so there's two of them for each "1".
A bunch of ones and zeroes will look like:
  NnsS SsnN SssssnnnN NnnnnsssS NnsS SsnN NnsS SsnN
      1         0         0         1         1

Now, the gaps I've shown between those magnets aren't really there, they squidge right up against each other, but they're to draw your attention to the points on each of those magnets where the magnetic flux is strongest.

Rust isn't picky about north or south, but it is picky about field strength. It sticks best where the field's strong. So what you get from the above bit pattern is dust that clumps something like:
  NnsS SsnN NnnnnsssN SssssnnnN NnsS SsnN NnsS SsnN
      1         0         0         1         1
or if you're using slightly less fine dust:

So zeroes look like "gaps", and ones look like "blobs". The size of these can be read by comparing them to the "clocking bits", which are nice and easy to read.

Converting bits to bytes

So now you know how to read ones and zeroes, how do you convert those into values? How they're encoded varies, but there's a surprising amount of standardisation.

Firstly, the magstripe is almost always located on the back of the card, and goes along it lengthways, 0.223 inches (5.56 mm) from the closest edge (which I'll call the "top"), and is almost always 0.375 inches (9.52 mm) wide.

The data is almost always recorded in three parallel "tracks", each 0.110 inches (2.79 mm) wide. In some cards, track 3 is missing, and a correspondingly narrower magstripe is used... but the reader typically won't know that, and will treat track 3 as still existing and just containing no data.

The bits are usually recorded at one of two densities: 210 bits per inch, or 75 bits per inch. Sometimes, different tracks use different densities, in which case normally tracks 1 and 3 are high density, and track 2 is low density.

If you look at the back of any credit card or other magstripe card, in "landscape" orientation, with the magstripe facing towards you, and closest to the "top" edge of the card, then track 1 is the row at the top of the magstripe, track 2 in the middle, and track 3 at the bottom, if it's there at all.

Each track can either contain 7-bit alphanumeric characters stored as DEC-SIXBIT plus one bit of odd parity, or 5-bit numeric characters stored as four-bit BCD plus one bit of odd parity. Most commonly (always, for financial cards), track 1 is 7-bit, tracks 2 and 3 are 5-bit.

But what does that mean, for us card-decoders? Well, parity makes life easy for us. It means we know what to do once we've read in a line of values like (read from left to right on the magstripe):

Parity means that one bit in each group (of 5 or 7) is either set to 1 or 0, in order to make sure there's always an odd number of 1s in the group. And looking at the above list of 1s and zeroes, there's only one way to split it. There's no way to split it into groups of 5 so that every group has either 1, 3, or 5 "1"s, and there's only a single way to split it into groups of seven:
1010001 0100101 0000100 1100100 0100101 0000100 1100100 1100100 0110010 1010010 1010010 0011010 1111111 1011000

Note that all those leading and trailing zeroes weren't grouped together to have odd parity, so we ignore them: if they were DATA zeroes, and part of the data, then they'd have had a 1 in them for the parity bit. Their actual purpose is as individual "clocking bits", to let the card reader calculate the speed the card's being swiped, and synch with that.

Converting bytes to characters

To convert the bytes to characters, first, ignore the last bit of each byte: it's the "parity" bit, which has already served its purpose.
101000 010010 000010 110010 010010 000010 110010 110010 011001 101001 101001 001101 111111 101100

Then, swap those bytes around: currently, they're stored least-significant-bit first, and we're used to reading numbers the other way round (this and the next steps can be done as a single step using a script).
000101 010010 010000 010011 010010 010000 010011 010011 100110 100101 100101 101100 111111 001101

Then, convert them to numbers (wincalc can do this for you, or just do it in your head).
5 18 16 19 18 16 19 19 38 37 37 44 63 13

Add 0x20 (32) to each value for 7-bit tracks, or 0x30 (48) for 5-bit tracks.
37 50 48 51 50 48 51 51 70 69 69 76 95 45

That gives you the ascii value, so convert that to characters.
% 2 0 3 2 0 3 3 F E E L _ -

Converting characters to values

But what does it *mean*?

Well, that depends on the data format. Once again, there's a fair bit of standardisation.

Sentinel characters

Sentinel characters are the "framing characters" that let the reader know when the swiping has begun and ended, so they are at the beginning and end of the data. They ideally also let the reader uniquely know which WAY the card was swiped, so it can properly decode the data. Because of this, their values are typically chosen so that the start sentinel cannot be confused with a reversed end sentinel.

5-bit tracks normally begin with a '%' (1010001) as the start sentinel, then have the data, then '?' (1111100) as the end sentinel, possibly followed by one last character.

7-bit tracks normally begin with ';' (11010) as the start sentinel, then have the data, then '?' (11111) as the end sentinel, possibly followed by one last character.

LRC characters

That "one last character" mentioned above is also considered part of the "framing characters", and like the start and end sentinel, is typically discarded by reading software. It's called the Longitudinal Redundancy Check (LRC) character, and is used to verify that the data is correct. For each bit in this character (other than the parity bit, which is worked out for this character as normal), it is set to 1 if there are an odd number of other bytes (including sentinels) that have that bit set, and 0 of there's an even number. This is most easily generated by XORing all the other characters together.

Separator characters

Since each track may store multiple fields of varying length, the fields need to be separated. The usual characters for this are '$' and '^' (7-bit tracks) and '=' (both 5 and 7-bit tracks).

What these fields MEAN, though, depends on the card, and is a topic for another post.

Further reading and references:

Whole bunch of magstripe docs
Track format of magnetic stripe cards - L. Padilla
Wikipedia page
Card-O-Rama: Magnetic Stripe Technology and Beyond by Count Zero, Phrack 37.
  • (Anonymous)
    When you write:

    NnsS SsnN SssssnnnN NnnnnsssS NnsS SsnN NnsS SsnN

    Is that really correct? Sometimes zeros are written starting with an S, other times starting with a N. From what you say, it appears that this is intentional, as they alternate. But then shouldn't the first zero after the one in your example be flipped if the rule is that like poles go together?

    That is, I think it should read

    NnsS SsnN NnnnnsssS SssssnnnN NnsS SsnN NnsS SsnN

    Am I correct?
  • (Anonymous)
    One other question. In a strange sense, because the sensor is only reading a stream of field discontinuities, it relies on the timing between these discontinuities to determine anything. After all, the pattern always alternates between North and South, so the timing (or spacing) plays an important role here to distinguish a one from two narrow zeros. Is that really how the readers work? They assume the swiper has a steady hand and swipes at a relatively constant rate so that short and long intervals can be distinguished?
    • [holy crap, someone who isn't a spammer found my blog! :P]

      Yes, exactly - if you swipe it all stoppy-starty, then unless the reader has wheels in to detect the rate you're moving it, it will read wrong. Just like, if you place a barcode on an uneven surface, the bars will appear to have inconsistent thicknesses to the scanner, so it won't scan.

      I think this need for ruggedly detecting failure is part of why there is so much error checking built into the format.

      I also suspect that this is why a lot of the readers in the past used to have instructions like "slide smoothly", but nowadays are more likely to have instructions like "slide rapidly", "swipe quickly", etc.

      I guess people took "smoothly" to mean "slowly and smoothly", and there was usually more variance in a slow, trying-to-be-smooth swipe: you might start off slow, but finish off maybe moving twice as fast. Or vice versa. In a quick swipe, your hand's momentum acts as a limiter for how much you can accelerate/decelerate, so the variance is less. So they changed the wording.

      That's just a guess, though.

      And as for the NnnnsssS thing... yup, you're completely right, I had it backwards, cut-n-paste bug: thanks! I'll edit that to fix.

      [edit: oh, and I did write a li'l script to do all the faff of converting a binary string from a magstripe into ascii/numbers, if you're interested I can dig that out again.]

      Edited at 2012-07-05 08:19 pm (UTC)
      • Your script

        Please post the script to convert the binary to ASCII. I know it'll be hard to convince you that I'm not a bot, but I'd find such a script useful
        • Re: Your script

          Only bots would be interested in scripts! Clearly, you must be a bot!

          Nontheless, http://www.dewimorgan.com/coding/magstrip.html is the script (javascript, so just view source).

          I'm afraid it's not as generic as I thought I'd done - I think I had an earlier version that would just take any string of 1s and 0s, and display all possible permutations of 5+1 and 7+1, even and odd parity, to see what it could get from them.

          I can rewrite that if it'd be terribly useful?
          • Re: Your script

            I'm playing with magstripes and Square. I wish I knew more about this. Generic code is always cool, but don't spend time you don't have. If you would be able to get in touch via email, that would be more useful, actually. :-)
            • Re: Your script

              Sorry about the delay responding!
              You can email me at: dewi@dewimorgan.com
  • Some true and some not ;)

    You are wrong about magnetic field, is not like "NnsS SsnN SssssnnnN NnnnnsssS NnsS SsnN NnsS SsnN" Stick a white label on the stripe and after put some iron oxide on it. You will be able to read the data with the naked eye. Is about the distances between the magnetic fields. You will know which is a 1 and which is a 0. Is like NS_NS_NS____NS, and this wil be a 1 and a 0.
    • Re: Some true and some not ;)

      The gaps you see between your clumps of rust/oxide are not because they are not magnetized there, but because the *direction* of the magnetization is not changing in those areas.

      You can see the same behavior with a bar magnet: when you put iron filings on it, the metal goes mostly to the ends of the magnet, not in the middle.

      So when you get a bar magnet, one big enough to hold in your hand:


      ...if you put paper over it, and sprinkle iron filings on top, the iron filings stick to the ends:


      So imagine if you got two bar magnets, and placed their north poles together:

      >>>> <<<<

      Then you dust them with iron. What would you see? You'd see the iron filings gathering mostly at the ends of the magnets like this:


      What if you add two more bar magnets, and put them on the end, like this:

      >>>> <<<< >>>> >>>>

      You'd see this:


      The two added magnets are both pointing in the same direction, so there's no increase in flux density between them, so the filings won't cling between them: they effectively become one long magnet.

      And that's exactly how a 1 and a 0 are encoded on the magnetic strip.

      There is no "between the magnetic fields", only "between the points of magnetic flux reversal".
Powered by LiveJournal.com