Archivo de la etiqueta: SmartCard

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.

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));
    }

}

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);
    }    

}