Comecei a trabalhar com Java e a utilizar SmartCards no final de 2006, no mesmo período que comecei a utilizar Gnu/Linux efetivamente. Sim, emprego novo. =p
Mas… o que é um SmartCard?
Bom, basicamente é um cartão de plástico com um chip “embedado”.
Ah, então eu já vi um desses! É aqueles cartões bancários certo?
Isso, também. Mas há outros tipos, não apenas bancários.
Você tem celular? É GSM? Sabe aquele chipzinho que você insere nele?
Então, é um SmartCard também =]
A diferença entre eles é a estrutura criada internamente. Tem também a história das chaves de autenticação e outros detalhes que complicam bastante, dependendo do que precisa ser feito.
Como conheço um pouco sobre isso e já percebi que algumas pessoas ainda têm dúvidas, resolvi criar um pequeno tutorial com códigos em java para acesso ao SmartCard, para envio de alguns comando simples, apenas para começar a brincadeira.
Aqui considero que você já possui uma leitora com os drivers instalados, possui o JDK 1.6 (java) instalado (utilizaremos a classe SmartCardIO, que está disponível a partir dessa versão) e corretamente configurado, além de conhecimento sobre o funcionamento e comunicação com SmartCards (informações podem ser obtidas aqui e também aqui).
A comunicação com o cartão se dá através de comandos APDU (definida na ISO 7816-4).
Em java, antes de enviar o comando é necessário alguns passos importantes:
Criar uma “fábrica” de leitoras:
factory = TerminalFactory.getDefault();
Preencher uma lista com as leitoras conectadas e iniciadas:
terminals = factory.terminals().list();
Realizar a conexão com o cartão:
card = terminal.connect("t=0");
Abrir um canal para comunicação com o cartão:
channel = card.getBasicChannel();
Após esses passos é possível recuperar o ATR do cartão, que é sua resposta quando o mesmo é energizado:
atr = card.getATR();
Antes de enviar o comando APDU é desejável que seja feita a verificação para garantir que há um cartão na leitora e evitar uma exception
if(!terminal.isCardPresent()) {
}
Após a verificação, podemos enviar o comando desejado através do canal criado, recebendo a resposta do cartão:
lastResponse = channel.transmit(new CommandAPDU(myCommand));
Certo, esse é o básico. Mas temos um problema: A comunicação é feita através de array de bytes, e vai dar muito trabalho para converter toda hora que for preciso enviar um comando ou receber resposta e exibí-la. Basta criar uma classe para fazer a conversão =D Lembrando, valores HEXADECIMAIS.
A seguir uma classe simples que criei para demonstrar os passos citados e sua utilização.
import java.util.List;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
/**
* Classe para comunicação com SmartCards
* @author Luiz Gustavo Silva Marcondes
* http://lgmarcondes.wordpress.com
*/
public class MySmartCardTest {
private TerminalFactory factory;
private List<CardTerminal> terminals;
private CardTerminal terminal;
private Card card;
private CardChannel channel;
private ResponseAPDU lastResponse;
public MySmartCardTest() {
try {
if(initialization("t=0")) {
//Se conseguiu inicializar, vamos
//recuperar informações do ATR
//(Answer to Response) do cartão
System.out.println(getAtr());
}
} catch (CardException ex) {
ex.printStackTrace();
}
}
/**
* Inicializa a seção, criando um canal para se
* comunicar com o SmartCard.
* @param protocol o procoloco a ser utilizado
* ("T=0", "T=1", or "T=CL"), ou "*" para se
* conectar usando qualquer protocolo disponível
* @return true em caso de inicialização correta.
*/
public boolean initialization(String protocol)
throws CardException {
//Criar uma "Fábrica" de Terminais (leitoras)
factory = TerminalFactory.getDefault();
//Preencher a lista de Terminais (leitoras)
terminals = factory.terminals().list();
//Exibe mensagem e retorna se não achar leitoras
if(terminals.size() == 0) {
JOptionPane.showMessageDialog(null, "Erro,"+
" nenhuma leitora foi encontrada.");
return false;
}
//Box contendo a lista das leitoras
JComboBox readersList;
readersList = new JComboBox(terminals.toArray());
int opcao = JOptionPane.showConfirmDialog(null,
readersList, "Escolha a leitora: ",
JOptionPane.OK_CANCEL_OPTION);
if (opcao == JOptionPane.OK_OPTION) {
terminal = terminals.
get(readersList.getSelectedIndex());
} else {
return false;
}
//Ligação com o cartão utilizando protocolo de
//comunicação T=0
card = terminal.connect("t=0");
//Abre um canal com um cartão
channel = card.getBasicChannel();
return true;
}
/**
* Retorna o ATR - Answer to Reset - Resposta do
* cartão ao reset
* @return o ATR
*/
public String getAtr() {
//Armazena o ATR do cartão em um array de bytes
byte[] atr = card.getATR().getBytes();
String response = "";
//Converte a resposta de array de bytes para
//strings hexadecimais em letras maiúsculas
for (int i = 0; i < atr.length; i++) {
if (atr[i] < 0) {
response += Integer.toHexString(256 +
atr[i]).toUpperCase();
} else {
if (atr[i] < 16)
response += "0";
response += Integer.toHexString(atr[i])
.toUpperCase();
}
}
return response;
}
/**
* Envia o comando APDU para o SmartCard e retorna a
* APDU de resposta.
* @param apdu uma string que contém o comando APDU.
* @return uma string que contém a APDU de resposta.
* @throws CardException
*/
public String sendApdu(String apdu)
throws CardException {
//Se não houver cartão na leitora retorna
if(!terminal.isCardPresent())
return "";
//Exibe o comando a ser enviado
System.out.println("Command APDU: " + apdu);
//Converte a string contendo o comando para um
//array de bytes
byte[] buffer = strToHexByte(apdu);
//Envia a APDU e guarda resposta em lastResponse
lastResponse = channel.transmit(
new CommandAPDU(buffer));
String response = "";
//Armazena a resposta (SW) em forma de string
response = Integer.toHexString(
lastResponse.getSW());
response += " ";
byte[] data = lastResponse.getData();
for (int i = 0; i < data.length; i++) {
if (data[i] < 0) {
response += Integer.toHexString(256 +
data[i]).toUpperCase();
} else {
if (data[i] < 16)
response += "0";
response += Integer.toHexString(data[i])
.toUpperCase();
}
}
return response.toUpperCase();
}
/**
* Converte uma string hexadecimal em array de bytes
* @param str a string hexadecimal a ser convertida.
* @return um array de bytes com a strign convertida.
*/
public byte[] strToHexByte(String string) {
byte[] b = new byte[string.length() / 2];
try {
String s1;
int j=0;
for(int i = 0; i < string.length(); i += 2) {
s1 = string.substring(i, i + 2);
b[j] = (byte)Integer.parseInt(s1, 16);
j++;
}
} catch(NumberFormatException ex) {
ex.printStackTrace();
} catch(StringIndexOutOfBoundsException ex) {
ex.printStackTrace();
}
return b;
}
public static void main(String args[]) {
MySmartCardTest myTest;
myTest = new MySmartCardTest();
try {
String myAPDU, myAnswer;
myAPDU = "00A40400023F00";
myAnswer = myTest.sendApdu(myAPDU);
System.out.println("Resp: " + myAnswer);
} catch(CardException ex) {
ex.printStackTrace();
}
}
}
Bom, como o Corinthians apenas empatou ontem, por hoje é só! =p
Escrito por lgmarcondes