Making RC522 RFID cards un-copy-able [?]

Hi,
Is there a way to encode the card used for the RC522 RFID module so that copying the card will invalidate that copied card for the target reader software?

I'm thinking that, as each card has a serial number, doing an operation on a set of bytes resident in the card programmer interface using that unique serial and posting the result to a set of bytes on the card along with the payload might yield good results; the reverse happening in the reader. This should render any direct copy of the card inoperable since the unique immutable serial on the clone card would, upon being read and used, decrypt the copied bytes "incorrectly" in the target reader, by yielding a different (checksum) result.

Anyone done or tried anything similar?

I realise that the data on the card would be un-touched with this method, but I'm less bothered by this than I am by the ease with which the use of the card by the target system can so readily and easily be duplicated without some form of protection.
I am using two blocks for the data I transfer with the cards, leaving plenty of space for the addition of encoded bytes if this method is indeed workable.

You didn't write what type of attack you want to protect against. For most types of attack you must have a secret additionally to the serial number because otherwise an attacker simply does the same as you do when writing the card (calculating the correct checksum).
The most common way is probably to sign the content of the card and store the signature to the card. That way you can have the reading station's software publicly available but keep the control of writing valid cards.

pylon:
You didn't write what type of attack you want to protect against

My bad - I thought it was implicit in my description.
I have a project where I supply cards with different parameters (payload) coded into block 4, along with a unique set of 4 bytes (integrity) to ensure no random data is applied to the target system by spurious cards.
However, the cards are open to copying as they are, and this is not ideal.
I wish to work out a way to ensure that if a card is copied that the copy will be invalidated with respect to my target system.
If I am right, and the serial of the card is indeed immutable, then I think I should be able to incorporate the uniqueness of that serial into the written data, by operating on the integrity bytes with the serial before posting them. Anyone thereafter copying the card would, by putting the copied data into another card which would have a different serial, render the data unworkable on the target as the integrity bytes would not read true once they are decoded by the target system.
As I have said, I am not bothered at this point by lack of encryption of the data bytes; I am only concerned that the cards be proof against copying.
If I have misunderstood the basics of the serial number, then this will not work - but all I've read so far says that each card has a unique number that cannot be altered.
If anyone can tell me that this is not a pointless cul-de-sac of a rabbit hole, then I shall proceed with attempting the coding.
If there is a better, more secure way to protect these (MIFARE) cards from copying, then please let me know, thanks.

As I understand it, the MIFARE cards are indeed, already unable to be copied because they contain a serial number which is unalterable.

Paul__B:
As I understand it, the MIFARE cards are indeed, already unable to be copied because they contain a serial number which is unalterable.

That would not prevent someone from producing a card or card emulator with a writable serial number.
To secure a card against copying you would need a card that can prove that it contains the proper serial number without making that serial number readable. One way to do that is to use public-key cryptography in the card. The reader writes a random value to the card and reads back an encrypted version of the value. If the value decrypts to the chosen random value using the public key of the card then the card is known to contain the private key and is valid. I don't know if such a card is available for a reasonable cost. :slight_smile:

johnwasser:
That would not prevent someone from producing a card or card emulator with a writable serial number.

Emulator, sure. I do not think the chance of someone going to this trouble for my application is remotely likely, so not a concern atm.

johnwasser:
. . . use public-key cryptography in the card. . . . I don't know if such a card is available for a reasonable cost. :slight_smile:

So not a MIFARE card, readable by the RC522?
As it stands I can pop a 522 onto a Uno and with a minimum of publicly available code I can easily read the contents of one of my cards, copy that read to another card and hey-presto.
The data is not at all sensitive - but the use of the data loaded card is. The target system does not use the UID (serial) as it stands because that would necessitate my pre-programming each target with these numbers, which of course I don't have before I purchase new cards.
The cards trigger the target system to behave in a pre-determined way and the cards are wholly passive in this respect. I load a set of cards with parameters and the set gives the target system a multitude of possible setups - a neat way of guarding against fat-finger mess-ups of deep menu choices.
I don't want these cards to be copy-able by the simple method I've just mentioned.
Could I take the UID (4 bytes) and XOR that UID with a pre-defined set of bytes (key), and load the resultant 4 bytes (guard) onto an unused portion of the card data field. The idea being that the target system would need to recover that key correctly by reversing the XOR process with the serial & the guard from the presented card before it would be accepted. Were the card simply copied then the guard bytes would not decode correctly - I hope.
As a method it seems very simple - perhaps too simple. This is why I'm posting this query - to see if there is a better way to do what I'm trying to do.

The problem seems to resemble the "rolling key code" situation with KeeLoq and should be do-able.

You need to write to the card, a value determined according to the serial number by a process using a "secret" (method) known only to the code which writes and verifies the value. This requires - as does the KeeLoq - no "intelligence" or in this case, processing on the part of the card.

Thank you Paul__B.

Then I shall proceed with this concept and see how I get on.

I will post the code concept here once I've managed to get it working for me - . . . I may be gone some time :o

These two methods work very well in my software, but these examples are little more than detailed snippets

/**
 * ----------------------------------------------------------------------------
 * This is a MFRC522 example modified by OldManNoob to include 
 * the facility to add an "Original Card" check against simple card copying; 
 * see https://github.com/miguelbalboa/rfid
 * for further details and other examples.
 * This sample shows how to read and write data blocks on a MIFARE Classic PICC
 * (= card/tag).
 *
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 *
 */

#include <SPI.h>
#include <MFRC522.h>
constexpr uint8_t RST_PIN = 9;     // Configurable, see typical pin layout above
constexpr uint8_t SS_PIN = 10;     // Configurable, see typical pin layout above
MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.
MFRC522::MIFARE_Key key;

void setup() {
    Serial.begin(115200);         // Initialize serial com
    while (!Serial);              // Do nothing if no serial port is opened
    SPI.begin();                  // Init SPI bus
    mfrc522.PCD_Init();           // Init MFRC522 card

    // Prepare the key (used both as key A and as key B)
    // using FFFFFFFFFFFFh which is the default at chip delivery from the factory
    for (byte i = 0; i < 6; i++) {
        key.keyByte[i] = 0xFF;
    }
    Serial.println(F("Scan a MIFARE Classic PICC to demonstrate read and write."));
    Serial.print(F("Using key (for A and B):"));
    dump_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE);
    Serial.println();
    Serial.println(F("BEWARE: Data will be written to the PICC, in sector #1"));
}

void loop() {
  uint8_t i;
    if ( ! mfrc522.PICC_IsNewCardPresent()) // Card present?
        return;
    if ( ! mfrc522.PICC_ReadCardSerial())   // read UID
        return;

    Serial.print(F("Card UID:"));
    dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
    Serial.println();
    Serial.print(F("PICC type: "));
    MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
    Serial.println(mfrc522.PICC_GetTypeName(piccType));

    // Check for compatibility
    if (    piccType != MFRC522::PICC_TYPE_MIFARE_MINI
        &&  piccType != MFRC522::PICC_TYPE_MIFARE_1K
        &&  piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
      Serial.println(F("This sample only works with MIFARE Classic cards."));
      return;
    }

    // We use the second sector here: sector #1, block #4 -> #7 incl
    byte sector         = 1;
    byte blockAddr      = 4;
    byte dataBlock[]    = {
      0x51,   0x52,   0x53,   0x54,           //|
      0x55,   0x56,   0x57,   0x58,           //|_ your DATA here
      0x59,   0x60,   0x61,   0x62,           //|
                
                                              //    ***** Your 4 byte KEY here *****
      mfrc522.uid.uidByte[0] ^ 0x1A,          //|   Example:  0x1A, 0x2C, 0x33, 0x1F 
      mfrc522.uid.uidByte[1] ^ 0x2C,          //|_  XOR  UID with KEY to generate GUARD bytes
      mfrc522.uid.uidByte[2] ^ 0x33,          //|   which will be decoded in target sys
      mfrc522.uid.uidByte[3] ^ 0x1F };        //|   to verify card integrity
    
    byte blockAddr1      = 5;
    byte dataBlock1[]    = {
      0xFE, 0xFE, 0xFE, 0xFE,                 //|  
      0x08, 0x07, 0x06, 0x05,                 //|_    More DATA here
      0x09, 0x0a, 0xff, 0x0b,                 //|  
      0x0c, 0x0d, 0x0e, 0x0f };               //|  
    
    byte trailerBlock   = 7;
    MFRC522::StatusCode status;
    byte buffer[18];
    byte size = sizeof(buffer);

    // Authenticate using key A
    Serial.println(F("Authenticating using key A..."));
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("PCD_Authenticate() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
        return;
    }

    // Show the whole sector 
    Serial.println(F("Current data in sector:"));
    mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
    Serial.println();

    // Read data from the block
    Serial.print(F("Reading data from block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Read() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block ")); Serial.print(blockAddr); Serial.println(F(":"));
    dump_byte_array(buffer, 16); Serial.println();
    Serial.println();

    // Authenticate using key B
    Serial.println(F("Authenticating again using key B(not!)")); // flip the below comment-out if needed.
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
    //status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("PCD_Authenticate() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
        return;
    }

    // Write data to 1st block
    Serial.print(F("Writing data into block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    dump_byte_array(dataBlock, 16); Serial.println();
    //dump_byte_array(dataBlock1, 16); Serial.println();
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, 16);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Write() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.println();
    
    // Write data 2nd block
    Serial.print(F("Writing data into block ")); Serial.print(blockAddr1);
    Serial.println(F(" ..."));
    dump_byte_array(dataBlock1, 16); Serial.println();
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr1, dataBlock1, 16);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Write() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.println();
    // Read new data from block 
    Serial.print(F("Reading data from block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Read() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block ")); Serial.print(blockAddr); Serial.println(F(":"));
    dump_byte_array(buffer, 16); Serial.println();

    // Check that data in block is what we have written
    // by counting the number of bytes that are equal
    Serial.println(F("Checking result..."));
    byte count = 0;
    for (byte i = 0; i < 16; i++) {
        // Compare buffer (= what we've read) with dataBlock (= what we've written)
        if (buffer[i] == dataBlock[i])
            count++;
    }
    Serial.print(F("Number of bytes that match = ")); Serial.println(count);
    if (count == 16) {
        Serial.println(F("Success :-)"));
    } else {
        Serial.println(F("Failure, no match :-("));
        Serial.println(F("  perhaps the write didn't work properly..."));
    }
    Serial.println();

    // Dump the sector data
    Serial.println(F("Current data in sector:"));
    mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
    Serial.println();

    // Halt PICC
    mfrc522.PICC_HaltA();
    // Stop encryption on PCD
    mfrc522.PCD_StopCrypto1();
}

/**
 * Helper routine to dump a byte array as hex values to Serial.
 */
void dump_byte_array(byte *buffer, byte bufferSize) {
    for (byte i = 0; i < bufferSize; i++) {
        Serial.print(buffer[i] < 0x10 ? " 0" : " ");
        Serial.print(buffer[i], HEX);
    }
}
/* Card read-back and verify on target system
 * This is a code snippet example and not a stand-alone product
 * It is designed to show how.
 */

#include <EEPROM.h>
#include <U8x8lib.h>
#include <SPI.h>
#include <MFRC522.h>
#define __CS0 10        // SSD1325 CS   OLED
#define __DC  9         // SSD1325 DC   OLED
#define SS_PIN  19      // RFID  SDA/SS/SPI
#define RST_PIN 6       // RFID  Reset
#define RESET_DIO 28    // (28) OLED

U8X8_SSD1325_NHD_128X64_4W_HW_SPI u8x8(/* cs=*/ 10, /* dc=*/ 9, RESET_DIO);          // 2.5" ADA
MFRC522 mfrc522(SS_PIN, RST_PIN);                 // Create MFRC522 instance.
MFRC522::MIFARE_Key key;

struct PARAMSTORE {

uint8_t   Param1  =  5;
uint8_t   Param2  = 10;
//  .....
uint8_t   Param14 = 70;
uint8_t   Param15 = 75;       

// All the rest of your card-read variables here; 0-15 in this example 

};
struct PARAMSTORE paramStore;



void setup() {
  SPI.begin();
  mfrc522.PCD_Init();                             // Init MFRC522 card
  mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max);
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
  
  getRFID(&paramStore);  //function call to RFID

  // Loads more code
}

void getRFID(struct PARAMSTORE *ps){
  bool guardBytes = false;
  if ( ! mfrc522.PICC_IsNewCardPresent())   //Look for new cards
      return;
  if ( ! mfrc522.PICC_ReadCardSerial())     // Select one of the cards
      return;

  MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
  if (    piccType != MFRC522::PICC_TYPE_MIFARE_MINI
      &&  piccType != MFRC522::PICC_TYPE_MIFARE_1K
      &&  piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
      return;
  }
  byte blockAddr      = 4;
  byte trailerBlock   = 7;
  MFRC522::StatusCode status;
  byte buffer[18];
  byte size = sizeof(buffer);

  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK) {
    return;
  }

  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
  //  Read last four bytes of block & compare with our example:  0x1A, 0x2C, 0x33, 0x1F
  //  if fail, no valid read
  if (   (buffer[12] ^ mfrc522.uid.uidByte[0]) == 0x1A    // Test against mask to verify
      && (buffer[13] ^ mfrc522.uid.uidByte[1]) == 0x2C    // by XOR operation with UID
      && (buffer[14] ^ mfrc522.uid.uidByte[2]) == 0x33
      && (buffer[15] ^ mfrc522.uid.uidByte[3]) == 0x1F) {
    guardBytes = true;
    mfrc522.MIFARE_Read(blockAddr, (byte*)ps, &size);     // Here the data is copied into paramStore
    EEPROM.put(0, paramStore);                            // Here the paramStore data is copied into EEPROM 
    displayParamsOnOLED();
    clearFromLine(5);
    u8x8.inverse(); 
    u8x8.draw2x2String(0, 6, "Card Read");
    u8x8.noInverse(); 
  } else {                                
    displayParamsOnOLED();
    clearFromLine(5);
    u8x8.inverse(); 
    u8x8.draw2x2String(0, 6, "NO MATCH");
    u8x8.noInverse(); 
  }
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}