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:
// Exemplo de leitura/escrita em cartão/tag MIFARE com 1K de memória
// usando um NFC Shield baseado no PN532 da NXP
// Código a partir de exemplos do Seeed Technology Inc (www.seeedstudio.com)

#include <PN532.h>

// Pinos usados para conexão
#define SCK 13
#define MOSI 11
#define SS 10
#define MISO 12

// Objeto para acesso ao PN532
PN532 nfc(SCK, MISO, MOSI, SS);

// Chave de fabrica
// Pode mudar conforme o fabricante do cartao/tag !
uint8_t chave_fab[]= {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};

// Chaves para o nosso teste
uint8_t chave_A[]= {0x01,0x02,0x03,0x04,0x05,0x06};
uint8_t chave_B[]= {0x06,0x05,0x04,0x03,0x02,0x01};

// Blocos que vamos usar
// No MIFARE 1K os blocos são numerados de 0 a 63
// Cada setor tem 4 blocos, o último bloco do setor é de configuração
const uint8_t bloco_dados = 12;  // protegeBloco assume que é o 1o bloco do setor
const uint8_t bloco_cfg = 15;    // CUIDADO COM O QUE ESCREVER AQUI


// Iniciacao
void setup(void) {
  Serial.begin(9600);
  Serial.println("Demonstracao MIFARE");
  Serial.println();
  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("NFC shield nao encontrada");
    while (1);   // fica parado aqui
  }
  
  // Informacoes sobre o shield
  Serial.print("Achou NFC Shield "); 
  Serial.println((versiondata>>24) & 0xFF, HEX); 
  Serial.print("Firmware ver. "); 
  Serial.print((versiondata>>16) & 0xFF, DEC); 
  Serial.print('.'); 
  Serial.println((versiondata>>8) & 0xFF, DEC);
  
  // configura a placa
  nfc.SAMConfig();
}

// Laco principal
void loop(void) {
  uint32_t id;
  char cmd [3];
  uint8_t bloco[16];
  uint8_t *pChave;
  
  // procura um cartao ou tag MIFERE
  id = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A);
  if (id == 0) {
    // nao achou
    delay (500);  // aguarda meio segundo
    return;     // tenta de novo
  }
  
  Serial.println();
  Serial.print("Achou cartao #"); 
  Serial.println(id);
  Serial.print("Cmd: ");
  leCmd (cmd);
  switch (cmd[0]) {
    case 'P':  // Protege
      if (!protegeBloco (id)) {
        Serial.println ("Erro ao proteger o bloco!");
      } else {
        Serial.println ("Bloco protegido.");
      }
      break;
    case 'D':  // Desprotege
      if (!cfgPadrao (id))    
      {
        Serial.println ("Erro ao retornar a config de fabrica!");
      } else {
        Serial.println ("Retornado a config de fabrica.");
      }
      break;
    case 'L':  // Lê
      pChave = chave_fab;
      if (cmd[1] == 'A')
        pChave = chave_A;
      else if (cmd[1] == 'B')
        pChave = chave_B;
      if (!nfc.authenticateBlock(1, id, bloco_dados, 
        (cmd[1] == 'B')? KEY_B : KEY_A, pChave))
      {
        Serial.println ("Autenticacao falhou!");
        break;
      }
      if (!nfc.readMemoryBlock(1, bloco_dados, bloco))
      {
        Serial.println ("Erro na leitura!");
        break;
      }
      dump (bloco);
      break;
    case 'E':  // Escreve
      pChave = chave_fab;
      if (cmd[1] == 'A')
        pChave = chave_A;
      else if (cmd[1] == 'B')
        pChave = chave_B;
      if (!nfc.authenticateBlock(1, id, bloco_dados, 
        (cmd[1] == 'B')? KEY_B : KEY_A, pChave))
      {
        Serial.println ("Autenticacao falhou!");
        break;
      }
      for (int i = 0; i < 16; i++)
        bloco[i] = i;
      if (!nfc.writeMemoryBlock(1, bloco_dados, bloco))
      {
        Serial.println ("Erro na escrita!");
        break;
      }
      dump (bloco);
      break;
  }
  esperaEnter();
}
  
// Configura as chaves A e B
// Escrita com chave B
// Leitura com A ou B
int protegeBloco (uint32_t id) {
  uint8_t bloco[16];

  // Monta a configuração  
  memcpy (bloco, chave_A, 6);
  bloco[6] = 0x78;
  bloco[7] = 0x77;
  bloco[8] = 0x88;
  bloco[9] = 0;
  memcpy (bloco+10, chave_B, 6);
  dump (bloco);

  // Autentica no bloco de configuracao
  if (!nfc.authenticateBlock(1, id, bloco_cfg, KEY_A, chave_fab)) {
    Serial.println ("Autenticacao com chave da fabrica na cfg falhou!");
    return false;
  }
  
  // Grava o bloco de configuracao
  if (!nfc.writeMemoryBlock (1, bloco_cfg, bloco)) {
    Serial.println ("Erro ao gravar cfg!");
    return false;
  }
  
  return true;
}

// Volta a configuracao de fabrica
int cfgPadrao (uint32_t id) {
  uint8_t bloco[16];

  // Monta a configuração  
  memcpy (bloco, chave_fab, 6);
  bloco[6] = 0xFF;
  bloco[7] = 0x07;
  bloco[8] = 0x80;
  bloco[9] = 0;
  memcpy (bloco+10, chave_fab, 6);

  // Autentica no bloco de configuracao
  if (!nfc.authenticateBlock(1, id, bloco_cfg, KEY_B, chave_B)) {
    Serial.println ("Autenticacao com chave B na cfg falhou!");
    return false;
  }
  
  // Grava o bloco de configuracao
  if (!nfc.writeMemoryBlock (1, bloco_cfg, bloco)) {
    Serial.println ("Erro ao gravar cfg!");
    return false;
  }
  
  return true;
}

// Le um comando finaliado por ENTER
void leCmd (char *cmd)
{
  int i = 0;
  int c;
  
  while (true) {
    c = Serial.read();
    if (c == -1)
      continue;
    if (c == 0x0d)
      break;
    if ((c >= 'a') && (c <= 'z')) {
      c -= 0x20;
    }
    if (i < 4)
      cmd[i++] = c;
  }
  cmd[i] = 0;
  Serial.println (cmd);
}


// Aguarda receber um ENTER
void esperaEnter() {
  Serial.print ("Digite ENTER...");
  while (Serial.read() != 0x0d)
    ;
  Serial.println ();
}

// Lista o conteudo de um bloco
void dump (uint8_t *pBloco)
{
  for (uint8_t i=0; i<16; i++)
  {
    Serial.print(*pBloco++, HEX);
    Serial.print(" ");
  }
  Serial.println();
}
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: