SmartCard + Java

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

8 Respostas para “SmartCard + Java”

  1. Ricardo Disse:

    Prezado Marcondes,
    Tem como copiar um cartão smart? Fazer um igual ao outro literalmente?
    Grato desde já pela atenção que puder dispensar.
    Ricardo

  2. lgmarcondes Disse:

    Olá Ricardo,
    O que poderia ser feito é recriar a estrutura de arquivos com os dados desejados, porém, os sistemas bancários, por exemplo, utilizam chaves de criptografia para impedir a visualização dos dados.
    Há também outras diferenças internas entre os tipos de cartões encontrados.

  3. Douglas Disse:

    Pra mim esta dando o seguinte erro:

    javax.smartcardio.CardException: connect() failed
    at sun.security.smartcardio.TerminalImpl.connect(TerminalImpl.java:67)
    at MySmartCardTest.initialization(MySmartCardTest.java:79)
    Command APDU: 00A40400023F00
    at MySmartCardTest.(MySmartCardTest.java:29)
    at MySmartCardTest.main(MySmartCardTest.java:191)
    Caused by: sun.security.smartcardio.PCSCException: SCARD_W_UNRESPONSIVE_CARD
    at sun.security.smartcardio.PCSC.SCardConnect(Native Method)
    at sun.security.smartcardio.CardImpl.(CardImpl.java:65)
    at sun.security.smartcardio.TerminalImpl.connect(TerminalImpl.java:61)
    … 3 more
    Exception in thread “main” java.lang.NullPointerException
    at MySmartCardTest.sendApdu(MySmartCardTest.java:136)
    at MySmartCardTest.main(MySmartCardTest.java:196)
    Java Result: 1

  4. Kennedy Disse:

    Bom dia,

    gostaria de saber os custos e se possivel os fabricantes de leitores e cartões que funcionam com essa API.

    Alguém saberia me dizer onde posso adquirir isso?

    obrigado,

    Kennedy

  5. Marcelo Disse:

    Caro Marcondes,

    Muito boa sua explanação. Estou tentando assinar digitalmente um pdf, mas estou tendo muitas dificuldades. Como ler o cartão, pegar o certificado nele, etc… Não tenho idéia de como isso é feito. Seria feito através dos comandos APDU ?? Onde poderia pegar uma listagem desses comandos ??

    Obrigado.

  6. Juliano Disse:

    Olá Marcondes,
    estou muito inressado em fazer minha monografia utilzando a tecnologia Java Card. Poderia me enviar um email infomando-me onde posso comprar um reader/writer e alguns cards “em branco” para começar a brincadeira?
    Obrigado!

  7. Gabriel Disse:

    Por favor, vc poderia me ajudar??

    Na hora em que envio o APDU ele esta dando uma exception

    Exception in thread “main” java.lang.IllegalArgumentException: Invalid APDU: length=8, b1=254

    O ATR recupera o B7940081FE9000D1, mas quando realizo o strToHexByte, ele gera apenas com tamanho 8.
    Nao consigo entender…primeiro ele funcionou normalmente, dps começou essa exception.
    Sera que vc pode me ajudar?

  8. lgmarcondes Disse:

    Gabriel
    O comando APDU que está tentando enviar é o mesmo do exemplo?
    Qual tipo de cartão está utilizando?

    Geralmente esse erro aparece quando o tamanho do comando está incorreto.

Deixe uma resposta