tags:

views:

31

answers:

1

I have an application that decodes data from a magnetic stripe reader. But, I'm having difficulty getting my calculated LRC check byte to match the one on the cards. If I were to grab 3 cards each with 3 tracks, I would guess the algorithm below would work on 4 of the 9 tracks in those cards.

The algorithm I'm using looks like this (C#):

private static char GetLRC(string s, int start, int end)
{
    int result = 0;
    for (int i = start; i <= end; i++)
    {
        result ^= Convert.ToByte(s[i]);
    }
    return Convert.ToChar(result);
}

This is an example of track 3 data that fails the check. On this card, track 2 matched, but track 1 also failed.

   0 1 2 3 4 5 6 7  8 9 A B C D E F
00 3 4 4 4 4 4 4 4  4 4 4 5 5 5 5 5 
10 5 5 5 5 5 6 6 6  6 6 6 6 6 6 6 7 
20 7 7 7 7 7 7 7 7  7 8 8 8 8 8 8 8 
30 8 8 8 9 9 9 9 9  9 9 9 9 9 0 0 0 
40 0 0 0 0 0 0 0 1  2 3 4 1 1 1 1 1 
50 1 1 1 1 1 2 2 2  2 2 2 2 2 2 2 3 
60 3 3 3 3 3 3 3 3 

The sector delimiter is ';' and it ends with a '?'.

The LRC byte from this track is 0x30. Unfortunately, the algorithm above computes an LRC of 0x00 per the following calculation (apologies for its length. I want to be thorough):

00 ^ 3b = 3b ';'
3b ^ 33 = 08
08 ^ 34 = 3c
3c ^ 34 = 08
08 ^ 34 = 3c
3c ^ 34 = 08
08 ^ 34 = 3c
3c ^ 34 = 08
08 ^ 34 = 3c
3c ^ 34 = 08
08 ^ 34 = 3c
3c ^ 34 = 08
08 ^ 35 = 3d
3d ^ 35 = 08
08 ^ 35 = 3d
3d ^ 35 = 08
08 ^ 35 = 3d
3d ^ 35 = 08
08 ^ 35 = 3d
3d ^ 35 = 08
08 ^ 35 = 3d
3d ^ 35 = 08
08 ^ 36 = 3e
3e ^ 36 = 08
08 ^ 36 = 3e
3e ^ 36 = 08
08 ^ 36 = 3e
3e ^ 36 = 08
08 ^ 36 = 3e
3e ^ 36 = 08
08 ^ 36 = 3e
3e ^ 36 = 08
08 ^ 37 = 3f
3f ^ 37 = 08
08 ^ 37 = 3f
3f ^ 37 = 08
08 ^ 37 = 3f
3f ^ 37 = 08
08 ^ 37 = 3f
3f ^ 37 = 08
08 ^ 37 = 3f
3f ^ 37 = 08
08 ^ 38 = 30
30 ^ 38 = 08
08 ^ 38 = 30
30 ^ 38 = 08
08 ^ 38 = 30
30 ^ 38 = 08
08 ^ 38 = 30
30 ^ 38 = 08
08 ^ 38 = 30
30 ^ 38 = 08
08 ^ 39 = 31
31 ^ 39 = 08
08 ^ 39 = 31
31 ^ 39 = 08
08 ^ 39 = 31
31 ^ 39 = 08
08 ^ 39 = 31
31 ^ 39 = 08
08 ^ 39 = 31
31 ^ 39 = 08
08 ^ 30 = 38
38 ^ 30 = 08
08 ^ 30 = 38
38 ^ 30 = 08
08 ^ 30 = 38
38 ^ 30 = 08
08 ^ 30 = 38
38 ^ 30 = 08
08 ^ 30 = 38
38 ^ 30 = 08
08 ^ 31 = 39
39 ^ 32 = 0b
0b ^ 33 = 38
38 ^ 34 = 0c
0c ^ 31 = 3d
3d ^ 31 = 0c
0c ^ 31 = 3d
3d ^ 31 = 0c
0c ^ 31 = 3d
3d ^ 31 = 0c
0c ^ 31 = 3d
3d ^ 31 = 0c
0c ^ 31 = 3d
3d ^ 31 = 0c
0c ^ 32 = 3e
3e ^ 32 = 0c
0c ^ 32 = 3e
3e ^ 32 = 0c
0c ^ 32 = 3e
3e ^ 32 = 0c
0c ^ 32 = 3e
3e ^ 32 = 0c
0c ^ 32 = 3e
3e ^ 32 = 0c
0c ^ 33 = 3f
3f ^ 33 = 0c
0c ^ 33 = 3f
3f ^ 33 = 0c
0c ^ 33 = 3f
3f ^ 33 = 0c
0c ^ 33 = 3f
3f ^ 33 = 0c
0c ^ 33 = 3f
3f ^ 3f = 00 '?'

If anybody can point out how to fix my algorithm, I would appreciate it.

Thanks, PaulH


Edit:

So that you can see if I'm accidentally missing any bytes in my LRC calculation or including the wrong ones (the final '.' is actually a '\r'). The complete data from all three tracks:

   0 1 2 3 4 5 6 7  8 9 A B C D E F
00 % U V W X Y Z 0  1 2 3 4 5 6 7 8 
10 9 9 A B C D E F  G H I J K L M N 
20 O P Q R S T U V  W X Y Z 1 2 3 0 
30 1 2 3 4 5 6 7 8  9 A B C D E F G 
40 H I J K L M N O  P Q R S T ? 3 ; 
50 1 2 3 4 5 6 7 1  2 3 4 5 6 7 8 9 
60 0 1 2 3 4 5 6 7  8 9 0 1 2 3 4 5 
70 6 7 8 9 0 ? 5 ;  3 4 4 4 4 4 4 4 
80 4 4 4 5 5 5 5 5  5 5 5 5 5 6 6 6 
90 6 6 6 6 6 6 6 7  7 7 7 7 7 7 7 7 
A0 7 8 8 8 8 8 8 8  8 8 8 9 9 9 9 9 
B0 9 9 9 9 9 0 0 0  0 0 0 0 0 0 0 1 
C0 2 3 4 1 1 1 1 1  1 1 1 1 1 2 2 2 
D0 2 2 2 2 2 2 2 3  3 3 3 3 3 3 3 3 
E0 ? 0 .

The GetLRC() algorithm re-instrumented as suggested to only XOR bytes that appear an odd number of times:

private static char GetLRC(string s, int start, int end)
{
    int result = 0;

    byte cur_byte = Convert.ToByte(s[start]);

    int count = 0;
    for (int i = start; i <= end; i++)
    {
        byte b = Convert.ToByte(s[i]);
        if (cur_byte != b)
        {
            if (count % 2 != 0)
            {
                result ^= cur_byte;
            }
            cur_byte = b;
            count = 0;
        }
        ++count;
    }

    if (count % 2 != 0)
    {
        result ^= cur_byte;
    }

    return Convert.ToChar(result);
}

The calculation steps taken by the new GetLRC() function:

00 ^ 3b = 3b ';'
3b ^ 33 = 08
08 ^ 31 = 39
39 ^ 32 = 0b
0b ^ 33 = 38
38 ^ 34 = 0c
0c ^ 33 = 3f
3f ^ 3f = 00 '?'

Question: Does the LRC byte come from the card itself or is it being added by the reader firmware? (i.e. perhaps this is a firmware bug)

+1  A: 

Can I make a suggestion? Store your data as run lengths and only do the xor if the run length is odd - and then only do it once (runLength & 0x01) times. That will get rid of a ton of the worthless bit work and make it clearer on what is occuring. Doing that yields:


Run Lengths:
(01,3b)(01,33)(10,34)(10,35)(10,36)(10,37)(10,38)(10,39)(10,30)
(01,31)(01,32)(01,33)(01,34)(10,31)(10,32)(09,33)(1,3f)

Doing the even/odd thing gives:


3b ^ 33 ^ 31 ^ 32 ^ 33 ^ 34 ^ 33 ^ 3f
        08-->39-->0B-->38-->0C-->3F-->00

Much simpler and cleaner to look at. My guess is that looking at your data, that there is an extra 30 somewhere in your data stream or 1 short. Adding that extra 30 gets you your answer:


3b ^ 33 ^ 31 ^ 32 ^ 33 ^ 34 ^ 33 ^ 30 ^ 3F
        08-->39-->0B-->38-->0C-->3F-->0F-->30

Beyond that, I'll keep digging...

Can you add some asserts or other validation to your input parameters? I'd hate to see out of bounds start/end causing excitement and/or a null string. Also, is there a possibility of an off by one with start end? Inclusive/exclusive data range? That could account for an extra 0x030 at the end of your data from a 0 stored at the end of your track 3 being converted to a 0x30. Also, is there any possibility of having either corrupt data or a corrupt LRU? Obviously, this is the kind of thing your check is trying to catch. Perhaps it caught something?

Michael Dorgan
I might believe it actually caught something if this didn't happen on so many different cards. I've made the suggested algorithm update, but, as you pointed out, that won't make an difference to the final result.You can see the data range used by the algorithm. I've added the complete track dump. If it is incorrect, please, let me know.
PaulH
Are you absolutely certain that you are not supposed to include that trailing 0 after the '?' in your answer (or perhaps the LRC is including it)? If you did, that would give you the LRC that it is expecting. It could also explain why some other lines fail and others don't if the final byte happened to be a 0x00 in some cases. I'd really look close at that.
Michael Dorgan
I think the idea is that the trailing `'0'` after the `'?'` *is* the LRC that's supposed to be compared-against.
caf
Then perhaps the LRC includes itself - a last bit flip? Dunno on this one.
Michael Dorgan
I'm not absolutely certain that it shouldn't include the LRC byte itself. However, if I include the LRC byte then only track 3 passes its check. Track 1 still fails and Track 2, which previously passed, now fails.
PaulH
Bleh. Do you have access to the code itself that made the LRCs you are working with? I assume not. How often are you getting failures - just this set of data or is it 66% overall?
Michael Dorgan
@Michael Dorgan - I assume the LRC is on the card itself. While that data above is from a 'test card', I'm also using driver's licenses and credit cards for my data set. So, I don't have access to the code that generated the LRC. I have to assume that because WalMart accepts my credit card, the LRC bytes on it are okay. I have yet to read a card where all tracks have an LRC that matches my calculated value. Most have at least 1 track that matches, though.
PaulH