Restricted access to devices via contactless card with the NodeMCU and the RC522 module part 4 - Down the Rabbit Hole

Today's blog goes " down the Rabbit Hole" as promised and we deal with the internals of the MiFare Proximity Integrated Contactless Cards (PICC) Number. We look at the specifics and take a closer look at the functions.

Many Card Reader- Projects with which mf522 chipset is used as the basis for the Arduino which I have personally already seen, unfortunately neither in security technology nor in possibilities use what the Mifare Classic card offers us in functions.

These projects are limited to reading the unique ID (UUID) of the card, which can be read freely for each card reader and mobile phone, and to check it against a list of allowed UUID's. If it is included in the list, the card is considered valid.

Also in our previous parts we make use of this principle. We want to change this with our blog entry today and store data securely on our MiFare Classic card.

Due to the card type "MiFare Classic" used in this project (please also note this note), we can store our own information from the map and protect it against unauthorized reading or modification.

For this purpose, the manufacturer already has 16 sectors (0-15) with 4 * 16 bytes each, with the exception of the sector 0 , 3 * 16 bytes can be described freely. 16 bytes of each sector are called sector trailers and used to deposit the 2 sector keys and to define the access matrix.

The 16 bytes of a sector trailer are divided as follows:
6 bytes - first sector key
6 bytes - second sector key
4 bytes - access permission definition

We now want to store the first name and last name of us encrypted on our map. To do this, we define a sector (1-15) via the constant ... which we want to use for this purpose. I chose sector block 1 in the code.

The first name should be in the second 16 bytes field, and the last name in the third 16 bytes field. The first 16 bytes field is reserved for future extensions.

We also need a key definition, which we store in the MiFareClassicKeyTable structure.

Please change this block (2x 6 bytes) with your own key material during the replica and save it in a safe place, otherwise anyone who knows this project with the key material mentioned in the code in plain text will read your card and read your own valid! can create cards for your device control.

Small spoiler: We will also need this key material again in a later continuation of the series. Please keep in mind that you describe the card and change security options. As a result, the card may no longer be usable for other purposes!

After changing the key material, we upload the following code to our ESP:

 

#include <Spi.H>
#include <MFRC522.H>
#include <ESP8266WiFi.H>
#include <WiFiClient.h> 
#include <ESP8266WebServer.H>
#include <ESP8266mDNS.H>
#include <Eeprom.H>
#include <Fs.H>           Include the SPIFFS library

#define RST_PIN     5     SPI Reset Pin (D1 output)
#define RELAIS_PIN  16    Relay (D0 output) [LOW Active] - Also internal LED near USB port
#define SS_PIN      15    SPI Slave Select Pin

#define RGBLED_R    2     Red (D4 output) 
#define RGBLED_G    0     Green (D3 output) - Also internal LED on the ESP module
#define RGBLED_B    4     Blue (D2 output)

#define WiFiPwdLen   25   Maximum WiFi password length
#define STANameLen 20     Maximum WiFi SSID length
#define ESPHostNameLen 20 Maximum number of characters ESPHostName

#define Keya True         PICC Flag Definition
#define Keyb False        PICC Flag Definition

#define LED_BUILTIN 16
#define PIN_WIRE_SDA 4 
#define PIN_WIRE_SCL 5 

#define USED_Sector 1  Card sector used for authentication and configuration data (valid: 1- 15)

ADC_MODE(ADC_TOUT);   Configure analog input A0 to external. ADC_TOUT (for external voltage), ADC_VCC (for system voltage). 
MFRC522 mfrc522(SS_PIN, RST_PIN);   Create instance of MFRC522
MFRC522::MIFARE_Key Key;
ESP8266WebServer Server(80);        Web Server Create Instance

Struct WiFiEEPromData   {     Char ESPHostName[ESPHostNameLen];      Char APSTAName[STANameLen]; STATION Name to Connect, if defounded        Char WiFiPwd[WiFiPwdLen]; WiFiPAssword, if definded         Char ConfigValid[3]; If Config is Vaild, tag "TK" is required"   };    Struct MiFareClassicKeyTable   {    Byte Key_A[6] = {0x22,0x44,0xFA,0xAB,0x90,0x11};   Key for PICC card please change.     Byte Key_B[6] = {0xFE,0xE1,0xAA,0x3D,0xDF,0x37};   Key for PICC card please change.     Char ConfigValid[3]; If Config is Vaild, tag "TK" is required"   };    MiFareClassicKeyTable MiFareClassicKey;
WiFiEEPromData MyWiFiConfig;

Globally used variables
Bool Result  = False;
Bool LearnNewCard = False;
Bool EraseCard = False; 
Bool ExpirationDateActive = False;
String Surname;
String Givenname;
String ExpirationDate;
String Temp;
Unsigned Long Sessionid;
Unsigned Long PCD_ServiceCall_Handler = 0;
Unsigned Long PCD_WatchDog_Handler = 0;
uint8_t DataBuffer[18]  = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };


Void Setup()     {    pinMode(RST_PIN,Output);   digitalWrite(RST_PIN,High);   pinMode(RELAIS_PIN,Output);   pinMode(RGBLED_R,Output);   pinMode(RGBLED_G,Output);   pinMode(RGBLED_B,Output);   digitalWrite(RELAIS_PIN,High);    Relay inactive   SetRGBLed(255,0,255,False);       Led Color Purple Initalization Begin   Serial.Begin(115200);               Initialize serial communication with the PC with 115200 Baud   Serial.println("");   Temp = "ATSN:"+ String(Esp.getChipId());   Serial.println(Temp);   Serial.print("ADC Value:"); Serial.println(analogRead(A0));   Spi.Begin();                      Initialize SPI communication   PCDHardReset();   Sessionid = millis();    Result  = startWiFiClient();     InitalizeHTTPServer();   SetRGBLed(0,0,255,False);       Led Color Blue Initalization Completed   ESP.wdtEnable(WDTO_4S);              Start Watchdog   }


Start Helper/ Optimization Functions **************************************************************************************************************

Void SetRGBLed(Byte RedValue,Byte GreenValue,Byte BlueValue,Boolean SlowFade)  Funkion for controlling the RGB Led
{   digitalWrite(RGBLED_R,Low);       digitalWrite(RGBLED_G,Low);   digitalWrite(RGBLED_B,Low);    If (RedValue == 255)   { digitalWrite(RGBLED_R,High); }      If (GreenValue == 255) { digitalWrite(RGBLED_G,High); }   If (BlueValue == 255)  { digitalWrite(RGBLED_B,High); }    }

Stop Helper/ Optimization Functions *****************************************************************************************************************

Start Functions Web server *******************************************************************************************************************************************

Cookie base routines are based on GIT excerpt:
//https://github.com/esp8266/ESPWebServer/blob/master/examples/SimpleAuthentification/SimpleAuthentification.ino
Bool is_authentified()
{     If (Server.Hasheader("Cookie")){       Cookie found      Temp = Server.Header("Cookie");       Serial.println(temp);      String SessionStr = String(Esp.getChipId()) + "=" + String(Sessionid);      Yield();      If (Temp.Indexof(SessionStr) != -1) {         Web authentication successful        Temp = "";        Return True;       }     }        Web Authentication Failed    Temp = "";    Sessionid = millis();    Return False; 
} 

Void handleLogin(){   String Msg;   String cookie = server.header("Cookie");   Serial.println(cookie);   If (Server.hasArg("DISCONNECT")){     Disconnection User;     Server.sendHeader("Location","/login");     Server.sendHeader("Cache-Control","no-cache");     Sessionid = millis();     Temp = String(Esp.getChipId()) + "= NA ; HttpOnly ; SameSite=Strict";     Server.sendHeader("Set cookie",Temp);     Temp = "";     Server.send(301);     Yield();     Return;   }   If (Server.hasArg("USERNAME") && Server.hasArg("PASSWORD")){     Temp = String(Esp.getChipId());     If (Server.Bad("USERNAME") == "admin" &&  Server.Bad("PASSWORD") == Temp ){       Server.sendHeader("Location","/");       Server.sendHeader("Cache-Control","no-cache");       Sessionid = millis();       Temp = String(Esp.getChipId()) + "=" + String(Sessionid) + "; HttpOnly ; SameSite=Strict";       Server.sendHeader("Set cookie",Temp);       Temp = "";       Server.send(301);       Yield();       Return;     }   Msg = "<script>alert('Wrong username or password !'); </script>";   }    CSS_Header_Template();    Yield();    Temp = "<head><title>Login</title></head><body><DIV ALIGN=CENTER>";    Server.sendContent(Temp);    Temp = "<h2>Registration to card reader RC522</h2><body><br><br><br>table border=0 bgcolor=black><tr><th><DIV ALIGN=RIGHT>";    Server.sendContent(Temp);    Temp = "<form action='/login' method='post'>Username: <input type=text Name='USERNAME' Size=17 required><br>";    Server.sendContent(Temp);    Temp = "Password: <input type=password Name='PASSWORD' Size=17 required><br><br><br><br><button type=submit' ";    Server.sendContent(Temp);    Temp = "name='Login_Button' value='1' style='height: 30px; width: 100px' >Login</button><br></th></tr></form></DIV></table>";    Server.sendContent(Temp);    Temp = "<br><SMALL>For login to work, cookies for this website should be allowed.</SMALL>";      Server.sendContent(Temp);    Temp = Msg + "</DIV></body></HTML>";    Server.sendContent(Temp);    Temp = "";
}

Void handleNotFound()    {   Sessionid = millis();   Temp = "Page not found.;   Temp += "URI: ";   Temp += Server.Uri();   Temp += "AnMethod: ";   Temp += (Server.method() == HTTP_GET)?"GET":"POST";   Temp += "AnArguments: ";   Temp += Server.Args();   Temp += "An";   for (uint8_t =0; <Server.Args(); ++){     Temp += " " + Server.argName() + ": " + Server.Bad() + "An";   }   Yield();   Server.send(404, "text/plain", Temp);   Temp = "";   }



Void handleErasePICC()
{   If (!is_authentified())     {     Server.sendHeader("Location","/login");     Server.sendHeader("Cache-Control","no-cache");     Server.send(301);     Yield();     Return;     }     CSS_Header_Template();   Yield();   Temp = "<head><title>Card reader RC522</title></head><body>";   Server.sendContent(Temp);   HtmlNavStructure();   Temp = "<script>alert('Please keep the card to be deleted in front of the reader NOW!'); </script>";      Server.sendContent(Temp);    Yield();   SetRGBLed(0,255,255,False);       Led color cyan programming mode   EraseCard = True;         Temp = "</body></html>";   Server.sendContent(Temp);   Server.Client().Stop();   Temp = "";
}


Void handleNewPICC()
{   If (!is_authentified())     {     Server.sendHeader("Location","/login");     Server.sendHeader("Cache-Control","no-cache");     Server.send(301);     Return;     }   If (Server.hasArg("Surname") && Server.hasArg("Givenname"))   {      Surname = Server.Bad("Surname");     Givenname = Server.Bad("Givenname");     ExpirationDate = Server.Bad("ExpDate");     If (Server.hasArg("ExpDateOption")) { ExpirationDateActive = True; } else { ExpirationDateActive = False; }     Temp = "<script>alert('Please keep the new card in front of the reader NOW!'); </script>";        Server.sendContent(Temp);      SetRGBLed(255,255,0,False);       Led Color Yellow Programming Mode     LearnNewCard = True;     Yield();     Return;       }         CSS_Header_Template();   yield();   temp = "<head><title>Kartenleser RC522</title></head><body>";   server.sendContent(temp);   HtmlNavStructure();   temp = "";   temp = "<br><br><br><br><table border=0 ALIGN=CENTER><th>";   server.sendContent(temp);   temp = "<table border=1 bgcolor = black><form action='/newPICC' method='post'>";   server.sendContent(temp);   temp = "<tr><th>Karteninhaber:<br><div ALIGN=RIGHT>";   server.sendContent(temp);   temp = "Vorname: <input type=text Name='Surname' Size=17 maxlenght=16 placeholder='Max' required><br>";   server.sendContent(temp);   temp = "Nachname: <input type=text Name='Givenname' Size=17 maxlenght=16 placeholder='Mustermann' required><br>";   server.sendContent(temp);   temp = "</div></th><th>Kartenmetadaten:<br><DIV ALIGN=RIGHT>";   server.sendContent(temp);       temp = "<input Name='ExpDateOption' TYPE=checkbox VALUE=1 >Ablaufdatum:<input type=date Name='ExpDate' Size = 17 >";   server.sendContent(temp);   temp = "<br><th><tr><th></table><br>";   server.sendContent(temp);   temp = "<button type='submit' name='NewCard' value='1' style='height: 30px; width: 200px' >Neue Smartcard erstellen</button>";   server.sendContent(temp);   temp = "<br></form></tr></th></table>";   server.sendContent(temp);   temp = "</body></html>";   server.sendContent(temp);   server.client().stop();   yield();     temp = "";
}

void handleRoot()
{   if (!is_authentified()){     server.sendHeader("Location","/login");     server.sendHeader("Cache-Control","no-cache");     server.send(301);     return;     }   // HTML Content   CSS_Header_Template();   yield();   temp = "<head><title>Kartenleser RC522</title></head><body>";   server.sendContent(temp);   HtmlNavStructure();   temp = "<div ALIGN=CENTER><br><br><br><br><BIG>Willkommen auf der Smartkartenleser RC522 Webseite.</BIG><br>";   server.sendContent(temp);   temp = "Resetgrund: " + String(ESP.getResetReason()) + "<br>";   server.sendContent(temp);   temp = "Freier Heapspeicher: " + String(ESP.getFreeHeap()) + " Bytes<br>";   server.sendContent(temp);   temp = "Int. Flash: " + String(ESP.getFlashChipRealSize()) + " Bytes<br>";   server.sendContent(temp);   Result = mfrc522.PCD_PerformSelfTest();    mfrc522.PCD_Init();                       // Initialisiere MFRC522 Lesemodul   mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max); // Setzt Antenne auf max. Empfang   mfrc522.PCD_AntennaOn();    yield();   if (Result) {temp = "RC522 PCD-Status: OK<br>"; } else {temp = "RC522 PCD-Status: Fehler!<br>"; }    server.sendContent(temp);   temp = "CPU ID: " + String(ESP.getChipId()) + " @ " + String(ESP.getCpuFreqMHz()) + " MHz<br>";    server.sendContent(temp);   temp = "<br>Sie sind erfolgreich angemeldet !<br><br><form action='/login' method='get'>";   server.sendContent(temp);   temp = "<button type='submit' name='DISCONNECT' value='YES' style='height: 30px; width: 200px' >Logout</button>";   server.sendContent(temp);   temp = "</form></div></body></html>";   server.sendContent(temp);   if (server.hasArg("Reboot") )  // Reboot System      {     //ESP.wdtFeed();             ESP.wdtDisable();     temp = "<script>alert('Das System startet JETZT neu.'); </script>";        server.sendContent(temp);     server.client().stop();     yield();     temp = "";     ESP.reset();     delay(4000);      }     server.client().stop();   temp = "";
}

void CSS_Header_Template() // Formatvorlage für alle internen ESP Webseiten. https://wiki.selfhtml.org/wiki/CSS   {    server.setContentLength(CONTENT_LENGTH_UNKNOWN);    temp = "";    server.send (200, "text/html", temp);    temp = "<! DOCTYPE HTML PUBLIC '-/-//W3C//DTD HTML 4.01 Transitional//EN'><html lang='de'><meta charset='UTF-8'>";    server.sendContent(temp);    temp = "<style type='text/css'>*{margin: 0;padding: 0;} body{background:black;color:darkorchid;font-size: 16px;" ;    server. sendContent(temp);    temp = "font-family: sans-serif,arial;}. nav{width: 1300px;height: 30px;margin: 0 auto;border-radius: 5px;}";    server.sendContent(temp);    temp = "ul li{list-style: none;width: 200px;line-height: 60px;position: relative;background: darkorchid;" ;    server. sendContent(temp);    temp = "box-shadow: 0px 2px 5px 0px grey;text-align: center;float: left;background-color: #010000;} ul li ul{";    server.sendContent(temp);    temp = "position: absolute;}. nav > ul > li:nth-of-type(1){border-radius: 5px 0px 0px 5px;}. nav > ul > li:nth-of-type(5)";    server.sendContent(temp);    temp = "{border-radius: 0px 5px 5px 0px;} ul li a{color: rgb(182, 18, 18);width: 200px;height: 58px;display: inline-block;" ;    server. sendContent(temp);    temp = "text-decoration: none;} ul li a:hover{font-weight: bold;border-bottom: 2px solid #fff;} ul li ul{display: none;}";    server.sendContent(temp);    temp = ".nav ul li:hover ul{display: block;}. fa{margin-right: 5px;}. container{width: 1000px;height: 200px;" ;    server. sendContent(temp);    temp = "margin: 0 auto;padding:20px 20px;} @media screen and (max-width: 480px){header{width: 100%;}";    server.sendContent(temp);    temp = ".nav{display: none;width: 100%;height: auto;} ul li{width: 100%;float: none;} ul li a{width: 100%;" ;    server. sendContent(temp);    temp = "display: block;} ul li ul{position: static;} ul li ul li a{background: #222;}. fa-list.modify{display: block;}";    server.sendContent(temp);    temp = ".container{width: 100%;height: auto;} body{overflow-x:hidden;}} </style>";    server.sendContent(temp);    temp = "";   }


void HtmlNavStructure()   {   temp = "<div class='menu'><nav class='nav'><ul>";   server.sendContent(temp);   temp = "<li><a href='#'>System</a>";   server.sendContent(temp);   temp = "<ul><li><a href='/'>Information</a></li>";   server.sendContent(temp);   temp = "<li><a href='/? Reboot=YES'>Neustart</a></li>";   server.sendContent(temp);   temp = "</ul>";   server.sendContent(temp);   temp = "</li><li><a href='#'>PICC</a>";   server.sendContent(temp);   temp = "<ul><li><a href='/newPICC'>Neue Karte erstellen</a></li>";   server.sendContent(temp);   temp = "<li><a href='/erasePICC'>Karte löschen</a></li></ul>";   server.sendContent(temp);   temp = "</li>";   //temp = "</li><li><a href='#'>Ereignisprotokoll</a></li>";   server.sendContent(temp);   temp = "</ul></nav></div>";   server.sendContent(temp);   temp = "";   }       void InitalizeHTTPServer()    {   bool initok = false;   const char * headerkeys[] = {"User-Agent","Cookie"} ; //Header zum Tracken   size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*); //Header zum Tracken   server.on("/", handleRoot);   server.on("/login", handleLogin);   server.on("/newPICC", handleNewPICC);   server.on("/erasePICC", handleErasePICC);   server.onNotFound ( handleNotFound );   server.collectHeaders(headerkeys, headerkeyssize );// Server anweisen, diese zu Tracken    server.begin(); // Web server start   }        // ******************* End Functions Webserver *********************************************


// ******************* Start Functions WiFi Management *************************************
// Funktion von https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/wps-mit-dem-esp8266?ls=de
bool startWPS() 
{   bool wpsSuccess = WiFi.beginWPSConfig();   if(wpsSuccess) {       // Muss nicht immer erfolgreich heißen! Nach einem Timeout ist die SSID leer       String newSSID = WiFi.SSID();        if(newSSID.length() > 0) {         // Nur wenn eine SSID gefunden wurde waren wir erfolgreich          yield();          Serial.println("ATWPS:OK");         saveCredentials(); // Save Credentials to EEPROM              } else {         Serial.println("ATWPS:NOK");       }   }   return wpsSuccess; 
}

bool startWiFiClient() 
{   bool WiFiClientStarted = false;   size_t A0_ADCValue = 0;   byte i = 0;   byte connRes = 0;   Serial.setDebugOutput(false);  // Zu Debugzwecken aktivieren.    WiFi.hostname("CrdRdr41667");   WiFi.softAPdisconnect(true);   WiFi.disconnect();   WiFi.mode(WIFI_STA);   if(loadCredentials())      {      WiFi.begin(MyWiFiConfig.APSTAName, MyWiFiConfig.WiFiPwd);      while (( connRes != 3 ) and( connRes != 4 ) and (i != 30))  //if connRes == 0  "IDLE_STATUS - change Statius"       {        i++;       // Serial.print("."); // Connect vorgang auf der seriellen Schnittstelle beobachten       //ESP.wdtFeed();       delay(500);       yield();       connRes  = WiFi.waitForConnectResult();       }      if (connRes == 4 ) { // if password is incorrect       Serial.println("ATWIFI:PWDERR");             WiFi.disconnect();       }      if (connRes == 6 ) { //  module is not configured in station mode       Serial.println("ATWIFI:STAERR");       WiFi.disconnect();       }      }   if(WiFi.status() == WL_CONNECTED)      {     //ESP.wdtFeed();      Serial.print("ATIP:");     Serial.println(WiFi.localIP());     WiFi.setAutoReconnect(true); // Set whether module will attempt to reconnect to an access point in case it is disconnected.     // Setup MDNS responder     if (!MDNS.begin("CrdRdr41667"))        {       Serial.println("ATMDNS:NOK");       } else { MDNS.addService("http", "tcp", 80); }        WiFiClientStarted = true;     } else      {     A0_ADCValue = analogRead(A0);     //Wir waren nicht erfolgreich, daher starten wir WPS, wenn WPS Taster an A0 während des Resets gedrückt ist     if (A0_ADCValue > 499)       {         if(startWPS())            {             //ESP.wdtFeed();              delay(500);             WiFi.disconnect();             WiFi.mode(WIFI_STA);             WiFi.begin(WiFi.SSID().c_str(), WiFi.psk().c_str());             //ESP.wdtFeed();              WiFiClientStarted = true;           } else           {             WiFiClientStarted = false;             WiFi.disconnect();           }       } else       {         WiFi.disconnect();       }    }    //WiFi.printDiag(Serial);       // Zu Debugzwecken aktivieren.   return WiFiClientStarted; 
}
// ******************* END Functions WiFi Management *************************************

// ******************* Start Functions Store WiFi Credentials to EEPROM ******************
bool loadCredentials() 
{
 bool RetValue;
 EEPROM.begin(512);
 EEPROM.get(0,MyWiFiConfig);
 EEPROM.end();
 if (String(MyWiFiConfig.ConfigValid) == "TK")    {     RetValue = true;   } else   {     RetValue = false; // WLAN Settings nicht gefunden.   }   //ESP.wdtFeed();    return RetValue; 
}

void saveCredentials() // Speichere WLAN credentials auf EEPROM 
{   size_t i;   for (i = 0 ; i < sizeof(MyWiFiConfig) ; i++) // Loeschen der alten Konfiguration      {       EEPROM.write(i, 0);       }   for (i = 0 ; i < STANameLen  ; i++) // Loeschen der alten Konfiguration      {       MyWiFiConfig.WiFiPwd[i] = 0;       }   for (i = 0 ; i < WiFiPwdLen ; i++) // Loeschen der alten Konfiguration      {       MyWiFiConfig.APSTAName[i] = 0;       }      temp = WiFi.SSID().c_str();   i = temp.length();   temp.toCharArray(MyWiFiConfig.APSTAName,i+1);    temp = WiFi.psk().c_str();   i = temp.length();   temp.toCharArray(MyWiFiConfig.WiFiPwd,i+1);   temp = "";      strncpy(MyWiFiConfig.ConfigValid , "TK", sizeof(MyWiFiConfig.ConfigValid) );    EEPROM.begin(512);   EEPROM.put(0, MyWiFiConfig);   EEPROM.commit();   EEPROM.end();   //ESP.wdtFeed(); 
}
// ******************* END Functions StoreCredentialsto EEPROM ***************************

// ******************* Start Functions CardServices  *************************************


void PCDHardReset()
{   digitalWrite(RST_PIN,LOW);   delay(200);   digitalWrite(RST_PIN,HIGH);   mfrc522.PCD_Reset();   mfrc522.PCD_Init();                              // Initialisiere MFRC522 Lesemodul   mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max); // Setzt Antenne auf max. Empfang   mfrc522.PCD_AntennaOn();
}

boolean CardAuthenticate(boolean ABKey, byte Sector,byte ikey[6])
{
const byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
byte statusA;
statusA = 0;
for (int a = 0; a < 6;a++)   {   key.keyByte[a] = ikey[a];   }   // Key A
if (ABKey)   {   statusA = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, sectorkeytable[Sector], &key, &(mfrc522.uid));   if (statusA != MFRC522::STATUS_OK)     {     Serial.println("ATAUTH:ERR_A");     return false;     }       }   // Key B   else if (not ABKey)   {   statusA = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B,sectorkeytable[Sector], &key, &(mfrc522.uid));   if (statusA != MFRC522::STATUS_OK)     {     Serial.println("ATAUTH:ERR_B");     return false;     }        }
return true;   }

// WriteData . uses Global Variable DataBuffer for Data Return
boolean CardDataWrite(byte Sector,byte block,byte value[16])
{
byte status;
byte writevector;
byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
writevector = Sector * 4 + block -1 ;
for (byte a = 0; a < 16; a++)   {   if (writevector ==  sectorkeytable[a])     {     // Serial.println("NAK");     return false;     }   }
status = mfrc522.MIFARE_Write(writevector, value, 16);
if (status != MFRC522::STATUS_OK)   {   Serial.println("ATPCD:W_ERR");   //Serial.println(mfrc522. GetStatusCodeName(status));   return false;   } else    {   // Serial.print("OK");   return true;   }   }

// Read Data - uses Global Variable DataBuffer for Data Return
boolean CardDataRead(byte Sector,byte block)
{
byte statusi;
byte readvector;
const byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
byte sized = 18;
readvector = Sector * 4 + block -1 ;
for (byte a = 0; a < 16; a++)   {   if (readvector ==  sectorkeytable[a])     {      Serial.println("ATPCD:R_ERR");       return false;     }   }
statusi = mfrc522.MIFARE_Read(readvector, DataBuffer, &sized);
if (statusi != MFRC522::STATUS_OK)   {   Serial.println("ATPCD:R_ERR");   return false;   } else    {   return true;   }   }

boolean ResetCardToDefault()
{
byte ikey[16];
byte status,i;
byte writevector;
const byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
writevector = sectorkeytable[USED_Sector];
if (CardAuthenticate(KEYB,USED_Sector,MiFareClassicKey.Key_B)) // Sector Autenticate for WRITE Access      {       for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; }           if (!(CardDataWrite(USED_Sector,1,DataBuffer))) { return false; }        for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; }          if (!(CardDataWrite(USED_Sector,2,DataBuffer))) { return false; }       for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; }          if (!(CardDataWrite(USED_Sector,3,DataBuffer))) { return false;}      }
for (byte i = 0; i <= 16; i++) { ikey[i] = 255; }  //Load Default Key for all Sectors 
ikey[6] = 0xFF; // Default Setting for Access Bits
ikey[7] = 0x07; // 
ikey[8] = 0x80; // 
ikey[9] = 0x69;    status = mfrc522.MIFARE_Write(writevector, ikey, 16);
if (status != MFRC522::STATUS_OK)   {   return false;   }
return true;   }

boolean SetSectorAccessControl (byte Sector,byte Akey[6],byte Bkey[6])
{
byte ikey[16];
byte status;
byte writevector;
const byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
writevector = sectorkeytable[Sector];
ikey[0] = Akey[0];
ikey[1] = Akey[1];
ikey[2] = Akey[2];
ikey[3] = Akey[3];
ikey[4] = Akey[4];
ikey[5] = Akey[5];
ikey[6] = 0x78; // Data Block 0-3 Access Conditions: Key B write / Key A Read
ikey[7] = 0x77; // KEY A & KEY B & Acces Bits Write:Key B  / Key A Read Access Bits
ikey[8] = 0x88; // Calculator: http://calc.gmss.ru/Mifare1k/
ikey[9] = 0x69; // Fixer Wert - > default hex 69
ikey[10] = Bkey[0];
ikey[11] = Bkey[1];
ikey[12] = Bkey[2];
ikey[13] = Bkey[3];
ikey[14] = Bkey[4];
ikey[15] = Bkey[5];
status = mfrc522.MIFARE_Write(writevector, ikey, 16);
if (status != MFRC522::STATUS_OK)   {   Serial.println("ATPCD:W_KEY_ERR");   return false;   }else    {   return true;   }   }

boolean CheckforDefaultCardKey ()
{
byte tkey[6];
boolean CardResult; 
byte readvector;
byte statusi;
const byte sectorkeytable [16] = {3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63};
byte sized = 18;
for (byte i = 0; i <= 6; i++) { tkey[i] = 255; }  //Load Default Key for all Sectors 
CardResult = true;   if (!CardAuthenticate(KEYA,USED_Sector,tkey)) { CardResult = false; };
readvector = sectorkeytable[USED_Sector];
statusi = mfrc522.MIFARE_Read(readvector, DataBuffer, &sized);
if (statusi != MFRC522::STATUS_OK) { CardResult = false; } 
//if (!( (DataBuffer[7] = 0x07) & (DataBuffer[7] = 0x80)))  { CardResult = false; };
return CardResult; 
}

boolean WriteNewMiFareClassicPICC ()   {   byte tkey[6];   byte i,a;   boolean CardResult;   if (CheckforDefaultCardKey())     {     for (i = 0; i <= 6; i++) { tkey[i] = 255; }  //Load Default Key for all Sectors      for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; } // Clear Variable Buffer     CardResult = true;            if (CardAuthenticate(KEYA,USED_Sector,tkey)) // Sector  Autenticate       {       // Serial.println("Auth Sec 0 OK");        if (Surname.length() > 15) { a = 15; } else { a = Surname.length();}       if (Surname.length() > 0)         {         for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; }             for (i = 0; i <= a; i++) { DataBuffer[i] = Surname[i]; }            if (!(CardDataWrite(USED_Sector,2,DataBuffer))) { CardResult = false; } //Sector 0 Block 2 Vorname mit Key A schreiben         }       if (Givenname.length() > 15) { a = 15; } else { a = Givenname.length(); }       if (Givenname.length() > 0)         {         for (i = 0; i <= 16; i++) { DataBuffer[i] = 0; }          for (i = 0; i <= a; i++) { DataBuffer[i] = Givenname[i]; }          if (!(CardDataWrite(USED_Sector,3,DataBuffer))) { CardResult = false; } //Sector 0 Block 3 Nachname mit Key A schreiben         }             if (!(SetSectorAccessControl (USED_Sector,MiFareClassicKey.Key_A,MiFareClassicKey.Key_B))) { CardResult = false; }    // (byte Sector,byte Akey[6],byte Bkey[6])       } else {               CardResult = false;              return CardResult;              }        } else {               CardResult = false;              return CardResult;              }          if (CardResult)        {        //Serial.println("PICC written");            CardResult = true;       }     else        {        //Serial.println("PICC not empty");       CardResult = false;       }     yield();       return CardResult;   }


boolean ReadMiFareClassicPICC ()
{
boolean CardResult;
byte i,a ;
CardResult = true;    if (CardAuthenticate(KEYA,USED_Sector,MiFareClassicKey.Key_A)) // Sector  Autenticate with READ Key A   {   Givenname = "aaaaaaaaaaaaaaaa"; //PlaceHolder   Surname = "aaaaaaaaaaaaaaaa"; //PlaceHolder   for (i = 0; i < 18; i++) { DataBuffer[i] = 0; } // Clear Variable Buffer   if (CardDataRead(USED_Sector,2)) // Feld Vorname auslesen     {         for (i = 0; i < 16; i++) { Surname[i] = char(DataBuffer[i]);  }             } else {             return false;            }     for (i = 0; i < 18; i++) { DataBuffer[i] = 0; } // Clear Variable Buffer          if (CardDataRead(USED_Sector,3)) // Feld Nachname auslesen       {                for (i = 0; i < 16; i++) { Givenname[i] = char(DataBuffer[i]); }              } else {               return false;                 }      } else      {      return false;     }
Serial.print ("ATAUTH_S:");
Serial.println (Surname);
Serial.print ("ATAUTH_G:");
Serial.println (Givenname);
return true;
}

void CardServer()
{
#define PCD_Poll_Interval 400
#define PCD_Watchdog_Interval 60000
if (millis() - PCD_ServiceCall_Handler >= PCD_Poll_Interval)    {    PCD_ServiceCall_Handler = millis();        if (mfrc522.PICC_IsNewCardPresent())   // PICC = proximity integrated circuit card = kontaktlose Chipkarte     {      mfrc522.PICC_ReadCardSerial();       yield();     // Unterscheidung nach Kartentyp     // 0x08 für MIFARE Classic 1K     // 0x18 für MIFARE Classic 4K     // 0x11 für MIFARE PLUS     if (mfrc522.uid.sak == 0x08 || mfrc522.uid.sak == 0x18)       {       //  MiFare_Classic_Processor START (mfrc522.uid.sak);  // Nur ausführen wenn Eine Mifare Classic Karte vor den Leser gehalten wurde.       byte tkey[6];        for (byte i = 0; i <= 6; i++) { tkey[i] = 255; }  //Load Default Key for all Sectors        if(LearnNewCard) // neue Karte soll angelernt werden.         {           if (WriteNewMiFareClassicPICC()) { SetRGBLed(0,255,0,false); } else { SetRGBLed(255,0,0,false); }         }       else if (EraseCard)  // KartenDaten sollen gelöscht werden.         {           if (ResetCardToDefault()) { SetRGBLed(0,255,0,false); } else { SetRGBLed(255,0,0,false); }         }             else         {         if (ReadMiFareClassicPICC())            { // Karte gültig !           bool PinState= digitalRead(RELAIS_PIN);           PinState = !PinState;           digitalWrite(RELAIS_PIN, PinState);           SetRGBLed(0,255,0,false);        //Led Grün           } else { SetRGBLed(255,0,0,false); }               }       LearnNewCard = false;                     //  MiFare_Classic_Processor STOP (mfrc522.uid.sak);       } else if (mfrc522.uid.sak == 0x00) // Mifare Ultralight       {         SetRGBLed(255,0,0,false);       } else       {         SetRGBLed(255,0,0,false);         //Serial.print("PICC Type not supported. Type:");          //Serial.println(mfrc522.uid.sak); //Erweiterung: evtl andere Kartentypen         }     mfrc522.PCD_StopCrypto1();     mfrc522.PICC_HaltA();     delay(2000);     SetRGBLed(0,0,255,false);        //Led Farbe Blau Leser ist in Grundzustand     }   }   if (millis() - PCD_WatchDog_Handler >= PCD_Watchdog_Interval)    {    PCD_WatchDog_Handler = millis();    Result = mfrc522.PCD_PerformSelfTest();    Yield();    mfrc522.PCD_Init();                       Initialize MFRC522 Reading Module Again    mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max); Sets antenna to max. reception    mfrc522.PCD_AntennaOn();     Yield();    If (!(Result))      {       PCDHardReset();       Serial.println("ATPCD:ERR_H");     }    }   }

Stop Functions CardServices *************************************************************************************************************************


 
Void Loop()  Main loop
{   CardServer();          Map reader to handle specific requests   Yield();    Server.handleClient(); Edit Web server requests    ESP.wdtFeed();       Watchdog. Disable with "wdt_disable();"
}

 

The Wlan connection data is preserved because we stored it in a non-volatile memory in the previous sketch.

But how does the authentication of the card work now?

Simply put, when holding a MiFare Classic card (this is checked in advance), the card reader module tries to read a sector that can only be read with a valid key.

If the action is successful, the name fields first name and last name are read out and the card is recognized as valid. (relay is switched) If authentication is unsuccessful or the wrong key is used, the card is rejected as invalid.

The distinction between "card valid or card invalid" on this characteristic gives us the following advantages:

Several cards may be entitled to a reader at the same time, without which each card must be made known to the reader in advance by UUID.

Several card readers can be operated with one card at the same time without each card reader already knowing each card in advance via UUID.

By varying the sector definition or key material, "permission circles" can be formed.

 

We are now creating our first authorization card. To do this, after login on the menu item "PICC", then "Create a new map" and click on it. We see the following page:

We enter a first and a last name in the designated fields and press the button "Create a smart card"

The LED turns yellow. We now hold a LEERE!! Mifare Classic card in front of the reader and wait briefly until the LED turns green. That's it! From now on we can use this card as an authorized card to our reader.

If we want to invalidate this card, after login on the menu item PICC, we go under "Delete map" and click on it. -- After the message:

we hold the previously authorized card back in front of the reader. This is then invalid.

Have fun recreating us until the next part.

Esp-8266Für arduinoProjekte für fortgeschritteneSmart home

Leave a comment

All comments are moderated before being published