Current Topic: Using a Panasonic ZU-M1121S1 Magnetic Card Reader and an Arduino to provide a Local PASS/FAIL entry system. With a card reader and a credit card access is either granted or denied. Additionally since the implementation is not concerned with the data on the card, only that the user has the card, and, since the card info is stored in a local database the data is one-way encrypted using a SHA1 Hash.
The Basic Card Reader...
The ZU-M1121S1 Magnetic Card reader from Panasonic is an extremely simplified reader. It also only reads track 2. Fortunately that is where the banking institute stores its data. Including credit cards. Therefore: A system that can read a card and compare its data to a database of cards can provide various levels of access to a facility. Like a house or a shop. Basically it grants permissions to the card holder. The key here being the holder. This method does not attempt to verify that the card holder is the rightful owner of the card.
The Panasonic ZU-M1121S1 Is A Very Bare Bones Decoder...
The reader provides three interface signals. All active-low (negative logic). There is a 'Frame Sync' (CLD) signal that is active (low) whenever a card is being swiped (loaded). And there are clock (RCL) and data (RDT) signals used to transfer the data from the card.
According to the original specifications manual...
So: According to this specification all a program needs to do is to wait for the CLD signal to go 'low' (active) and then read the RDT signal (and invert because it's negative logic) every time the RCL signal transitions from 'high' to 'low' and continue until the CLD signal returns to the 'high' state. Additionally there is a preamble of logic '0' until the first '1' bit signifying the first bit of the Frame which is 200 bits. The Frame is followed by logic '0' until the card is unloaded. There is also, according to this timing chart, an indeterminate time when the RCL signal stops toggling and remains low before the CLD signal goes 'high' (inactive).
Applying This To A Micro-Controller Like The Arduino...
The best design method is to connect the three interface signals to three bits on the same input port of the controller. This implementation chose to use PORTF which maps to A0, A1, A2, as inputs on the AVR 'mega2560' Arduino configuration.
Where: | |||
A0 = | RDT | ||
A1 = | RCL | ||
A2 = | RDT |
Using a single port in this manor allows for a synchronous read of the three signals. The rest is simple programming.
It is always good software design practice to make a set of (macro) definitions used to interface with the hardware. Not only is this useful for the code to propagate changes correctly but it also makes changing the hardware configuration a simple matter of redefining the macros.
#define CARDLOAD 0X04 #define CARDCLK 0X02 #define CARDDATA 0x01 #define TRK2_START_SENTINEL 0X0B #define TRK2_FIELD_SEPARATOR 0X0D #define TRK2_STOP_SENTINEL 0X0F #define MAXCARDDATA 204 #define MAXTRK2BYTES 40 #define MAXSHA1HASH 20 #define MAXHASHENTRY 6 #define GATE_TIMEOUT 500
The program will also need some local variable storage...
static uint16_t timeout = 0; static uint8_t cardbits[MAXCARDDATA]; static uint8_t carddata[MAXTRK2BYTES]; static uint8_t cardhash[MAXSHA1HASH];
Reading the card data...
As stated this was simply a matter of acquiring the three interface signals, waiting for the CLD signal to assert (low) and then reading the RDT signal each time the RCL signal asserts (high->low).
static int8_t read_card() { uint8_t i = 0; uint8_t preamble = 1; uint8_t swipe = 0; while((~swipe & CARDLOAD) && (i < MAXCARDDATA)) { swipe = card_swipe(); while(swipe & CARDCLK) { // Wait for RCL signal to go low (assert). swipe = card_swipe(); } if(~swipe & CARDDATA) { // This is a one-shot flag triggered by the first logic-1 bit. preamble = 0; } if(!preamble) { // Cache bit in buffer. Also invert negative logic. cardbits[i++] = ~swipe & CARDDATA; } while((~swipe & CARDLOAD) && (~swipe & CARDCLK)) { // Wait for RCL to go high (deassert). swipe = card_swipe(); } } return i; }
Applying Reasonable Security measures...
This approach considers a satellite installation with a local database. Basically; Vulnerable. Since the end goal is 'Secure' this implementation uses the SHA1 Hashing algorithm to store a fingerprint of the card. Therefore: Only the original card (or exact duplicate) can generate the same 'Fingerprint'. Additionally. The original card data can not be recovered from the 'Fingerprint' hash. As a result the database is useless outside the context of this black box system.
static const uint8_t hashcache[MAXHASHENTRY][MAXSHA1HASH] PROGMEM = { { 0XAF,0X21,0X9D,0X3D,0X7B,0X64,0X1B,0XD8,0XA7,0X46,0XF2,0XFB,0X5C,0X15,0XBB,0XFA,0XCD,0X12,0X09,0X46 }, { 0XF6,0X38,0X3E,0XCA,0X6F,0XC9,0X8C,0X5F,0X8B,0X6B,0XB2,0XCD,0X77,0X3D,0XBC,0XF2,0X60,0X80,0X72,0X5C }, { 0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF }, { 0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF }, { 0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF }, { 0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF } };
What is not considered is the application of privileges to an individual hash in the database. Primarily this would only be a minor extension and trivial to apply.
static int8_t match_hash() { uint8_t i; int8_t rtn = -1; for(i = 0; (i < MAXHASHENTRY) && rtn; i++) { rtn = memcmp_P(cardhash, hashcache[i], MAXSHA1HASH); } return rtn; }
The hash matching operation is only noteworthy because it compares an array in RAM to an array in ROM. On the AVR architecture this needs to be manually referenced or the compiler will assume that all data address are in RAM space and generate erroneous code.
static void purge_card(void) { memset(cardbits, 0, MAXCARDDATA); memset(carddata, 0, MAXTRK2BYTES); memset(cardhash, 0, MAXSHA1HASH); }
And finally... Any good security system would destroy the data, even in volatile memory, as soon as it was no longer needed.
Putting It All Together...
To verify all this a multi-digit LED segmented display was used to show the PASS/FAIL state. Literally. The LED will display 'PASS' when a recognized card is swiped. And 'FAIL' if the card is unauthorized. The pass state will continue for a few seconds where it is assumed that the access (entry) will be enabled. After that the system will reset itself and revert back to a lockdown (fail) state.
void loop() { uint8_t swipe; swipe = card_swipe(); if(~swipe & CARDLOAD) { // Card loaded. card_load(); read_card(); decode_card(); hash_card(); while(~swipe & CARDLOAD) { // Wait for card to unload. swipe = card_swipe(); } report_card(); if(match_hash() == 0) { pass_card(); } else { fail_card(); } // Flush/Invalidate security buffers. purge_card(); // Reset state counter. timeout = 0; delay(10); // Mystery delay... ? } else { // No card or card FAIL or IDLE. card_unload(); } timeout += 1; if(timeout > GATE_TIMEOUT) { // Expire (cancel) PASS state. clear_card(); timeout = 0; } }
Additional Resources That May Be Useful...
13111