Réseaux et Télécommunications
Fermer ×

Le système RFID

Présentation générale

Introduction

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.

Les éléments de base

Un système RFID est composé de plusieurs éléments qui sont le transpondeur, la station de base et le système hôte.

Le transpondeur

Egalement appelé tag, étiquette ou encore badge, composé

La station de base

Egalement appelé interrogateur ou encore lecteur, composé

Le système hôte

C'est un système de traitement des données reçues associé à une base de données.

Applications

Les applications de ce système d'identification sont diverses et variées, comme par exemple :

Fonctionnement des transpondeurs

Différents mode de fonctionnements

Principe de fonctionnement des transpondeurs passifs

Par l'intermédiaire de l'antenne :

  • la base fournit l’énergie au transpondeur,
  • la base peut éventuellement envoyer des commandes pour une demande de lecture et des données (liaison montante) lors d'une écriture
  • le transpondeur répond en envoyant les données stockées en mémoire (liaison descendante),

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.

Echange d'énergie avec les transpondeurs passifs

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.

Voir le principe théorique du transfert d'énergie
L'alimentation des transpondeurs passifs utilise le champ proche, c'est à dire le champ magnétique de l'onde électromagnétique émise par la base. L'échange d'énergie dans l'air se fait avec l'induction magnétique, créée à partir du champ magnétique avec la relation :
B=μH avec μ=μ0μr et μr qui est le perméabilité magnétique relative (1 dans l'air) et μ0=4π10-7, H le champ magnétique en A/m et B l'induction en Tesla.
L'antenne de la base, composée de N spires, peut être circulaire ou rectangulaire. Dans le cas d'une antenne circulaire, Le champ magnétique H s'exprime avec la relation :
H ( d , r ) = r 2 2 [ ( r 2 + d 2 ) 3 2 ] N I
Ces deux courbes nous indiquent que la distance entre lecteur et transpondeur doit être faible (inférieur à 10cm) et que le champ magnétique est maximal pour un rayon de quelques centimètres (exemple r=5cm et d=5cm).
Cette induction magnétique induit un flux qui s'exprime avec la relation Φ=BNS cos(θ) avec N nombre de spires de l'antenne du transpondeur et S sa surface, l'angle θ correspond à l'angle entre le plan de l'antenne de la base et celle du transpondeur ce qui fait que la meilleure transmission se fait lorsque les plans des deux antennes sont parallèles. Les variations du flux Φ génère une tension qui respecte la relation e(t)=-dΦdt, cette tension alimente le convertisseur d'énergie.

Echange de données avec des transpondeurs passifs

Envoi de données

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.

Réception de donné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.

Fréquences, normes et standards

Fréquences utilisées

Plusieurs fréquences radio sont utilisées pour la communication entre base et transpondeur

fréquence inférieure à 135kHz

fréquence de 13.56MHz

fréquence de 860/930MHz

fréquence de 2.44GHz

Normes

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, billeterie, paiement, ...) respectent les normes ISO14443 et ISO14443-4 ou encore ISO15693.

Standard EPC

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

Utilisation sous linux

Note importante

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.

Outils et librairie

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

Caractéristiques

Le système Mifare est fabriqué par NXP Semiconductors et présente les caractéristiques suivantes :

Mifare Classic

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.

Exemples d'échange de données

Note importante

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.

Accès avec un smartphone

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 !!!

Programmation sous linux

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 :

  1. 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)
  2. nfc_open(contexte,liste) initialise le driver et retourne un identifiant lecteur de type pointeur vers nfc_devicequi 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é.
  3. nfc_initiator_init(lecteur) initialise le lecteur en utilisant l'identifiant lecteur retourné par la fonction nfc_open.
  4. nfc_device_get_name(lecteur) retourne une chaîne de caractères contenant l'identification du lecteur.
  5. Accès aux données du badge lecture ou écriture
    • 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.
  6. nfc_close(lecteur) ferme l'accès au driver.
  7. nfc_exit(contexte) libère la mémoire allouée à la structure de données.
Voir le contenu du code de la classe de gestion du badge Mifare classic

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
					
Cette classe dérivée de exception permet de traiter l'ensemble des erreurs des fonctions de la librairie libnfc

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 <iostream>
#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 ;
}
					
Voir le contenu du code de lecture du contenu du badge

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;
}
					
Voir le contenu du code de test de lectrure et écriture

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;
}
					

Lecteur RFID pour l'embarqué

On utilise le module RFID MFRC522 équipée du circuit MFRC522.

Arduino et MKR1010

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 :

  1. 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.
  2. objetmfrc522.PCD_Init() pour initialiser le circuit

Dans la fonction loop

  1. objetmfrc522.PICC_IsNewCardPresent() retourne vrai si la carte est présente
  2. objetmfrc522.PICC_ReadCardSerial() retourne vrai si l'UID de la carte est accessible en lecture.
  3. 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.
  4. 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é.
  5. Accès aux données
    • 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).
  6. objetmfrc522.PICC_HaltA() passe la puce en mode halt
  7. objetmfrc522.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.
Voir le contenu du code de lecture du contenu du badge
#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.

Voir le contenu du code de lecture et d'écriture de données
#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é.

C et raspberry picow

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

  1. MFRC522_Init() initialisation qui retourne un pointeur MFRC522Ptr_t vers une structure de données de type MFRC522_T.
  2. 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

  1. PICC_IsNewCardPresent() retourne vrai si une carte est présente sur le lecteur.
  2. PICC_ReadCardSerial(mfrc) retourne les informations de la carte comme par exemple l'uid dans la structure mfrc.
  3. Accès aux données
    • 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)
    mfrc est le pointeur retourné par la fonction 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é.
  4. PCD_StopCrypto1() termine la phase d'authentification de cette carte.
Voir le contenu du code de lecture du contenu du badge

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);
    }
}
					
Voir le contenu du code de lecture et d'écriture de données

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);
    }
}
					

Python et raspberry picow

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

  1. 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

  1. 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.
  2. 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.
  3. Accès aux données
    • objetlecteur.readSectorBlock(uid,secteur,numbloc,keyA=cleA,keyB=cleB)
    • objetlecteur.writeSectorBlock(uid,secteur,numbloc,bloc,keyA=cle,keyB=cleB)
    uid valeur de l'uid de la carte retourné par 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é.
  4. objetlecteur.stop_crypto1() termine la phase d'authentification de cette carte
Voir le contenu du code de lecture du contenu du badge
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.

Voir le contenu du code de lecture et d'écriture de données
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.