quinta-feira, maio 28, 2015

RFID: NFC Shield

Para exercitarmos um pouco o que vimos sobre o MIFARE, precisamos de um módulo que suporte este tipo de comunicação. Vou usar um NFC Shield que comprei algum tempo atrás na DealExtreme (o modelo que eu comprei está esgotado, mas tem outros equivalentes).

NFC?
NFC (Near Field Communication) é um padrão de comunicação via rádio a pequenas distâncias (até 10 cm). Não por acaso, este padrão é o mesmo usado na comunicação dos cartões e tags MIFARE.

O Shield

O coração do shield NFC (cuja documentação pode ser vista na loja e na wiki do fabricante) é o chip PN532 do NXP. É um circuito integrado bastante versátil (inclui um microcontrolador); o firmware suporta uma quantidade grande de recursos (o manual tem 200 páginas e não entra nos detalhes do comandos MIFARE).

Felizmente existe uma biblioteca para uso do PN532 (desenvolvida pela Adafruit com colaborações do SeeedStudio). Ela disponibiliza apenas o básico, mas é o suficiente para algumas experiências. Eu usei a versão no site da Elecfreaks, mas no site da Adafruit tem um versão mais nova.

A comunicação entre o PN532 e o Arduino é via SPI, os demais pinos permanecem disponíveis (a placa possui conectores para interligar um outro shield). A antena está na própria placa, o alcance é limitado a 1 cm.

Cartões e Tags

Para fazer testes precisamos de alguns cartões e tags. Eu comprei alguns no SeeedStudio, todos com 1K byte de memória:
  • Cartões
  • Tag em formato de chaveiro
  • Tag adesivo (etiqueta para livros)
Minha coleção de tags MIFARE
Tag MIFARE no formato de chaveiro
A etiqueta adesiva permite ver a antena.
Demonstração

O objetivo desta demonstração é exercitar a  gravação e leitura de dados e o uso das chaves de segurança. O software requer a biblioteca PN532:
  1. // Exemplo de leitura/escrita em cartão/tag MIFARE com 1K de memória  
  2. // usando um NFC Shield baseado no PN532 da NXP  
  3. // Código a partir de exemplos do Seeed Technology Inc (www.seeedstudio.com)  
  4.   
  5. #include <PN532.h>  
  6.   
  7. // Pinos usados para conexão  
  8. #define SCK 13  
  9. #define MOSI 11  
  10. #define SS 10  
  11. #define MISO 12  
  12.   
  13. // Objeto para acesso ao PN532  
  14. PN532 nfc(SCK, MISO, MOSI, SS);  
  15.   
  16. // Chave de fabrica  
  17. // Pode mudar conforme o fabricante do cartao/tag !  
  18. uint8_t chave_fab[]= {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};  
  19.   
  20. // Chaves para o nosso teste  
  21. uint8_t chave_A[]= {0x01,0x02,0x03,0x04,0x05,0x06};  
  22. uint8_t chave_B[]= {0x06,0x05,0x04,0x03,0x02,0x01};  
  23.   
  24. // Blocos que vamos usar  
  25. // No MIFARE 1K os blocos são numerados de 0 a 63  
  26. // Cada setor tem 4 blocos, o último bloco do setor é de configuração  
  27. const uint8_t bloco_dados = 12;  // protegeBloco assume que é o 1o bloco do setor  
  28. const uint8_t bloco_cfg = 15;    // CUIDADO COM O QUE ESCREVER AQUI  
  29.   
  30.   
  31. // Iniciacao  
  32. void setup(void) {  
  33.   Serial.begin(9600);  
  34.   Serial.println("Demonstracao MIFARE");  
  35.   Serial.println();  
  36.   nfc.begin();  
  37.   
  38.   uint32_t versiondata = nfc.getFirmwareVersion();  
  39.   if (! versiondata) {  
  40.     Serial.print("NFC shield nao encontrada");  
  41.     while (1);   // fica parado aqui  
  42.   }  
  43.     
  44.   // Informacoes sobre o shield  
  45.   Serial.print("Achou NFC Shield ");   
  46.   Serial.println((versiondata>>24) & 0xFF, HEX);   
  47.   Serial.print("Firmware ver. ");   
  48.   Serial.print((versiondata>>16) & 0xFF, DEC);   
  49.   Serial.print('.');   
  50.   Serial.println((versiondata>>8) & 0xFF, DEC);  
  51.     
  52.   // configura a placa  
  53.   nfc.SAMConfig();  
  54. }  
  55.   
  56. // Laco principal  
  57. void loop(void) {  
  58.   uint32_t id;  
  59.   char cmd [3];  
  60.   uint8_t bloco[16];  
  61.   uint8_t *pChave;  
  62.     
  63.   // procura um cartao ou tag MIFERE  
  64.   id = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A);  
  65.   if (id == 0) {  
  66.     // nao achou  
  67.     delay (500);  // aguarda meio segundo  
  68.     return;     // tenta de novo  
  69.   }  
  70.     
  71.   Serial.println();  
  72.   Serial.print("Achou cartao #");   
  73.   Serial.println(id);  
  74.   Serial.print("Cmd: ");  
  75.   leCmd (cmd);  
  76.   switch (cmd[0]) {  
  77.     case 'P':  // Protege  
  78.       if (!protegeBloco (id)) {  
  79.         Serial.println ("Erro ao proteger o bloco!");  
  80.       } else {  
  81.         Serial.println ("Bloco protegido.");  
  82.       }  
  83.       break;  
  84.     case 'D':  // Desprotege  
  85.       if (!cfgPadrao (id))      
  86.       {  
  87.         Serial.println ("Erro ao retornar a config de fabrica!");  
  88.       } else {  
  89.         Serial.println ("Retornado a config de fabrica.");  
  90.       }  
  91.       break;  
  92.     case 'L':  // Lê  
  93.       pChave = chave_fab;  
  94.       if (cmd[1] == 'A')  
  95.         pChave = chave_A;  
  96.       else if (cmd[1] == 'B')  
  97.         pChave = chave_B;  
  98.       if (!nfc.authenticateBlock(1, id, bloco_dados,   
  99.         (cmd[1] == 'B')? KEY_B : KEY_A, pChave))  
  100.       {  
  101.         Serial.println ("Autenticacao falhou!");  
  102.         break;  
  103.       }  
  104.       if (!nfc.readMemoryBlock(1, bloco_dados, bloco))  
  105.       {  
  106.         Serial.println ("Erro na leitura!");  
  107.         break;  
  108.       }  
  109.       dump (bloco);  
  110.       break;  
  111.     case 'E':  // Escreve  
  112.       pChave = chave_fab;  
  113.       if (cmd[1] == 'A')  
  114.         pChave = chave_A;  
  115.       else if (cmd[1] == 'B')  
  116.         pChave = chave_B;  
  117.       if (!nfc.authenticateBlock(1, id, bloco_dados,   
  118.         (cmd[1] == 'B')? KEY_B : KEY_A, pChave))  
  119.       {  
  120.         Serial.println ("Autenticacao falhou!");  
  121.         break;  
  122.       }  
  123.       for (int i = 0; i < 16; i++)  
  124.         bloco[i] = i;  
  125.       if (!nfc.writeMemoryBlock(1, bloco_dados, bloco))  
  126.       {  
  127.         Serial.println ("Erro na escrita!");  
  128.         break;  
  129.       }  
  130.       dump (bloco);  
  131.       break;  
  132.   }  
  133.   esperaEnter();  
  134. }  
  135.     
  136. // Configura as chaves A e B  
  137. // Escrita com chave B  
  138. // Leitura com A ou B  
  139. int protegeBloco (uint32_t id) {  
  140.   uint8_t bloco[16];  
  141.   
  142.   // Monta a configuração    
  143.   memcpy (bloco, chave_A, 6);  
  144.   bloco[6] = 0x78;  
  145.   bloco[7] = 0x77;  
  146.   bloco[8] = 0x88;  
  147.   bloco[9] = 0;  
  148.   memcpy (bloco+10, chave_B, 6);  
  149.   dump (bloco);  
  150.   
  151.   // Autentica no bloco de configuracao  
  152.   if (!nfc.authenticateBlock(1, id, bloco_cfg, KEY_A, chave_fab)) {  
  153.     Serial.println ("Autenticacao com chave da fabrica na cfg falhou!");  
  154.     return false;  
  155.   }  
  156.     
  157.   // Grava o bloco de configuracao  
  158.   if (!nfc.writeMemoryBlock (1, bloco_cfg, bloco)) {  
  159.     Serial.println ("Erro ao gravar cfg!");  
  160.     return false;  
  161.   }  
  162.     
  163.   return true;  
  164. }  
  165.   
  166. // Volta a configuracao de fabrica  
  167. int cfgPadrao (uint32_t id) {  
  168.   uint8_t bloco[16];  
  169.   
  170.   // Monta a configuração    
  171.   memcpy (bloco, chave_fab, 6);  
  172.   bloco[6] = 0xFF;  
  173.   bloco[7] = 0x07;  
  174.   bloco[8] = 0x80;  
  175.   bloco[9] = 0;  
  176.   memcpy (bloco+10, chave_fab, 6);  
  177.   
  178.   // Autentica no bloco de configuracao  
  179.   if (!nfc.authenticateBlock(1, id, bloco_cfg, KEY_B, chave_B)) {  
  180.     Serial.println ("Autenticacao com chave B na cfg falhou!");  
  181.     return false;  
  182.   }  
  183.     
  184.   // Grava o bloco de configuracao  
  185.   if (!nfc.writeMemoryBlock (1, bloco_cfg, bloco)) {  
  186.     Serial.println ("Erro ao gravar cfg!");  
  187.     return false;  
  188.   }  
  189.     
  190.   return true;  
  191. }  
  192.   
  193. // Le um comando finaliado por ENTER  
  194. void leCmd (char *cmd)  
  195. {  
  196.   int i = 0;  
  197.   int c;  
  198.     
  199.   while (true) {  
  200.     c = Serial.read();  
  201.     if (c == -1)  
  202.       continue;  
  203.     if (c == 0x0d)  
  204.       break;  
  205.     if ((c >= 'a') && (c <= 'z')) {  
  206.       c -= 0x20;  
  207.     }  
  208.     if (i < 4)  
  209.       cmd[i++] = c;  
  210.   }  
  211.   cmd[i] = 0;  
  212.   Serial.println (cmd);  
  213. }  
  214.   
  215.   
  216. // Aguarda receber um ENTER  
  217. void esperaEnter() {  
  218.   Serial.print ("Digite ENTER...");  
  219.   while (Serial.read() != 0x0d)  
  220.     ;  
  221.   Serial.println ();  
  222. }  
  223.   
  224. // Lista o conteudo de um bloco  
  225. void dump (uint8_t *pBloco)  
  226. {  
  227.   for (uint8_t i=0; i<16; i++)  
  228.   {  
  229.     Serial.print(*pBloco++, HEX);  
  230.     Serial.print(" ");  
  231.   }  
  232.   Serial.println();  
  233. }  
Atenção que para usar a biblioteca da Elecfreaks com as versões mais recentes do Arduino é necessário trocar as linhas #include <WProgram.h> por #include <Arduino.h>.

A chave de fábrica utilizada na demonstração é a dos cartões e tags que eu tenho. Cartões e tags de outro fabricante podem ter uma chave diferente.

O transcript abaixo mostra a seguinte sequência de operações:
  • O setor de dados é lido com a chave de fábrica.
  • São reprogramadas as chaves e as condições de acesso.
  • Não é mais possível a leitura do setor de dados com a chave de fábrica.
  • A leitura do setor de dados pode ser feita com as chaves A e B.
  • A escrita no setor de dados só pode ser feita com a chave B.
  • São restauradas as chaves e condições de acesso de fábrica.
Demonstracao MIFARE

Achou NFC Shield 32
Firmware ver. 1.6

Achou cartao #706831521
Cmd: L
0 1 2 3 4 5 6 7 8 9 A B C D E F 
Digite ENTER...

Achou cartao #706831521
Cmd: P
1 2 3 4 5 6 78 77 88 0 6 5 4 3 2 1 
Bloco protegido.
Digite ENTER...

Achou cartao #706831521
Cmd: L
Autenticacao falhou!
Digite ENTER...

Achou cartao #706831521
Cmd: LA
0 1 2 3 4 5 6 7 8 9 A B C D E F 
Digite ENTER...

Achou cartao #706831521
Cmd: LB
0 1 2 3 4 5 6 7 8 9 A B C D E F 
Digite ENTER...

Achou cartao #706831521
Cmd: EA
Erro na escrita!
Digite ENTER...

Achou cartao #706831521
Cmd: EB
0 1 2 3 4 5 6 7 8 9 A B C D E F 
Digite ENTER...

Achou cartao #706831521
Cmd: D
Retornado a config de fabrica.
Digite ENTER...

Achou cartao #706831521
Cmd: L
0 1 2 3 4 5 6 7 8 9 A B C D E F 
Digite ENTER...
ATENÇÃO: ao reprogramar as configurações de segurança você corre o risco de perder o acesso ao setor. Se você for fazer experiências, se prepare para ficar com um cartão ou tag "capenga"!

Nenhum comentário: