Archivo de la categoría: SmartCard

Tecnología SmartCard utilizada en varios paises como documento de identidad, asi mismo tarjetas de crédito e idenfitcación biométrica.

Driver SmartCard VISA Alpha0.1

GTY_EMV_Chip_MEM_150930_12x5_1600

Hola estoy trabajando en este driver para lectura de tarjetas visa, una de las clases principales que compone este programa es la siguiente:

/*
 * Version 2.0 Revision 1
 *
 * 01/03/2014
 *
 * Copyright 2013-2016 gigaDatta, S.A.
 * Julio Francisco Chinchilla Valenzuela - gigadatta@gmail.com
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

package com.gd.creditcard.visa;

import com.gd.creditcard.entitys.CreditCard;
import com.gd.creditcard.tlv.utils.EMVTags;
import com.gd.smartcard.SCardConexion;
import com.gd.utils.ByteConverts;
import com.gd.utils.ByteEngine;
import javax.smartcardio.ATR;

import javax.smartcardio.CardException;

/**
 *
 * @author Julio Chinchilla
 */
public final class VisaSCDataRead extends CreditCard {

    public VisaSCDataRead(int pcsc, String protocol) throws CardException, Exception {
        SCardConexion c = new SCardConexion();
        ATR atr = c.connect(pcsc, protocol);
        setAtr(com.gd.utils.ByteConverts.byteToHex(atr.getBytes()));
        VisaCardCheck checkVisa = new VisaCardCheck(atr);
        if (checkVisa.isVisaCard()) {
            setIssuerData(checkVisa.getIssuerData());
            c.transmit(APDU.VISA);
            byte[] visaData = c.transmit(APDU.RECORD0C).getData();
            c.transmit(APDU.PAYSYSDDF01);
            byte[] paySysData = c.transmit(APDU.RECORD0C).getData();
            scannData(visaData, paySysData);
        } else {
            throw new Exception("No es una tarjeta visa");
        }
    }

    public void scannData(byte[] visaData, byte[] paySysData) {
        setNumber(VisaNumberScanner(visaData));
        setDateExpiration(VisaDateExpireScanner(visaData));
        int cardHolderName = ByteEngine.find(visaData, EMVTags.CardHolderName);
        int trackData = ByteEngine.find(visaData, EMVTags.Track1DiscretionaryData);
        setName(ByteConverts.byteToASCII(visaData,cardHolderName+3,(trackData == -1)?visaData.length-1:trackData-3));
        setPaySys(PaySysScanner(paySysData));
    }

    private String PaySysScanner(byte[] paySysData) {
        int trackData = ByteEngine.find(paySysData, EMVTags.ApplicationPreferredName);
        int applIndicator = ByteEngine.find(paySysData, EMVTags.ApplicationPriorityIndicator);
        return ByteConverts.byteToASCII(paySysData,trackData+3,(trackData+3<applIndicator)?applIndicator-1:paySysData.length-1);
    }

    private String VisaNumberScanner (byte[] data) {
        String num = ByteConverts.byteToHex(data,4,11).replace(" ","");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < num.length(); i++) {
            sb.append(num.charAt(i));
            if( ((i+1) % 4) == 0) {
                sb.append(" ");
            }
        }
        return sb.toString();
    }

    private String VisaDateExpireScanner (byte[] data) {
        String d = ByteConverts.byteToHex(data,12,14).replace(" ","").substring(1, 5);
        String year = d.substring(0, 2);
        String month = d.substring(2,4);
        return month.concat("/").concat(year);
    }

}

Bancos-en-guatemala1

Como veran es una fase alfa aún en desarrollo, el proyecto se encuentra completo en GitHub VISASmartCardDriver0.1A entre las características del driver es que este cuenta con una lista aún en construcción que posee los emisores de tarjetas visa para Guatemala, en este caso los bancos del país. Entre la lista recavada hasta el momento se encuentran:

0x17 0xB1 BANCO INDUSTRIAL
0x06 0x1B BANRURAL
0x18 0x60 BANRURAL
0x18 0x64 BANRURAL
0x08 0x8B G&T CONTINENTAL
0x15 0x19 BANTRAB

 

/*
 * Version 2.0 Revision 1
 *
 * 01/03/2014
 *
 * Copyright 2013-2016 gigaDatta, S.A.
 * Julio Francisco Chinchilla Valenzuela - gigadatta@gmail.com
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */
package com.gd.creditcard.cardissuer;

import java.nio.ByteBuffer;
import java.util.Map;
import java.util.TreeMap;

/**
 *
 * @author Julio Chinchilla
 */
public class VISAIssuerDataGTM {

    public final Map<Short, String> IssuerData = new TreeMap<>();

    public VISAIssuerDataGTM() {
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x17,(byte)0xB1}).getShort(), "BANCO INDUSTRIAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x06,(byte)0x1B}).getShort(), "BANRURAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x18,(byte)0x60}).getShort(), "BANRURAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x18,(byte)0x64}).getShort(), "BANRURAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x08,(byte)0x8B}).getShort(), "BANRURAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x17,(byte)0xAE}).getShort(), "G&T CONTINENTAL");
        IssuerData.put(ByteBuffer.wrap(new byte[] {(byte)0x15,(byte)0x19}).getShort(), "BANTRAB");
    }

}

Estos bytes asociados corresponden al ATR de la tarjeta, específicamente a los bytes en las posiciones 9 y 10, los cuales son conocidos como “Issuer Data”, en fín el driver solo lee los datos mas importantes o que he considerado hasta el momento, ya que la data que se recava es mucho más extensa, si quisieran determinar a exactitud los records extraidos en la data, les recomiendo esta herramienta: TLV Utilities. La cual sirve para parsear los extraído en las aplicaciones que posee el chip regidos bajo el estándar EMV.

Este driver recoge información de los AID’s principales de las tarjetas Visa:

card_large

00 A4 04 00 07 A0 00 00 00 03 10 10

00 A4 04 00 0E 31 50 41 59 2E 53 59 53 2E 44 44 46 30 31

 

Y por si se lo preguntan, el código CVV no viene en los records del chip,  ya que por estandar este se genera en base al número de la tarjeta, el emisor y un algoritmo propio del emisor, esto se hace por seguridad.

Accediendo al nuevo chip Smart Card del DPI (Fase I)

ChipDPI2La curiosidad nos ha llevado lejos, y en esta ocasión no ha sido la excepción. Tengo que decir en palabras bastante simples y resumidas, que la lectura de datos de la zona pública es bastante sencilla. No obstante otros datos como la fotografía principalmente, se encuentran almacenados en una zona de seguridad criptográfica, haciendo así casi imposible la lectura de este dato (de momento aunque seguimos curioseando). En esta fase inicial no pudimos obtener algunos datos que la aplicación oficial de Renap muestra, como lo son el Serial y el MRZ, pero en compensación logramos acceder a otros datos que la aplicación pública de Renap no muestra, como lo son el nuevo y polémico campo ETNIA asi tambien nos topamos con 2 datos mas que en realidad no sabemos que son, en total accedimos a 31 datos del chip, no esta mal para un primer comienzo aunque sentimos que no es suficiente, pero bueno en definitiva el resultado de esta primera investigación fue el siguiente:

Características:

ATR: Anteriormente había publicado un post (muy apresuradamente) donde mencionaba el nuevo ATR, por lo visto estaba equivocado y por eso lo puse en modo oculto. Tal parece ser que ahora ya no se utilizarán 2 ATR para todos los chips como el anterior fabricante, sino que ahora será un ATR por cada DPI (admito que puedo estar equivocado), pero la nueva forma del ATR es la siguiente:

3B FD 96 00 00 81 31 FE 45 00 00 01 XX XX XX XX 00 00 00 00 00 00 XX

Como podrán ver son 4 bytes que varían (256ˆ4 – 1) el último byte tambien varía pero este es un checksum correspondiente a todo el ATR pueden revisar un artículo anterior donde está como calcular el checksum de un ATR aquí. Lo que nos hizo falta fue realizar mas lecturas para corroborar esto. Por lo que hacemos la corrección correspondiente en este post.

Datos:

Logramos acceder a 31 datos del CHIP entre ellos 3 que la aplicación pública de Renap no muestra, los listamos a continuación:

CAMPO DESCRIPCIÓN
Gentilicio No sabemos bien el propósito de este campo, pero este se encuentra antes del CUI y al principio de la Zona Pública
CUI Código Único de Identificación
Primer Nombre Primer nombre del ciudadano
Segundo Nombre Segundo Nombre del ciudadano
Primer Apellido Primer apellido del ciudadano
Segundo Apellido Segundo apellido del ciudadano
Apellido de Casada Apellido de casada de la ciudadana si lo tuviese
Sexo Género sexual del ciudadano, Masculino o Femenino
Departamento de nacimiento Departamento donde nació el ciudadano
Municipio de nacimiento Municipio donde nació el ciudadano
País de nacimiento Pais donde nació la persona
Fecha de nacimiento Fecha de nacimiento de la persona
Fecha de emisión Fecha de emisión del documento
Fecha de vencimiento Fecha de vencimiento del documento
Nacionalidad País de nacionalidad
Estado Civil Estado Civil del ciudadano
Departamento de Vecindad Departamento donde está avecindado el ciudadano
Municipio de Vecindad Municipio donde está avecindadno el ciudadano
Libro Libro de la partida de nacimiento
Folio Folio de la partida de nacimiento
Partida Número de partida de nacimiento
Etnia Grupo Etnico al cual pertenece el ciudadano
Número de Cédula Número de Cédula de ciudadano si lo posee
Departamento de Cédula Departamento en el cual fue extendida la cédula
Municipio de Cédula Municipio en el cual fue extendida la cédual
DESCONOCIDO_0 No sabemos que dato es este, lo único que se identifica como S/N
Sabe leer Si el ciudadano sabe leer S/N
Sabe escribir Si el ciudadano sabe escribir S/N
Oficial Activo Si el ciudadano es un oficial activo del ejercito o la policía S/N
Profesión Profesión del ciudadano
DESCONOCIDO_1 En primera instancia concluimos que puede ser el puesto o cargo que desempeña el ciudadano, pero no es una conclusión final

Aca algunas capturas de lo que se logró hacer en una primera instancia:

 

1454566_702330666550607_4502002852617539329_n 10351478_702330669883940_7378564346226784771_n 10959731_702330689883938_3208707829978063719_n

En una segunda instancia, cambiamos los APDU de lectura y encontramos el dato DESCONOCIDO_1 el cual suponemos pertenece al cargo o puesto que desempeña el ciudadano, este campo es diferente al de Profesión:

dpi2 dpi22

Al momento este es el resultado de nuestra Fase I, seguimos curioseando el chip y mas adelante cuando tengamos mas resultados comento en este blog todo lo que me sea permitido publicar.

Sobrevaloración y Retención de la Firma-e en Guatemala

No es muy común usar este espacio para expresar una opinión personal, seré breve y espero este espacio sirva para dar a conocer como están las cosas aca en Guatemala:

El DNI Electrónico de Perú, es fabricado exactamente por la misma empresa que fabrica el DPI en Guatemala, Oberthur Technologies. En Perú este documento no es obligatorio sino que opcional, tiene un precio de 40 Soles lo que vendría a ser unos Q105.00 aproximadamente, lo que impulsa el RENIEC ente emisor del DNI Electronico Peruano, es la firma electrónica, la firma electrónica es un concepto jurídico, equivalente electrónico al de la firma manuscrita, donde una persona acepta el contenido de un mensaje electrónico a través de cualquier medio electrónico válido, esto es un gran impulsor del Gobierno Digital. Aca en Guatemala el ente emisor y autorizado legalmente para emitir firma electrónica es la Cámara de Comercio, y tiene un precio de Q.1,000.00, aunque RENAP liberara las herramientas para realizar firma electrónica con el DPI, este no tiene validez jurídica ya que esta fue otorgada a Cámara de Comercio. El DPI vale Q.80.00, pero tener el negocio de Firma Electrónica por parte de Cámara de Comercio a Q. 1,000.00 y que esté amparado por la ley, no se vale. Todos ya tenemos firma electrónica, solo que muy pocos saben que ya la poseen y aunque la del DPI no tiene validez jurídica, no deberían de pagar mil quetzales por adquirirla.imagen-dni-electronico-peru

 

En conclusión, en Guatemala ya todos tenemos Firma Electrónica, solo que no tiene validéz jurídica, hay que pagar por otro documento sobrevalorado que cuesta Q.1000.00

Comprobación de ATR mediante Suma de Verificación XOR [Código Java]

FE26DS8GJ7MQAYF.MEDIUMCada vez que conectamos un SmartCard y hacemos la conexión correspondiente, obtenemos la cadena de respuesta del Chip conocida como ATR, pero ¿como verificar si el ATR recibido es correcto? para ello la norma ISO estable que el último byte recibido corresponde a uno de verificación desde el byte T0 hasta el Tj, donde T0 es el segundo byte del atr en mención y el byte Tj corresponde al penúltimo, por lo que el último es un byte de comprobación. Para ello se debe de usar una suma de verificación XOR.

ISO7816_23

Por ejemplo y para hacerlo de manera sencilla el ATR siguiente se comprobarían de la siguiente manera:

3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C3 08 01 00 0F 90 00 9B

3B sería el byte incial, el cual se omite
DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C3 08 01 00 0F 90 00 Sería la serie de bytes a comprobar (T0 – Tj)
9B sería la suma de verificación de los bytes T0 – Tj
por lo cual si la suma de verifciación xor desde T0 hasta Tj es distinta de 9B, este ATR estaría incorrecto o alterado.

 

A continuación un código de java que facilita la comprobación debida.

/*
 * Version 1.0 Revision 1
 *
 * 18/08/2014
 *
 * Copyright 2014 bit502
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */



/**
 *
 * @author Julio Chinchilla
 */
public class ATRVerify {

    /**
     * Función que recibe un ATR en formato String con separador de espacio
     * entre cada byte, la función formatea la cadena de caracteres quitándole
     * los espacios en blanco, posteriormente separa los bytes T0 hasta TJ en
     * el String t0tj y separa el byte de comprobación final TCK, luego utiliza
     * la función getXORCheckSumValue para obtener la suma verificativa y lo
     * compara con el byte TCK, si los bytes coinciden la respuesta será true
     * de lo contrario será false, y el ATR a verificar estará incorrecto
     * @param atr ATR con espacios entre bytes
     * @return
     */
    public static boolean checkATRStrWS (String atr) {
        boolean resp = false;
        String t0tj = atr.replaceAll(&amp;quot;\\s&amp;quot;,&amp;quot;&amp;quot;);
        String tck = atr.substring(atr.length()-2, atr.length());
        t0tj = t0tj.substring(2, t0tj.length()-2);
        if (getXORCheckSumValue(t0tj).equals(tck))
            resp = true;
        return resp;
    }

    /**
     * Función que recibe un ATR en formato String con separador de espacio
     * entre cada byte, posteriormente separa los bytes T0 hasta TJ en
     * el String t0tj y separa el byte de comprobación final TCK, luego utiliza
     * la función getXORCheckSumValue para obtener la suma verificativa y lo
     * compara con el byte TCK, si los bytes coinciden la respuesta será true
     * de lo contrario será false, y el ATR a verificar estará incorrecto
     * @param atr ATR sin espacios entre bytes
     * @return
     */
    public static boolean checkATRStr (String atr) {
        boolean resp = false;
        String t0tj = atr.substring(2, atr.length()-2);;
        String tck = atr.substring(atr.length()-2, atr.length());
        if (getXORCheckSumValue(t0tj).equals(tck))
            resp = true;
        return resp;
    }

    /**
     * Función que realiza una suma de verificación a una cadena String en
     * formato Hexadecimal con el operador XOR
     * @param bytes Cadena String en formato hexadecimal a analizar
     * @see &amp;lt;a href=&amp;quot;http://es.wikipedia.org/wiki/Suma_de_verificaci%C3%B3n&amp;quot;&amp;gt;WikiES: Suma de verificación&amp;lt;/a&amp;gt;
     * @return String de comprobación mediante Suma de Verificación HEX
     */
    private static String getXORCheckSumValue (String bytes) {
        int cc = 0;
        for (int i = 0; i &amp;lt; bytes.length(); i=(i+2)) {
            if (i==0){
                cc = Integer.parseInt(bytes.substring(i, i+2), 16) &amp;amp; (0x00FF);
            } else {
                cc^=(Integer.parseInt(bytes.substring(i, i+2),16)&amp;amp;(0x00FF));
                cc=cc&amp;amp;(0x00FF);
            }
        }
        return Integer.toString(cc, 16).toUpperCase();
    }

}

Su forma de uso es la siguiente:

/**
 *
 * @author Julio
 */
public class ATRVerify {

    public static final String GT01 = &amp;quot;3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C3 08 01 00 0F 90 00 9B&amp;quot;;
    public static final String GT02 = &amp;quot;3BDB960080B1FE451F830031C064C7FC10000F90007A&amp;quot;;
    public static final String ATR1 = &amp;quot;3B DB 18 00 80 1F 03 00 31 C0 64 77 E3 03 00 82 90 00 4F&amp;quot;;

    public static void main(String[] args) {
        System.out.println(checkATRStrWS(GT02));
        System.out.println(checkATRStr(GT02));
        System.out.println(checkATRStrWS(ATR1));
    }

}

ATR’s Utilizados por el RENAP en el DPI

Que es el ATR:
atr
Una Respuesta al reajuste (ATR) es una salida de mensaje por un contacto de tarjeta inteligente conforme a 7.816 normas ISO / IEC, tras el rearme eléctrico de el chip de la tarjeta por un lector de tarjetas. El ATR transmite información acerca de los parámetros de comunicación propuestas por la tarjeta, y la naturaleza de la tarjeta y el estado.

Por extensión, ATR a menudo se refiere a un mensaje obtenido de una tarjeta inteligente en un estadio temprano de la comunicación; o desde el lector de tarjetas utilizado para acceder a la tarjeta, que puede transformar el mensaje de la tarjeta en un formato de ATR-como (esto ocurre por ejemplo, para algunos lectores de tarjetas de PC / SC [1] [2] al acceder a un 14,443 tarjeta inteligente ISO / IEC) .

Los ATR utilizados por el RENAP son los siguientes:
3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C3 08 01 00 0F 90 00 9B
3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C7 FC 10 00 0F 90 00 7A

Aca dejo un código que hace la conexión SmartCard utilizando el primer ejemplo que desarrollamos aca en bit502 http://goo.gl/9MkNxt este código lee el ATR y comprueba si es un DPI emitido por el RENAP
imagen-oficial-DPI-2013-oficial

/*
 * 17/08/2014
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

package test;

import com.gd.smartcard.SCardConexion;
import javax.smartcardio.CardException;

/**
* @version 1.0 Agosto de 2014
* @author Julio Chinchilla
*/
public class DPIVerify {
    
    //ATR's utilizados por el RENAP en la generación del DPI 
    public static final String GT01 = "3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C3 08 01 00 0F 90 00 9B";
    public static final String GT02 = "3B DB 96 00 80 B1 FE 45 1F 83 00 31 C0 64 C7 FC 10 00 0F 90 00 7A";

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws CardException {
        String atr = getATR();
        if (atr.equals(GT01) | atr.equals(GT02))
            System.out.println("El ATR es: "+ atr);
        else
            System.err.println("Este documento no es un DPI");
    }
    
    /**
     * Funcion que hace una conexión SmartCard utilizando la clase de conexión
     * SmartCard estandar desarrollada antriormente http://goo.gl/9MkNxt esta
     * conexión tiene conexión al dispositivo 0, utilizando el protocolo por 
     * default con el parámetro "*"
     * @return ATR de la tarjeta
     * @throws CardException 
     */
    private static String getATR() throws CardException {
        SCardConexion sc = new SCardConexion();
        String atr = byteToHex(sc.connect(0, "*").getBytes());
        sc.disconnect();
        return atr;
    }
    
    /**
     * Función que transforma una cadena de bytes a su valor Hexadecimal en un
     * tipo de dato String, con espacios entre cada byte
     * @param data Array de bytes
     * @return Valor del array de bytes en formato Hexadecimal con espacios
     * entre cada byte
     */
    private static String byteToHex(byte[] data)  {
        StringBuilder localStringBuilder = new StringBuilder();
        for (int i = 0; i < data.length; i++)  {
            String str;
            if ((str=Integer.toHexString(data[i]&0xFF).toUpperCase()).length()==1) {
                localStringBuilder.append(0);
            }
        localStringBuilder.append(str).append(" ");
        }
        return localStringBuilder.substring(0, localStringBuilder.length() - 1);
    }
    
}

Conexión Smart Card Simple con Java

Ejemplo conexión a un chip smartcard, utilizando Java

import java.util.List;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.ATR;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

/**
* Clase que maneja las conexiones PC/SC
*
* @version 2.0 Marzo de 2014
* @author Julio Chinchilla
*/
public class SCardConexion  {

    private Card card = null;
    private CardChannel channel = null;    

    /**
     * Devuelve una lista de objetos tipo CardTerminal, donde viene toda la
     * información de las terminales PC/SC conectadas
     * @return
     * @throws CardException
     */
    public synchronized List<CardTerminal> terminals () throws CardException {
        TerminalFactory factory = TerminalFactory.getDefault();
        return factory.terminals().list();
    }

    /**
    * Metodo conncet abre la conección utilizando un canal básico con el PC/SC
    * @param t número de lector PC/SC conectado al puerto USB
    * @param protocol protocolo a utilizar durante la conección utilice ("T=0", "T=1",
    * o "T=CL"), o "*" para conectar al protocolo disponible durante la conexion
    * @return Un objeto de tipo ATR que contiene el mensaje de contacto Anwer to reset
    * @see <a href="http://es.wikipedia.org/wiki/Tarjeta_inteligente">WikiES: Tarjeta inteligente</a>
    * @see <a href="http://en.wikipedia.org/wiki/Answer_to_reset">WikiEng: Answer to reset</a>
    * @throws CardException
    */
    public synchronized ATR connect (int t, String protocol) throws CardException {
        CardTerminal terminal = this.terminals().get(t);
        card = terminal.connect(protocol);
        card.beginExclusive();
        channel = card.getBasicChannel();
        return card.getATR();
    }

    /**
    * Metodo transmit, transmite un comando hacia el canal abierto de la conexión PC/SC
    * @param apdu recibe un objeto de tipo CommandAPDU
    * @return Un objeto de tipo ResponseAPDU que contiene la data de respuesta Response Data Lr y SW1-SW2
    * @see <a href="http://es.wikipedia.org/wiki/Application_Protocol_Data_Unit">WikiES: Application Protocol Data Unit</a>
    * @throws CardException
    */
    public synchronized ResponseAPDU transmit (CommandAPDU apdu) throws CardException {
        return channel.transmit(apdu);
    }

    /**
     * Metodo disconnect, desconeta la conexion PC/SC terminando el canal exclusivo
     * @throws CardException
     */
    public synchronized void disconnect () throws CardException {
        card.endExclusive();
        card.disconnect(true);
    }    

}