img

APDUGT para Android

APDUGT para Android es un driver nativo en C que lee el chip de identificación de Guatemala (DPI) desde un teléfono o tablet, conectando el lector por USB. En Android no existe PC/SC, así que el driver implementa el protocolo CCID directamente sobre transferencias USB bulk, usando el file descriptor que el sistema entrega a la aplicación.

Es el mismo motor de la versión de Windows: los comandos APDU, los parsers de las cinco generaciones de chip y la criptografía BAC son idénticos. Lo único que cambió fue la capa de transporte. Sobre ese núcleo se añadió una interfaz JNI y una capa Java que resuelve el ciclo de vida USB de Android —permisos, conexión y reconexión— para que la aplicación solo tenga que pedir los datos.

Driver Nativo USB CCID para Android

Sin PC/SC, sin libusb, sin OpenSSL y sin servicios en la nube. El driver habla CCID directo contra el lector y resuelve la criptografía del chip dentro de la propia librería.

libapdugt.so · C99 + JNI v1.0 Estable
Versión
1.0.0-ANDROID
Transporte
USB CCID
ABIs
4
Dependencias externas
0
🏗️

Arquitectura

Toda la lógica vive en C. Java solo administra el ciclo de vida del USB en Android; no hay reglas de negocio en la capa Java.

1

Núcleo en C — src/ · include/

Transporte CCID sobre USB, comandos APDU, detección de versión por ATR, parsers V1–V5, criptografía BAC y extracción de la fotografía. Seis módulos, C99 puro, portable desde la versión Windows.

2

Puente JNI — jni/apdugt_jni.c

Expone la API de C a Java/Kotlin y traduce la estructura nativa a un objeto Java campo por campo, incluyendo el arreglo de bytes de la fotografía.

3

Capa Android — java/studio/bitmedia/android/

APDUGT.java (wrapper JNI), USBReaderManager.java (permisos, conexión, detach y reconexión) y DPIData.java (modelo de datos).

Módulos del núcleo: ccid_usb.c (transporte), dpi_apdu.c (comandos y ATR), dpi_crypto.c (SHA-1, 3DES, MAC), dpi_parser.c (parsers V1–V5), dpi_photo.c (BAC y foto) y apdugt.c (fachada pública).
🔀

Diferencias vs. la versión Windows

Este driver es un port de APDUGT.dll. Solo cambió la capa de transporte: todo lo que interpreta la tarjeta se mantiene byte por byte.

Aspecto Windows Android
API de smartcard winscard.dll (PC/SC) USB bulk transfer (CCID)
Conectar SCardConnect() CCID_PowerOn() vía USB
Transmitir SCardTransmit() CCID_Transmit() vía USB
Sistema de licencias No — removido en el port
Comandos APDU Idénticos
Parsers V1–V5 Idénticos
Criptografía Idéntica
⚙️

API Nativa (C)

Nueve funciones exportadas desde include/apdugt.h, con extern "C" para consumo desde C++.

APDUGT_GetVersion
APDUGT_GetErrorMessage
APDUGT_Init
APDUGT_Release
APDUGT_IsCardPresent
APDUGT_GetATR
APDUGT_GetChipVersion
APDUGT_ReadDPI
APDUGT_FreeDPIData

Firma principal:

/* Inicializa la conexión con el lector USB.
   fd, ep_out y ep_in provienen de la capa Java. */
int  APDUGT_Init(int fd, int ep_out, int ep_in);
void APDUGT_Release(void);

int  APDUGT_IsCardPresent(void);
int  APDUGT_GetATR(char* atr);
int  APDUGT_GetChipVersion(char* version);

int  APDUGT_ReadDPI(APDUGT_DPIData* data);
void APDUGT_FreeDPIData(APDUGT_DPIData* data);
Estado global: el driver mantiene una sola conexión activa por proceso. Los siete puntos de entrada públicos están protegidos con pthread_mutex, de modo que llamadas concurrentes se serializan en lugar de corromper el estado.
🤖

API Java / Kotlin

La clase studio.bitmedia.APDUGT carga libapdugt.so y expone la API nativa como métodos estáticos.

Wrapper JNI:

package studio.bitmedia;

import studio.bitmedia.android.model.DPIData;

public class APDUGT {
    static { System.loadLibrary("apdugt"); }

    /* Inicializa con el file descriptor USB y sus endpoints */
    public static native int     init(int fd, int epOut, int epIn);
    public static native void    close();
    public static native boolean isCardPresent();
    public static native String  getATR();
    public static native String  getChipVersion();
    public static native int     readDPI(DPIData dpiData);
}

Lectura desde Kotlin:

// 1. Localizar el lector CCID y pedir permiso USB
val reader = USBReaderManager(context)
reader.setCallback(object : USBReaderManager.ReaderCallback {
    override fun onReaderConnected(device: UsbDevice) { /* listo para leer */ }
    override fun onReaderDisconnected() { }
    override fun onPermissionDenied() { }
    override fun onDPIRead(data: DPIData) { }
    override fun onError(message: String) { }
})
reader.registerReceiver()
reader.findReader()

// 2. Leer la tarjeta
val data = reader.readDPI()
if (data != null && data.isValid) {
    Log.i(TAG, "Chip:   ${data.chipVersion}")
    Log.i(TAG, "CUI:    ${data.cui}")
    Log.i(TAG, "Nombre: ${data.nombreCompleto}")

    // La foto llega como JPEG listo para decodificar
    val foto = BitmapFactory.decodeByteArray(data.photoData, 0, data.photoSize)
}

// 3. Liberar
reader.disconnect()

Uso directo del driver (sin USBReaderManager):

// fd viene de UsbDeviceConnection.getFileDescriptor()
int rc = APDUGT.init(fd, epOut, epIn);

if (rc == 0 && APDUGT.isCardPresent()) {
    String version = APDUGT.getChipVersion();   // "2009" … "2024"
    DPIData data = new DPIData();

    if (APDUGT.readDPI(data) == 0) {
        Log.i(TAG, data.cui + " — " + data.nombreCompleto);
    } else {
        Log.e(TAG, "SW=" + data.lastSW1 + "/" + data.lastSW2);
    }
}

APDUGT.close();
🔌

Gestión USB en Android

USBReaderManager resuelve todo lo que Android exige alrededor del USB Host, para que la aplicación no tenga que tocar la API de bajo nivel.

  • Detección por clase, no por modelo — recorre los dispositivos conectados y toma el primero cuya interfaz sea de clase CCID (0x0B), en lugar de mantener una lista de VID/PID
  • Permiso USB en tiempo de ejecución — solicita el permiso con PendingIntent, aplicando FLAG_MUTABLE en API 31+
  • Endpoints automáticos — localiza los endpoints bulk IN y OUT recorriendo la interfaz, sin valores fijos en el código
  • Reconexión ante detach espurio — el BroadcastReceiver reintenta la conexión tras 500 ms antes de reportar el lector como desconectado
  • Compatible con Android 13+ — registra el receiver con RECEIVER_NOT_EXPORTED en TIRAMISU y superiores
  • Callbacks explícitosonReaderConnected, onReaderDisconnected, onPermissionDenied, onDPIRead y onError
Sin root: la aplicación no necesita privilegios especiales. Basta con que el usuario conceda el permiso USB cuando Android lo solicite.
🗂️

Modelo de Datos

Una sola lectura llena un objeto DPIData con todos los campos del documento. El JNI escribe cada campo por reflexión, así que no hay parsing en Java.

Tarjeta: atr, chipVersion

Datos personales: cui, gentilicio, primerNombre, segundoNombre, otrosNombres, primerApellido, segundoApellido, apellidoCasada, nombreCompleto, nombreUsual, sexo

Nacimiento: fechaNacimiento, municipioNacimiento, departamentoNacimiento, paisNacimiento

Documento: fechaEmision, fechaVencimiento, numeroRenovacion, serie

Vecindad: municipioVecindad, departamentoVecindad, nacionalidad, estadoCivil

Residencia: direccionResidencia1, direccionResidencia2, municipioResidencia, departamentoResidencia, codigoPostal, paisResidencia

Contacto: telefono1, telefono2, correoElectronico, nombreCompletoContacto, parentescoContacto

Características físicas: colorTez, colorOjos, colorCabello, especificacionesCabello, limitacionesFisicas, lunares, cicatrices

Educación y ocupación: estudia, escolaridad, idioma, etniaComunidadLinguistica, oficio, ocupacion

Registro civil: libro, folio, partida, numeroInscripcion, numeroCedula, municipioCedula, departamentoCedula

Alfabetización: sabeLeer, sabeEscribir, puedeFirmar

Otros: nit, identificacionPersona, tipoSolicitud, oficialActivo, mrz

Biométricos: photoData (JPEG), photoSize, numeroHuellasAlmacenadas, huellasSTR

Diagnóstico: errorCode, errorMessage, lastSW1, lastSW2

Utilidades: isValid() indica si la lectura fue correcta y toMap() devuelve todos los campos como Map<String, Object>, útil para serializar a JSON o enviar a una API.
💳

Versiones de Chip Soportadas

El driver identifica la generación del chip comparando el ATR y despacha al parser correspondiente. La aplicación no necesita saber qué tarjeta tiene enfrente.

Versión Año ATR característico
V1 2009 3B DB 96 00 80 B1…
V2 2014 3B FD 96 00 00 81…
V3 2017 3B 9D 13 81 31 60…
V4 2018 3B FF 94 00 00 81…
V5 2024 3B D5 18 FF 81 B1…
Mismos campos que en Windows: los parsers son idénticos, así que cada versión devuelve exactamente el mismo conjunto de datos. Consulta el desglose campo por campo en la ficha de la versión Windows.
Fotografía con BAC: en V1 la imagen se lee sin cifrar; de V2 en adelante el driver ejecuta Basic Access Control según ICAO 9303 y abre un canal de Secure Messaging para extraer el JPEG.
⚠️

Códigos de Error

Toda la API devuelve 0 en éxito o un código negativo. APDUGT_GetErrorMessage() traduce cada código a un mensaje legible en español.

Código Constante Significado
0APDUGT_SUCCESSOperación exitosa
-1APDUGT_ERROR_NO_READERSNo se encontraron lectores
-2APDUGT_ERROR_NO_CARDNo hay tarjeta presente
-3APDUGT_ERROR_CONNECTError de conexión
-4APDUGT_ERROR_TRANSMITError de transmisión
-5APDUGT_ERROR_UNSUPPORTEDTarjeta no soportada
-6APDUGT_ERROR_CONTEXTError de contexto
-7APDUGT_ERROR_MEMORYError de memoria
-8APDUGT_ERROR_USBError USB
-9APDUGT_ERR_TIMEOUTTiempo de espera agotado
-10APDUGT_ERR_USB_DISCONNECTEDLector desconectado
-11APDUGT_ERR_CARD_ERRORError de tarjeta (SW ≠ 9000)
Diagnóstico fino: cuando la tarjeta responde con un status word distinto de 9000, el par lastSW1 / lastSW2 llega hasta el objeto DPIData junto con el nombre del APDU que falló y la versión de chip detectada.
📇

Lectores USB Compatibles

Cualquier lector de contacto que exponga una interfaz USB de clase CCID debería funcionar. Estos son los verificados:

Lector VID PID Estado
ACS ACR38072F9000Probado
ACS ACR39072F9006Compatible
Gemalto PC Twin08E63437Compatible
HID OMNIKEY 3021076B3021Compatible
Solo USB: el driver trabaja con lectores de contacto conectados por USB. La lectura NFC del chip está fuera de alcance en esta versión.
📦

ABIs y Binarios

Se distribuye libapdugt.so compilada para las cuatro ABIs de Android:

arm64-v8a
libapdugt.so · 351 KB
armeabi-v7a
libapdugt.so · 252 KB
x86_64
libapdugt.so · 301 KB
x86
libapdugt.so · 242 KB
Las ABIs x86 y x86_64 permiten depurar en emulador sin necesidad de un dispositivo físico, aunque la lectura real requiere hardware con USB Host.
🛠️

Compilación e Integración

El SDK se compila con el NDK dentro de tu propio proyecto, mediante externalNativeBuild.

1 · Copiar el SDK al proyecto

app/src/main/cpp/APDUGT/

2 · Declararlo en build.gradle

android {
    ndkVersion "26.0.10792818"

    externalNativeBuild {
        cmake {
            path "src/main/cpp/APDUGT/CMakeLists.txt"
        }
    }
}

3 · Declarar el USB Host en el manifiesto

<uses-feature android:name="android.hardware.usb.host" />

Configuración de compilación

  • CMake 3.13+ y estándar C99
  • Flags: -Wall -Wextra -O2
  • Única librería enlazada: liblog del NDK
  • El JNI se incluye automáticamente cuando el target es Android
Cero dependencias de terceros: no requiere libusb, OpenSSL ni Bouncy Castle. Todo el criptográfico —SHA-1 (RFC 3174), 3DES CBC (FIPS 46-3) y MAC ISO/IEC 9797-1 Algoritmo 3— está implementado dentro del propio driver.
🛡️

Estabilidad y Seguridad

La versión 1.0 cerró un ciclo completo de endurecimiento con un objetivo concreto: lecturas consecutivas sin caídas, sin fugas de memoria y sin bloqueos.

  • Memoria acotada — las reservas en pila bajaron de 65 KB a menos de 2 KB, con validación de límites en cada copia de datos provenientes del hardware
  • JNI en heap — la estructura de datos se asigna en heap dentro del JNI, eliminando un marco de pila de más de 15 KB
  • Acceso serializadopthread_mutex en los siete puntos de entrada públicos
  • Detección no destructiva — la presencia de tarjeta se consulta con GetSlotStatus, sin reiniciar la sesión
  • Recuperación ante fallos USB — reintento con backoff de 50 ms y 100 ms, y recuperación de sesión ante errores transitorios
  • Respuestas fragmentadas — encadenamiento GET RESPONSE resuelto de forma transparente dentro del transporte
  • Status word verificado — SW1/SW2 comprobados en los 34 puntos donde se transmite un APDU, y propagados hasta Java
  • Criptografía validada — SHA-1, 3DES y MAC contrastados contra vectores de prueba conocidos (KAT) del NIST
  • Binario endurecido-fstack-protector-strong, _FORTIFY_SOURCE=2, relro, now y noexecstack

Requisitos

  • Dispositivo Android con soporte USB Host (OTG)
  • Lector de smartcard de contacto con interfaz USB CCID
  • Android Studio con NDK y CMake instalados desde el SDK Manager
  • Permiso USB concedido por el usuario en tiempo de ejecución
  • No requiere root ni permisos de sistema
📱

Solicitar el SDK Android

El SDK se entrega con el código fuente en C, la interfaz JNI, las clases Java del driver, las cuatro ABIs precompiladas y la configuración de CMake lista para integrar.

Hablemos de tu integración

¿Lo necesitas también en Windows?

La versión de escritorio expone la misma información del chip a través de una DLL nativa consumible desde .NET, Java, Python, Delphi, Rust, Go o Node.js.

VER APDUGT.DLL VER APDUGT.DLL