Le système RFID (Radio Frequency IDentification) est un système d'identification sans contact sur une faible distance développé depuis les années 1960. Ce système n'est pas considéré comme un système de sécurité, puisqu'il identifie le porteur du badge RFID.
Un système RFID est composé de plusieurs éléments qui sont le transpondeur, la station de base et le système hôte.
Egalement appelé tag, étiquette ou encore badge, composé
Egalement appelé interrogateur ou encore lecteur, composé
C'est un système de traitement des données reçues associé à une base de données.
Les applications de ce système d'identification sont diverses et variées, comme par exemple :
Par l'intermédiaire de l'antenne :
Les antennes de la base et du transpondeur sont suivies de circuits d'accord qui filtrent le signal afin de ne sélectionner que la fréquence radio du système.
La base génère un champ magnétique, le transpondeur situé à proximité, capte ce champ magnétique et le transforme en tension qui alimente le système de conversion d'énergie du transpondeur.
Le système de conversion d'énergie redresse le signal, le filtre, puis stabilise la tension (régulation) afin d'alimenter la puce du transpondeur.
L'envoi de données est superposée à la fréquence radio du système. Les données sont modulées par la base, puis après réception, elle sont démodulées par le transpondeur pour être ensuite traitées et/ou mémorisées.
Le transpondeur passif ne peut émettre de signal, la transmission de données du transpondeur vers la base doit donc être gérée par la base. Pour répondre à une demande de données, le transpondeur module sa consommation d'énergie. Cette variation de consommation d'énergie est interprétée comme une suite de données modulées, on parle de rétro-modulation.
Plusieurs fréquences radio sont utilisées pour la communication entre base et transpondeur
Il existe de nombreuses normes pour la technologie RFID, voici quelques exemples de ces normes :
Les normes ISO11784 et ISO11785 ainsi que la norme ISO14223 définissent les identifications électroniques des animaux.
Les cartes sans contact (badges, carte bancaire, billetterie, paiement, ...) respectent les normes ISO14443 et ISO14443-4 ou encore ISO15693.
Le standard EPC (Electronic Product Code) définit un code unique pour chaque objet qui permet la traçabilité, l'identification à distance :
Il intègre
Notez que je décline toutes responsabilités quant aux conséquences que pourraient avoir l'application des méthodes et conseils suivants. Ceux-ci pourraient être erronés ou obsolètes.
Cette utilisation dépend du lecteur utilisé, on propose ici d'utiliser le lecteur ACR 122U qui fonctionne avec les badges de fréquence 13.56MHz comme les transpondeurs Mifare, ISO14443A et ISO14443B. Ce lecteur est compatible avec la librairie libnfc. Celle-ci contient des outils qui permettent, entre autres, de lire et écrire des données dans un transpondeur. La version de cette librairie peut être importante, cela dépend de la distribution linux. La version 1.7.1 peut ne pas fonctionner à cause du driver acr122_usb. Les outils comme nfc-mfclassic de la version 1.8.0 posent des problèmes lors de l'écriture de certains blocs (en dehors ud bloc en lecture seule) des transpondeurs Mifare. La solution consiste à mixer les deux versions en compilant, par exemple, la version 1.7.1 avec les sources du driver usb122_usb de la version 1.8.0 (solution trouvée après une recherche sur internet).
Le système Mifare est fabriqué par NXP Semiconductors et présente les caractéristiques suivantes :
La documentation complète est disponible sur le site de NXP.
La mémoire de 1ko est décomposée en 16 secteurs de 4 blocs de 16 octets.
Chaque secteur contient 3 blocs de 16 octets de données et le bloc 3 qui contient deux clés d'accès A et B ainsi que 3 octets d'accessibilité (octets 6 à 8) aux données en fonction des clés. LA clé A occupe les octets 0 à 5, la clé B les octets 10 à 15. L'octet 9 est disponible.
Chaque bloc possède 8 niveaux d'accès codes sur 3 bits. Le bloc 3 ayant des modes d'accès différents. Ces 4 paquets de 3 bits sont stockés dans les 3 octets d'accès en utilisant les bits et leurs compléments. Sur la figure chaque bit est représenté par Cxy avec x qui représente le numéro du bit et y le numéro du bloc.
Attention une erreur d'écriture de ces bits peut rendre le bloc ou le secteur inaccessible et ce de manière irréversible !!.
Pour connaître les valeurs de ces octets en fonction du type d'accès recherché, consulter la documentation NXP.
Notez que je décline toutes responsabilités quant aux conséquences que pourraient avoir l'utilisation des programmes présentés. Ceux-ci pourraient être erronés ou obsolètes.
Il est possible de lire ou bien modifier les données avec l'application Mifare Classic Tool disponible sur Android.
Attention cette application ne permet que d'écrire des octets bruts, il faut donc être sûr de la valeur de ces octets. Si les octets d'accès sont erronés, cela peut rendre le bloc ou secteur inutilisable à tout jamais !!!
La documentation des fonctions de la librairie libnfc est disponible, voici les fonctions utilisées dans les exemples de lecture et écriture de données sur un badge Mifare :
nfc_init(contexte)
initialise la structure de données de la librairie de type nfc_context
. Le paramètre contexte correspond à l'adresse d'un pointeur (allocation dynamique)nfc_open(contexte,liste)
initialise le driver et retourne un identifiant lecteur de type pointeur vers nfc_device
qui sera utilisé par toutes les fonctions d'accès au badge par l'intermédiaire du lecteur de badge. contexte est la structure de données initialisée par init. liste est la liste des lecteurs disponibles, NULL sélectionne le premier lecteur disponible, ce qui est le cas si un seul lecteur est connecté.nfc_initiator_init(lecteur)
initialise le lecteur en utilisant l'identifiant lecteur retourné par la fonction nfc_open
.nfc_device_get_name(lecteur)
retourne une chaîne de caractères contenant l'identification du lecteur.nfc_initiator_select_passive_target(lecteur,typebadge,options,tailleoptions,infos)
avec lecteur identifiant retourné par nfc_open
, typebadge structure de données de type nfc_modulation
qui contient les caractéristiques du badge, options tableau d'octets qui peut être NULL si aucune option supplémentaires n'est nécessaire, tailleoptions taille du tableau d'octets, infos qui est l'adresse d'une liste de données de type nfc_target
.nfc_device_set_property_bool(lecteur,propriete,valeur)
modifie une propriété de type nfc_property
avec valeur.nfc_initiator_transceive_bytes(lecteur,octets,tailledonnees,reponse,taillereponse,timeout)
transfert des données au badge via le lecteur identifié par lecteur . octets est le tableau d'octets à transférer, tailledonnees est la taille de ce tableau, reponse est le tableau d'octets qui contient la réponse du transfert, taille reponse est la taille maximale de la réponse acceptée, timeout est le temps maximum accepté (0 bloque jusqu'à réception, -1 valeur par défaut). Cette fonction retourne le nombre d'octets de la réponse.nfc_close(lecteur)
ferme l'accès au driver.nfc_exit(contexte)
libère la mémoire allouée à la structure de données.Code du fichier NFCMFException.h
#ifndef __NFCMFEXCEPTION_H
#define __NFCMFEXCEPTION_H
#include <string>
#include <exception>
using namespace std;
class NFCMFException : public exception {
public:
const string message;
NFCMFException(const string& msg) : message(msg) { }
~NFCMFException() { }
virtual const char* what() const throw () {
return message.c_str();
}
};
#endif
Code du fichier NFCMFClassic_defs.h
L'ensemble des valeurs de commande de la carte Mifare Classic est obtenue à partir de la documentation de la carte.
#ifndef __NFCMFClassic_DEFS_H
#define __NFCMFClassic_DEFS_H
#define MI_SIZE_AUTH 10
#define MI_SIZE_BLOC 16
typedef struct sMifareAuth {
uint8_t cle[6];
uint8_t uid[4];
} TMifareAuth;
typedef enum
{
MC_REQA = 0x26, // 7 bits
MC_WAKE_UP = 0x52, // WUPA wake-up (7 bits)
MC_CL1_SELECT = 0x93, // 0x93 0x20 Anticollision CL1
MC_CL1_ANTCOL = 0x93, // 0x93 0x70 Select CL1
MC_CL2_SELECT = 0x95, // 0x95 0x20 Anticollision CL2
MC_CL2_ANTCOL = 0x95, // 0x95 0x70 Select CL2
MC_HALT = 0x50, // 0x50 0x00 HLTA (ISO14443-3)
MC_AUTH_A = 0x60, // Authentication command cle A
MC_AUTH_B = 0x61, // Authentication command cle B
MC_PERS_UID = 0x40, // personalisation UID
MC_MODE_TYPE = 0x43, // set mode type
MC_READ = 0x30, // lecture bloc
MC_WRITE = 0xA0, // ecriture bloc
MC_DECREMENT = 0xC0, //
MC_INCREMENT = 0xC1, //
MC_RESTORE = 0xC2, //
MC_TRANSFER = 0xB0 //
} MifareCommande;
#endif
Code du fichier NFCMFClassic.h
Les informations de modulations correspondent à la carte Mifare Classic.
#ifndef __NFCMFClassic_H
#define __NFCMFClassic_H
#include <nfc/nfc.h>
#include "NFCMFClassic_defs.h"
class NFCMFClassic {
private:
const nfc_modulation nfcmodMifare = {
.nmt = NMT_ISO14443A,
.nbr = NBR_106,
};
nfc_context *contexte;
nfc_device *lecteur;
std::string nomLecteur;
bool initialiser(void);
int envoyercommande(uint8_t ,uint8_t ,uint8_t *,uint8_t ,uint8_t *);
public:
NFCMFClassic(void);
~NFCMFClassic(void);
void print_hex(uint8_t *,size_t);
std::string getNomLecteur(void);
int getUID(uint8_t *);
int authentification(MifareCommande ,TMifareAuth ,uint8_t ,uint8_t *);
int lireBloc(uint8_t ,uint8_t *) ;
int ecrireBloc(uint8_t ,uint8_t *) ;
};
#endif
L'ensemble des fonctions d'authentification, lecture et écriture utilisent toutes la méthode privée envoyercommande qui consiste à désactiver l'encapsulation des commandes mifare dans les commandes du lecteur afin d'envoyer directement les commandes mifare au lecteur, puis à transmettre une commande mifare. Le paramètre commande est l'une des valeurs définies dans le fichier NFCMFClassic_defs.h.
Code du fichier NFCMFClassic.cpp
#include <iomanip>
#include <cstring>
#include <string>
#include "NFCMFClassic.h"
#include "NFCMFClassicException.h"
using namespace std;
bool NFCMFClassic::initialiser() {
nfc_init(&contexte);
if (contexte == NULL) {
cerr << "nfc_init erreur (allocation)" << endl ;
return false;
}
lecteur = nfc_open(contexte, NULL);
if (lecteur == NULL) {
cerr << "nfc_open erreur" << endl ;
return false;
}
if (nfc_initiator_init(lecteur) < 0) {
cerr << "nfc_initiator_init erreur" << endl ;
return false;
}
nomLecteur = nfc_device_get_name(lecteur);
return true;
}
int NFCMFClassic::envoyercommande(uint8_t commande,uint8_t numeroBloc,uint8_t *datain,uint8_t tailleparam,uint8_t *dataout) {
int taille=0;
uint8_t cmd[512];
uint8_t recu[512];
int sizecmd;
int resultat;
cmd[0] = commande;
cmd[1] = numeroBloc;
memcpy(cmd+2,datain,tailleparam);
sizecmd = tailleparam + 2;
resultat = nfc_device_set_property_bool(lecteur, NP_EASY_FRAMING, 1);
if (resultat < 0) {
throw NFCMFException("erreur property");
}
resultat = nfc_initiator_transceive_bytes(lecteur, cmd, sizecmd , recu, sizeof(recu), -1);
if (resultat < 0) {
throw NFCMFException("erreur transfert");
}
taille = resultat ;
memcpy(dataout,recu,taille);
return taille;
}
NFCMFClassic::NFCMFClassic() {
if (!initialiser()) {
throw NFCMFException("erreur initialisation");
}
}
NFCMFClassic::~NFCMFClassic() {
nfc_close(lecteur);
nfc_exit(contexte);
}
void NFCMFClassic::print_hex(uint8_t *pbtData, size_t szBytes) {
size_t i;
for (i = 0; i < szBytes; i++) {
cout << setfill('0') << setw(2) << hex << int(pbtData[i]) << " ";
}
cout << endl ;
}
string NFCMFClassic::getNomLecteur() {
return nomLecteur;
}
int NFCMFClassic::getUID(uint8_t *uid) {
nfc_target nt;
if (nfc_initiator_select_passive_target(lecteur, nfcmodMifare, NULL, 0, &nt) > 0) {
memcpy(uid,nt.nti.nai.abtUid,nt.nti.nai.szUidLen);
return nt.nti.nai.szUidLen;
}
return 0;
}
int NFCMFClassic::authentification(MifareCommande typecle,TMifareAuth cle,uint8_t numeroBloc,uint8_t *dataout) {
int taille = 0;
nfc_target ant[1];
int resultat = nfc_initiator_select_passive_target(lecteur, nfcmodMifare, NULL, 0, &ant[0]) ;
if (resultat < 0) {
throw NFCMFException("Erreur select passive target");
}
resultat = envoyercommande(typecle, numeroBloc, (uint8_t *)&cle,MI_SIZE_AUTH, dataout);
if (resultat < 0) {
throw NFCMFException("Erreur envoi commande");
}
return taille ;
}
int NFCMFClassic::lireBloc(uint8_t numeroBloc,uint8_t *dataout) {
int taille = 0;
taille = envoyercommande(MC_READ, numeroBloc, NULL, 0 ,dataout);
return taille ;
}
int NFCMFClassic::ecrireBloc(uint8_t numeroBloc,uint8_t *datain) {
int taille = 0;
uint8_t recu[512];
taille = envoyercommande(MC_WRITE, numeroBloc, datain, MI_SIZE_BLOC ,recu);
return taille ;
}
Code du fichier Makefile
CC=g++
CFLAGNFC = $(shell pkg-config --cflags /chemin/libnfc/lib/pkgconfig/libnfc.pc)
LFLAGNFC = $(shell pkg-config --libs /chemin/libnfc/lib/pkgconfig/libnfc.pc)
CFLAGS= -Wall $(CFLAGNFC)
LDFLAGS= $(LFLAGNFC)
EXEC=nfmfdump
SRC= $(wildcard *.cpp)
OBJS= $(SRC:.cpp=.o)
all: $(OBJS)
$(CC) -o $(EXEC) $^ $(LDFLAGS)
%.o: %.cpp
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm *.o $(EXEC)
Les commandes shell utilisent pkg-config pour obtenir les informations de compilation et d'édition de lien afin d'inclure la librairie libnfc.
Dans le programme de test on utilise la clé A par défaut des badges Mifare Classic.
Le programme de test lit l'ensemble des données du badge, soit les 64 blocs de 16 octets, incluant les blocs contenant les clés, en sachant le la clé A n'est jamais accessible en lecture, et que la clé B l'est dans la configuration par défaut.
Code du fichier nfcmfdump.cpp
#include <iostream>
#include <cstring>
#include "NFCMFClassic.h"
#include "NFCMFClassicException.h"
using namespace std;
int main(int argc, char **argv) {
uint8_t uid[7];
uint8_t taille_uid;
uint8_t reponse[512];
int resultat;
TMifareAuth authUid = {
.cle = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff }
};
try {
NFCMFClassic mifare;
cout << "lecteur " << mifare.getNomLecteur() << endl ;
taille_uid = mifare.getUID(uid);
cout << "uid de " << int(taille_uid) << " octets : ";
mifare.print_hex(uid,taille_uid);
memcpy(authUid.uid,uid,taille_uid);
for(int i=0;i<64;i+=1) {
resultat = mifare.authentification(MC_AUTH_A,authUid,i,reponse);
resultat = mifare.lireBloc(i,reponse);
mifare.print_hex(reponse,resultat);
}
}
catch (const NFCMFException &e) {
cerr << e.what() << endl ;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Code du fichier Makefile
CC=g++
CFLAGNFC = $(shell pkg-config --cflags /usr/local/libnfc/lib/pkgconfig/libnfc.pc)
LFLAGNFC = $(shell pkg-config --libs /usr/local/libnfc/lib/pkgconfig/libnfc.pc)
CFLAGS= -Wall $(CFLAGNFC)
LDFLAGS= $(LFLAGNFC)
EXEC=nfmfreadwrite
SRC= $(wildcard *.cpp)
OBJS= $(SRC:.cpp=.o)
all: $(OBJS)
$(CC) -o $(EXEC) $^ $(LDFLAGS)
%.o: %.cpp
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm *.o $(EXEC)
Le programme de test lit le bloc 0, écrit "www.perga.fr" dans le bloc 2, puis lit le bloc 2 pour vérifier que l'écriture s'est bien passée.
Le bloc de 16 octets est mis à 0 avant de copier le message dans ce bloc.
Code du fichier nfcmfreadwrite.cpp
#include <iostream>
#include <cstring>
#include "NFCMFClassic.h"
#include "NFCMFClassicException.h"
using namespace std;
int main(int argc, char **argv) {
uint8_t uid[7];
uint8_t taille_uid;
uint8_t reponse[512];
uint8_t donnees[16] ;
int resultat;
TMifareAuth authUid = {
.cle = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff }
};
try {
NFCMFClassic mifare;
cout << "lecteur " << mifare.getNomLecteur() << endl ;
taille_uid = mifare.getUID(uid);
cout << "uid de " << int(taille_uid) << " octets : ";
mifare.print_hex(uid,taille_uid);
memcpy(authUid.uid,uid,taille_uid);
resultat = mifare.authentification(MC_AUTH_A,authUid,0,reponse);
resultat = mifare.lireBloc(0,reponse);
mifare.print_hex(reponse,resultat);
string message("www.perga.fr");
resultat = mifare.authentification(MC_AUTH_A,authUid,2,reponse);
memset(donnees,0,16);
memcpy(donnees,message.c_str(),16);
resultat = mifare.ecrireBloc(2,donnees);
resultat = mifare.authentification(MC_AUTH_A,authUid,2,reponse);
resultat = mifare.lireBloc(2,reponse);
mifare.print_hex(reponse,resultat);
}
catch (const NFCMFException &e) {
cerr << e.what() << endl ;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
On utilise le module RFID MFRC522 équipée du circuit MFRC522.
Il est vivement conseillé de se reporter à la programmation de la carte MKR1010 ou encore à la programmation Arduino MKR.
On utilise la librairie RFID_MFRC522v2 disponible à partir du gestionnaire de bibliothèque de l'IDE Arduino. Les exemples suivants utilisent les classes et fonctions décrites dans la documentation complète :
Dans la fonction setup :
MFRC522{driver}
Constructeur avec driver qui est un objet de la classe MFRC522DriverSPI
, objet construit avec un objet de la classe MFRC522DriverPinSimple
qui lui-même est construit avec le id de la broche CS utilisée pour sélectionner le module RFID.objetmfrc522.PCD_Init()
pour initialiser le circuitDans la fonction loop
objetmfrc522.PICC_IsNewCardPresent()
retourne vrai si la carte est présenteobjetmfrc522.PICC_ReadCardSerial()
retourne vrai si l'UID de la carte est accessible en lecture.objetmfrc522.MIFARE_CalculateAccessBits(octetsaccess,bitsbloc0,bitsbloc1,bitsbloc2,bitsbloc3)
calcule les octets d'accès à partir des 3 bits d'accès des 4 blocs. les 3 octets sont stockés dans octetsaccess. Cette méthode est nécessaire lorsque l'on veut changer le mode d'accès au bloc ou secteur.objetmfrc522.PCD_Authenticate(typecle,numbloc,cle,uid)
effectue la demande d'authentification, typecle est une constante qui vaut MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A
ou MFRC522Constants::PICC_CMD_MF_AUTH_KEY_B
, numbloc est le numéro de bloc auquel on veut accéder, cle est un tableau de 6 octets qui contient la clé A ou B, uid est l'adresse de l'UID de la carte qui peut être obtenu avec objetmfrc522.uid
. Cette fonction retourne un code de statut qui vaut MFRC522Constants::STATUS_OK
si tout s'est bien passé.objetmfrc522.MIFARE_Read(numerobloc,contenu,taille,typecle)
lit un bloc de 16 octets, numerobloc est le numéro du bloc, contenu est une suite d'octets qui contiendra le contenu du bloc suivi de deux octets de CRC, taille est une adresse qui contient la taille du bloc qui vaut 18 car elle inclut le CRC, typecle est un constante qui précise si on utilise la clé A ou B.objetmfrc522.PCD_CalculateCRC(contenu,taille,crc)
calcule le crc des octets transmis dans contenu, taille représente la taille des données dans le crc, crc est un tableau de 2 octets qui contient le CRC. Ce CRC calculé peut être comparé avec le crc reçu lors de la lecture.objetmfrc522.write(numerobloc,contenu,taille)
écrit le contenu dans le bloc numerobloc, la taille correspond à la taille du bloc sans le crc (16).objetmfrc522.PICC_HaltA()
passe la puce en mode haltobjetmfrc522.PCD_StopCrypto1()
termine la phase d'authentification, cette méthode précédée de objetmfrc522.PICC_HaltA()
termine les échanges de données et permet un accès à une nouvelle carte.
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>
MFRC522DriverPinSimple ss_pin(7);
MFRC522DriverSPI driver{ss_pin};
MFRC522 mfrc522{driver};
MFRC522::MIFARE_Key key_A = {
.keyByte = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff}
};
byte bloc[20];
MFRC522::StatusCode statut;
byte octetsaccess[3];
void printoctets(byte *octets, byte taille) {
for (byte i = 0; i < taille; i++) {
Serial.print(octets[i] < 0x10 ? " 0" : " ");
Serial.print(octets[i], HEX);
}
}
bool CartePresente() {
if ( !mfrc522.PICC_IsNewCardPresent()) {
Serial.print(".");
delay(100);
return false;
}
Serial.println();
Serial.println("carte presente");
if (!mfrc522.PICC_ReadCardSerial()) {
Serial.print("Erreur PICC_ReadCardSerial ");
return false;
}
return true;
}
bool LireBloc(byte bloc,MFRC522::MIFARE_Key key_A,byte *contenu) {
byte taille = 18;
byte crc[2];
statut = mfrc522.PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A,bloc,&key_A,&(mfrc522.uid));
if (statut != MFRC522Constants::STATUS_OK) {
Serial.print("Erreur authentification ");
Serial.println(statut);
return false;
}
if (mfrc522.MIFARE_Read(bloc,contenu,&taille) == MFRC522Constants::STATUS_OK) {
statut = mfrc522.PCD_CalculateCRC(contenu,16,crc);
if (statut != MFRC522Constants::STATUS_OK) {
Serial.print("Erreur fonction calcul CRC ");
Serial.println(statut);
return false;
}
if ((crc[0] != contenu[16]) || (crc[1] != contenu[17])) {
Serial.println("Erreur CRC");
return false;
}
}
return true;
}
void TermineAcces() {
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
void setup() {
Serial.begin(115200);
while (!Serial);
mfrc522.PCD_Init();
Serial.println("C'est parti");
MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);
Serial.println(F("on démarre"));
}
void loop() {
if (!CartePresente()) {
return;
}
mfrc522.MIFARE_CalculateAccessBits(octetsaccess,0,0,0,1);
printoctets(octetsaccess,3);
Serial.println();
for(byte numbloc=0;numbloc<8;numbloc +=1) {
if (LireBloc(numbloc,key_A,bloc)) {
Serial.print(numbloc);
Serial.print(": ");
printoctets(bloc, 16);
Serial.println();
}
}
delay(200);
TermineAcces();
}
L'objet mfrc522 est construit à partir d'un driver SPI pour une utilisation avec le bus SPI. Le driver SPI est construit avec le numéro de broche qui est utilisée pour la sélection du circuit.
La fonction CartePresente retourne vrai si la carte est présente et si son UID est accessible, prête pour un accès en authentification/lecture.
La fonction LireBloc réalise l'authentification, si celle-ci est ok, elle fait la lecture, ci celle-ci est ok, elle calcule le CRC des données reçues, ci celui-ci est Ok, elle compare les octets de crc calculés avec ceux reçus. Si ces octets sont égaux tout est OK et la fonction retourne vrai. En cas d'erreur dans une des étapes, la fonction retourne faux.
La fonction TermineAcces appelle les deux fonctions qui permettent de mettre fin à l'authentification pour permettre une nouvelle carte.
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>
MFRC522DriverPinSimple ss_pin(7);
MFRC522DriverSPI driver{ss_pin};
MFRC522 mfrc522{driver};
MFRC522::MIFARE_Key key_A = {
.keyByte = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff}
};
byte bloc[20];
MFRC522::StatusCode statut;
byte octetsaccess[3];
void printoctets(byte *octets, byte taille,bool withascii=false) {
for (byte i = 0; i < taille; i++) {
Serial.print(octets[i] < 0x10 ? " 0" : " ");
Serial.print(octets[i], HEX);
}
if (withascii) {
Serial.print(" \"");
for (byte i = 0; i < taille; i++) {
if (octets[i]>=0x20) {
Serial.print((char)octets[i]);
}
else {
Serial.print(".");
}
}
Serial.print("\"");
}
}
void chainetooctets(const char *chaine, byte *octets) {
int longueur = strlen(chaine);
for(int i=0;i<16;i+=1) {
if (i<longueur) {
octets[i] = (byte)chaine[i];
}
else {
octets[i] = 0;
}
}
}
bool CartePresente() {
if ( !mfrc522.PICC_IsNewCardPresent()) {
Serial.print(".");
delay(100);
return false;
}
Serial.println();
Serial.println("carte presente");
if (!mfrc522.PICC_ReadCardSerial()) {
Serial.print("Erreur PICC_ReadCardSerial ");
return false;
}
return true;
}
bool LireBloc(byte bloc,MFRC522::MIFARE_Key key_A,byte *contenu) {
byte taille = 18;
byte crc[2];
statut = mfrc522.PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A,bloc,&key_A,&(mfrc522.uid));
if (statut != MFRC522Constants::STATUS_OK) {
Serial.print("Erreur authentification ");
Serial.println(statut);
return false;
}
if (mfrc522.MIFARE_Read(bloc,contenu,&taille) == MFRC522Constants::STATUS_OK) {
statut = mfrc522.PCD_CalculateCRC(contenu,16,crc);
if (statut != MFRC522Constants::STATUS_OK) {
Serial.print("Erreur fonction calcul CRC ");
Serial.println(statut);
return false;
}
if ((crc[0] != contenu[16]) || (crc[1] != contenu[17])) {
Serial.println("Erreur CRC");
return false;
}
}
return true;
}
bool EcrireBloc(byte bloc,MFRC522::MIFARE_Key key_A,byte *contenu) {
byte taille = 16;
statut = mfrc522.PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A,bloc,&key_A,&(mfrc522.uid));
if (statut != MFRC522Constants::STATUS_OK) {
Serial.print("Erreur authentification ");
Serial.println(statut);
return false;
}
if (mfrc522.MIFARE_Write(bloc,contenu,taille) != MFRC522Constants::STATUS_OK) {
Serial.println("Erreur Ecriture");
return false;
}
return true;
}
void TermineAcces() {
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
void setup() {
Serial.begin(115200);
while (!Serial);
mfrc522.PCD_Init();
Serial.println("C'est parti");
MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);
Serial.println(F("On démarre"));
}
void loop() {
if (!CartePresente()) {
return;
}
mfrc522.MIFARE_CalculateAccessBits(octetsaccess,0,0,0,1);
printoctets(octetsaccess,3);
Serial.println();
byte numbloc = 4 ;
Serial.print("lecture bloc ");
if (LireBloc(numbloc,key_A,bloc)) {
Serial.print(numbloc);
Serial.print(": ");
printoctets(bloc, 16);
Serial.println();
}
chainetooctets("www.perga.fr",bloc);
Serial.print("Ecriture nouveau bloc : ");
printoctets(bloc, 16,true);
Serial.println();
if (!EcrireBloc(numbloc,key_A,bloc)) {
Serial.println("Erreur ecriture bloc");
}
Serial.print("lecture bloc ");
if (LireBloc(numbloc,key_A,bloc)) {
Serial.print(numbloc);
Serial.print(": ");
printoctets(bloc, 16,true);
Serial.println();
}
delay(200);
TermineAcces();
}
En plus de l'exemple précédent, on a la fonction EcrireBloc qui éccit le bloc après que celui-ci ait été modifié. Cette fonction réalise l'authentification, si celle-ci est ok, elle effectue l'écriture du bloc. La fonction retourne vrai si tout s'est bien passé.
Il est vivement conseillé de se reporter à la programmation de la carte picow ou encore à la programmation de la raspberry pico.
On utilise la librairie mfrc522 pour raspberry pico qui fournit un exemple d'utilisation qui est une base de départ pour utiliser la librairie. Cette librairie est, comme cela est indiqué dans les commentaires, une adaptation de la version Arduino. Les exemples qui suivent ont été construit à partir de cet exemple.
Fonctions d'initialisation
MFRC522_Init()
initialisation qui retourne un pointeur MFRC522Ptr_t
vers une structure de données de type MFRC522_T
.PCD_Init(mfrc,spi)
associe la libraire à la liaison spi, mfrc est le pointeur retourné par la fonction précédente, spi représente la liaison spi (spi0,...).Fonctions dans la boucle infinie
PICC_IsNewCardPresent()
retourne vrai si une carte est présente sur le lecteur.PICC_ReadCardSerial(mfrc)
retourne les informations de la carte comme par exemple l'uid dans la structure mfrc.PCD_Authenticate(mfrc,typecle,numerobloc,cle,uid)
MIFARE_Read(mfrc,numerobloc,donnees,ptaille)
PCD_CalculateCRC(mfrc,donnees,taille,crc)
MIFARE_Write(mfrc,numerobloc,donnees,taille)
MFRC522_Init()
, typecle est le type de clé A PICC_CMD_MF_AUTH_KEY_A
ou B PICC_CMD_MF_AUTH_KEY_B
, numerobloc est le numéro du bloc, uid est l'uid de la carte contenu dans la structure de donnée mfrc, donnees est le bloc de données de taille 16 en écriture et 18 en lecture pour inclure le crc, ptaille signifie qu'il faut transmettre l'adresse de la taille, initialisée à 18, afin d'avoir la valeur en retour après lecture, taille est la valeur de la taille initialisée à 16, crc est le résultat du calcul du crc. Ces fonctions retourne un code de statut qui vaut STATUS_OK
quand tout s'est bien passé.
PCD_StopCrypto1()
termine la phase d'authentification de cette carte.contenu du fichier CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(PICO_BOARD "pico_w")
pico_sdk_init()
add_executable(picow_rfid_dump
mfrc522.c
picow_rfid_dump.c
)
pico_enable_stdio_usb(picow_rfid_dump 1)
pico_enable_stdio_uart(picow_rfid_dump 1)
pico_add_extra_outputs(picow_rfid_dump)
target_link_libraries(picow_rfid_dump pico_stdlib hardware_spi)
Il convient d'ajouter le fichier mfrc522.c dans la liste des fichiers sources
On utilise la clé par défaut.
L'initialisation utilise la liaison spi0.
Code du programme d'affichage du contenu
#include "mfrc522.h"
MIFARE_Key cleA = {
.keybyte = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff}
};
void printoctets(uint8_t *octets, uint8_t taille) {
for (uint8_t i = 0; i < taille; i++) {
printf(" %02x",octets[i]);
}
}
uint8_t LireBloc(MFRC522Ptr_t mfrc,uint8_t numbloc,MIFARE_Key cleA,uint8_t *bloc) {
uint8_t taille = 18;
uint8_t resCRC[2];
StatusCode statut;
statut=PCD_Authenticate(mfrc,PICC_CMD_MF_AUTH_KEY_A,numbloc,&cleA,&(mfrc->uid));
if (statut != STATUS_OK) {
printf("ERREUR authentification\n");
return 0;
}
statut = MIFARE_Read(mfrc,numbloc,bloc,&taille);
if (statut != STATUS_OK) {
printf("ERREUR lecture\n");
return 0;
}
statut = PCD_CalculateCRC(mfrc,bloc,16,resCRC);
if (statut != STATUS_OK) {
printf("ATTENTION CRC\n");
printoctets(resCRC,2);
printf("\n");
return 0;
}
return taille;
}
void main() {
uint8_t numbloc;
uint8_t bloc[20];
stdio_init_all();
MFRC522Ptr_t mfrc = MFRC522_Init();
PCD_Init(mfrc, spi0);
sleep_ms(5000);
while(1) {
printf("Attente carte\n");
while(!PICC_IsNewCardPresent(mfrc));
printf("selectionne carte\n");
PICC_ReadCardSerial(mfrc);
printf("UID : ");
for(int i = 0; i < 4; i++) {
printf("%x ", mfrc->uid.uidByte[i]);
}
printf("\n");
for(numbloc=0;numbloc<8;numbloc+=1) {
uint8_t taille = LireBloc(mfrc,numbloc,cleA,bloc);
printoctets(bloc,taille);
printf("\n");
}
PCD_StopCrypto1(mfrc);
sleep_ms(200);
}
}
contenu du fichier CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(PICO_BOARD "pico_w")
pico_sdk_init()
add_executable(picow_rfid_readwrite
mfrc522.c
picow_rfid_readwrite.c
)
pico_enable_stdio_usb(picow_rfid_readwrite 1)
pico_enable_stdio_uart(picow_rfid_readwrite 1)
pico_add_extra_outputs(picow_rfid_readwrite)
target_link_libraries(picow_rfid_readwrite pico_stdlib hardware_spi)
Le programme affiche le contenu du bloc 0 du secteur 1, puis le modifie en écrivant "www.perga.fr" dans une boucle infinie, Ce programme n'a qu'un intérêt pédagogique.
La fonction chainetooctets permet d'insérer une chaîne de caractère dans un bloc de 16 octets, si la chaîne est plus longue, les 16 premiers caractères de la chaîne sont copié, si la chaîne contient moins de 16 octets, des 0 sont ajoutés.
Code du programme de modification d'un bloc
#include "mfrc522.h"
MIFARE_Key cleA = {
.keybyte = { 0xff , 0xff , 0xff , 0xff , 0xff , 0xff}
};
void printoctets(uint8_t *octets, uint8_t taille) {
for (uint8_t i = 0; i < taille; i++) {
printf(" %02x",octets[i]);
}
}
int LireBloc(MFRC522Ptr_t mfrc,uint8_t numbloc,MIFARE_Key cleA,uint8_t *bloc) {
uint8_t taille = 18;
uint8_t resCRC[2];
StatusCode statut;
statut=PCD_Authenticate(mfrc,PICC_CMD_MF_AUTH_KEY_A,numbloc,&cleA,&(mfrc->uid));
if (statut != STATUS_OK) {
printf("ERREUR authentification\n");
return 0;
}
statut = MIFARE_Read(mfrc,numbloc,bloc,&taille);
if (statut != STATUS_OK) {
printf("ERREUR lecture\n");
return 0;
}
statut = PCD_CalculateCRC(mfrc,bloc,16,resCRC);
if (statut != STATUS_OK) {
printf("ATTENTION CRC\n");
printoctets(resCRC,2);
printf("\n");
return 0;
}
return taille;
}
void chainetooctets(const char *chaine, uint8_t *octets) {
int longueur = strlen(chaine);
for(int i=0;i<16;i+=1) {
if (i<longueur) {
octets[i] = (uint8_t)chaine[i];
}
else {
octets[i] = 0;
}
}
}
uint8_t EcrireBloc(MFRC522Ptr_t mfrc,uint8_t numbloc,MIFARE_Key cleA,uint8_t *bloc) {
uint8_t taille = 16;
StatusCode statut;
statut = PCD_Authenticate(mfrc,PICC_CMD_MF_AUTH_KEY_A,numbloc,&cleA,&(mfrc->uid));
if (statut != STATUS_OK) {
printf("ERREUR authentification\n");
return 0;
}
statut = MIFARE_Write(mfrc,numbloc,bloc,taille);
if (statut != STATUS_OK) {
printf("ERREUR ecriture\n");
return 0;
}
return taille;
}
void main() {
uint8_t numbloc;
uint8_t bloc[20];
stdio_init_all();
MFRC522Ptr_t mfrc = MFRC522_Init();
PCD_Init(mfrc, spi0);
sleep_ms(5000);
while(1) {
printf("Attente carte\n");
while(!PICC_IsNewCardPresent(mfrc));
printf("selectionne carte\n");
PICC_ReadCardSerial(mfrc);
printf("UID : ");
for(int i = 0; i < 4; i++) {
printf("%x ", mfrc->uid.uidByte[i]);
}
printf("\n");
numbloc=4;
uint8_t taille = LireBloc(mfrc,numbloc,cleA,bloc);
printoctets(bloc,taille);
printf("\n");
uint8_t message[17] = "www.perga.fr";
chainetooctets(message,bloc);
taille = EcrireBloc(mfrc,numbloc,cleA,bloc);
if (taille != 16) {
printf("Erreur ecriture\n");
}
taille = LireBloc(mfrc,numbloc,cleA,bloc);
printoctets(bloc,taille);
printf("\n");
PCD_StopCrypto1(mfrc);
sleep_ms(200);
}
}
On utilise la librairie mfrc522 qui fournit des exemples d'utilisation qui sont une base de départ pour utiliser la librairie.
Fonctions d'initialisation
MFRC522(spi_id,sck,miso,mosi,cs,rst)
constructeur qui retourne un objet lecteur correspondant au lecteur, spi_id est le numéro de l'interface spi (0,1,...), sck,miso,mosi,cs,rst sont les numéros des gpio de la picow.Fonctions dans la boucle infinie
objetlecteur.request(lecteur.REQIDL)
retourne une liste qui contient le statut (carte présente) ainsi que le type de carte. Le paramètre correspond au mode d'accès. Si tout s'est bien passé, le statut vaut objetlecteur.OK
.objetlecteur.SelectTagSN()
retourne une liste qui contient le statut ainsi que l'uid de la carte. Si tout s'est bien passé, le statut vaut objetlecteur.OK
.objetlecteur.readSectorBlock(uid,secteur,numbloc,keyA=cleA,keyB=cleB)
objetlecteur.writeSectorBlock(uid,secteur,numbloc,bloc,keyA=cle,keyB=cleB)
objetlecteur.SelectTagSN()
, secteur numéro de secteur entre 0 et 15, numbloc numéro du bloc dans le secteur entre 0 et 3, keyA=cleA si on utilise la clé A, keyB=cleB si on utilise la clé B. Ces fonctions retourne le statut objetlecteur.OK
si tout s'est bien passé.
objetlecteur.stop_crypto1()
termine la phase d'authentification de cette carte
from mfrc522 import MFRC522
import time
def printbloc(donnees,ascii=False):
for octet in donnees:
print(f"{octet:02x}",end=' ')
if ascii:
for octet in donnees:
if ((octet >= 0x20) and (octet < 127)):
print(f"{octet:c}",end='')
else:
print(".",end='')
cle = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
lecteur = MFRC522(spi_id=0,sck=18,miso=16,mosi=19,cs=17,rst=15)
while True:
(statut,TagType) = lecteur.request(lecteur.REQIDL)
if statut == lecteur.OK:
print("Carte présente")
(statut, uid) = lecteur.SelectTagSN()
if (statut == lecteur.OK) :
for bloc in range(8):
statut= lecteur.auth(lecteur.AUTHENT1A,bloc,cle,uid)
if (statut == lecteur.OK):
(statut,donnees)= lecteur.read(bloc)
if (statut == lecteur.OK):
print(f"{bloc:02d} : ",end=' ')
printbloc(donnees,True)
print("")
else:
print("erreur lecture")
else:
print("auth err")
else:
print("TAG err")
lecteur.stop_crypto1()
time.sleep(0.2)
Le programme affiche le contenu du badge présent à l'infini.
La fonction printbloc affiche une ligne de 16 octets en hexadécimal suivi, en option, des caractères correspondants s'ils sont affichables.
from mfrc522 import MFRC522
import time
def printbloc(donnees,ascii=False):
for octet in donnees:
print(f"{octet:02x}",end=' ')
if ascii:
for octet in donnees:
if ((octet >= 0x20) and (octet < 127)):
print(f"{octet:c}",end='')
else:
print(".",end='')
def chaineversbloc(chaine):
bloc=list()
longueur=len(chaine)
for i in range(16):
if i < longueur:
bloc.append(ord(chaine[i]))
else:
bloc.append(0)
return bloc
cle = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
lecteur = MFRC522(spi_id=0,sck=18,miso=16,mosi=19,cs=17,rst=15)
while True:
(statut,TagType) = lecteur.request(lecteur.REQIDL)
if statut == lecteur.OK:
print("Carte présente")
(statut, uid) = lecteur.SelectTagSN()
if (statut == lecteur.OK) :
secteur=1
numbloc=0
(statut,bloc) = lecteur.readSectorBlock(uid,secteur,numbloc,keyA=cle)
print(f"{secteur:02d}.{numbloc:02d} : ",end=' ')
printbloc(bloc,True)
print("")
if (statut == lecteur.OK):
bloc = chaineversbloc("www.perga.fr")
printbloc(bloc,True)
print("")
statut = lecteur.writeSectorBlock(uid,secteur,numbloc,bloc,keyA=cle)
if (statut == lecteur.OK):
print("ecriture OK")
(statut,bloc) = lecteur.readSectorBlock(uid,secteur,numbloc,keyA=cle)
print(f"{secteur:02d}.{numbloc:02d} : ",end=' ')
printbloc(bloc,True)
print("")
else:
print("ecriture err")
else:
rint("auth err")
else:
print("TAG err")
lecteur.stop_crypto1()
time.sleep(0.2)
Le programme affiche le contenu du bloc 0 du secteur 1, puis le modifie en écrivant "www.perga.fr" dans une boucle infinie, Ce programme n'a qu'un intérêt pédagogique.
La fonction chaineversbloc permet d'insérer une chaîne de caractère dans un bloc de 16 octets, si la chaîne est plus longue, les 16 premiers caractères de la chaîne sont copié, si la chaîne contient moins de 16 octets, des 0 sont ajoutés.