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.
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 EstableToda 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.
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.
jni/apdugt_jni.cExpone 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.
java/studio/bitmedia/android/APDUGT.java (wrapper JNI), USBReaderManager.java (permisos, conexión, detach y reconexión) y DPIData.java (modelo de datos).
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).
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 | Sí | No — removido en el port |
| Comandos APDU | Idénticos | |
| Parsers V1–V5 | Idénticos | |
| Criptografía | Idéntica | |
Nueve funciones exportadas desde include/apdugt.h, con extern "C" para consumo desde C++.
/* 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);
pthread_mutex, de modo que llamadas concurrentes se serializan
en lugar de corromper el estado.
La clase studio.bitmedia.APDUGT carga libapdugt.so
y expone la API nativa como métodos estáticos.
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);
}
// 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()
// 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();
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.
0x0B), en lugar de mantener una lista de VID/PIDPendingIntent, aplicando FLAG_MUTABLE en API 31+BroadcastReceiver reintenta la conexión tras 500 ms antes de reportar el lector como desconectadoRECEIVER_NOT_EXPORTED en TIRAMISU y superioresonReaderConnected, onReaderDisconnected, onPermissionDenied, onDPIRead y onError
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
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.
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… |
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 |
|---|---|---|
| 0 | APDUGT_SUCCESS | Operación exitosa |
| -1 | APDUGT_ERROR_NO_READERS | No se encontraron lectores |
| -2 | APDUGT_ERROR_NO_CARD | No hay tarjeta presente |
| -3 | APDUGT_ERROR_CONNECT | Error de conexión |
| -4 | APDUGT_ERROR_TRANSMIT | Error de transmisión |
| -5 | APDUGT_ERROR_UNSUPPORTED | Tarjeta no soportada |
| -6 | APDUGT_ERROR_CONTEXT | Error de contexto |
| -7 | APDUGT_ERROR_MEMORY | Error de memoria |
| -8 | APDUGT_ERROR_USB | Error USB |
| -9 | APDUGT_ERR_TIMEOUT | Tiempo de espera agotado |
| -10 | APDUGT_ERR_USB_DISCONNECTED | Lector desconectado |
| -11 | APDUGT_ERR_CARD_ERROR | Error de tarjeta (SW ≠ 9000) |
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.
Cualquier lector de contacto que exponga una interfaz USB de clase CCID debería funcionar. Estos son los verificados:
| Lector | VID | PID | Estado |
|---|---|---|---|
| ACS ACR38 | 072F | 9000 | Probado |
| ACS ACR39 | 072F | 9006 | Compatible |
| Gemalto PC Twin | 08E6 | 3437 | Compatible |
| HID OMNIKEY 3021 | 076B | 3021 | Compatible |
Se distribuye libapdugt.so compilada para las cuatro ABIs de Android:
El SDK se compila con el NDK dentro de tu propio proyecto, mediante externalNativeBuild.
app/src/main/cpp/APDUGT/
android {
ndkVersion "26.0.10792818"
externalNativeBuild {
cmake {
path "src/main/cpp/APDUGT/CMakeLists.txt"
}
}
}
<uses-feature android:name="android.hardware.usb.host" />
3.13+ y estándar C99-Wall -Wextra -O2liblog del NDKLa 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.
pthread_mutex en los siete puntos de entrada públicosGetSlotStatus, sin reiniciar la sesiónGET RESPONSE resuelto de forma transparente dentro del transporte-fstack-protector-strong, _FORTIFY_SOURCE=2, relro, now y noexecstackEl 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ónLa 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