# Algoritmo de Autenticación ADIF - Ingeniería Reversa Completa > **Status:** ✅ Algoritmo completamente descifrado > > **Pendiente:** ⏳ Extracción de claves secretas de `libapi-keys.so` ## Resumen Ejecutivo El sistema de autenticación de ADIF es similar a **AWS Signature Version 4**: - Usa **HMAC-SHA256** para firmar peticiones - Requiere dos claves secretas: `accessKey` y `secretKey` - Las claves están en la librería nativa `libapi-keys.so` (ofuscadas) - Genera headers dinámicos para cada petición --- ## Archivo Fuente del Algoritmo **Ubicación:** `com/adif/elcanomovil/serviceNetworking/interceptors/auth/ElcanoAuth.java` **Líneas clave:** - 47-53: Cálculo del header Authorization - 129-172: Preparación del Canonical Request - 174-183: Preparación del String to Sign - 78-84: Cálculo de la firma - 109-111: Generación de la clave de firma (Signature Key) --- ## Paso a Paso del Algoritmo ### 1. Parámetros de Entrada ```java // Desde ElcanoClientAuth.Builder String elcanoAccessKey; // Clave de acceso (de libapi-keys.so) String elcanoSecretKey; // Clave secreta (de libapi-keys.so) String host; // Ej: "circulacion.api.adif.es" String path; // Ej: "/portroyalmanager/secure/circulationpaths/departures/traffictype/" String params; // Query string (puede ser "") String httpMethodName; // "GET" o "POST" String payload; // Body JSON (sin espacios ni saltos de línea) String contentType; // "application/json;charset=utf-8" String xElcanoClient; // "AndroidElcanoApp" String xElcanoUserId; // UUID persistente del usuario Date requestDate; // Fecha/hora actual ``` ### 2. Formato de Fechas ```java // ElcanoAuth.java:195-199 public static String getTimeStamp(Date date) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return simpleDateFormat.format(date); } // Ejemplo: "20251204T204637Z" // ElcanoAuth.java:55-59 public static String getDate(Date date) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return simpleDateFormat.format(date); } // Ejemplo: "20251204" ``` ### 3. Preparar el Payload ```java // ElcanoAuth.java:86-91 public String formatPayload(String str) { if (str == null) { str = ""; } return str.replace("\r", "").replace("\n", "").replace(" ", ""); } ``` **Ejemplo:** ``` Input: {"page": {"pageNumber": 0}} Output: {"page":{"pageNumber":0}} ``` ### 4. Canonical Request **Archivo:** `ElcanoAuth.java:129-172` **Estructura:** ``` \n \n \n content-type:\n x-elcano-host:\n x-elcano-client:\n x-elcano-date:\n x-elcano-userid:\n content-type;x-elcano-host;x-elcano-client;x-elcano-date;x-elcano-userid\n ``` **Ejemplo real:** ``` POST /portroyalmanager/secure/circulationpaths/departures/traffictype/ content-type:application/json;charset=utf-8 x-elcano-host:circulacion.api.adif.es x-elcano-client:AndroidElcanoApp x-elcano-date:20251204T204637Z x-elcano-userid:a1b2c3d4-e5f6-7890-abcd-ef1234567890 content-type;x-elcano-host;x-elcano-client;x-elcano-date;x-elcano-userid ``` **Nota importante:** Los headers deben estar en minúsculas y en orden alfabético. ### 5. String to Sign **Archivo:** `ElcanoAuth.java:174-183` **Estructura:** ``` HMAC-SHA256\n \n ///elcano_request\n ``` **Ejemplo:** ``` HMAC-SHA256 20251204T204637Z 20251204/AndroidElcanoApp/a1b2c3d4-e5f6-7890-abcd-ef1234567890/elcano_request ``` ### 6. Signature Key (Clave de Firma) **Archivo:** `ElcanoAuth.java:109-111` ```java public byte[] getSignatureKey(String secretKey, String date, String client) { return hmacSha256( hmacSha256( hmacSha256(secretKey.getBytes(StandardCharsets.UTF_8), date), client ), "elcano_request" ); } ``` **Pseudocódigo:** ```python kDate = HMAC_SHA256(secretKey, date) # "20251204" kClient = HMAC_SHA256(kDate, client) # "AndroidElcanoApp" kSigning = HMAC_SHA256(kClient, "elcano_request") ``` ### 7. Signature (Firma Final) **Archivo:** `ElcanoAuth.java:78-84` ```java public String calculateSignature(String stringToSign) { return bytesToHex( hmacSha256( getSignatureKey(secretKey, dateSimple, client), stringToSign ) ); } ``` **Pseudocódigo:** ```python signatureKey = getSignatureKey(secretKey, "20251204", "AndroidElcanoApp") signature = HMAC_SHA256(signatureKey, stringToSign) signatureHex = signature.hex() ``` ### 8. Authorization Header **Archivo:** `ElcanoAuth.java:61-63` **Formato:** ``` HMAC-SHA256 Credential=////elcano_request,SignedHeaders=,Signature= ``` **Ejemplo:** ``` HMAC-SHA256 Credential=ACCESS_KEY_HERE/20251204/AndroidElcanoApp/a1b2c3d4-e5f6-7890-abcd-ef1234567890/elcano_request,SignedHeaders=content-type;x-elcano-host;x-elcano-client;x-elcano-date;x-elcano-userid,Signature=a1b2c3d4e5f6789... ``` ### 9. Headers Finales de la Petición **Archivo:** `ElcanoAuth.java:97-107` ```http Content-Type: application/json;charset=utf-8 X-Elcano-Host: circulacion.api.adif.es X-Elcano-Client: AndroidElcanoApp X-Elcano-Date: 20251204T204637Z X-Elcano-UserId: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Authorization: HMAC-SHA256 Credential=... ``` **Nota:** Estos reemplazan a los headers `X-CanalMovil-*` que pensábamos inicialmente. --- ## Funciones Helper ### HMAC-SHA256 **Archivo:** `ElcanoAuth.java:117-127` ```java public byte[] hmacSha256(byte[] key, String data) { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); } ``` ### SHA-256 Hash (Hex) **Archivo:** `ElcanoAuth.java:185-193` ```java public String toHex(String str) { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); return String.format("%064x", new BigInteger(1, messageDigest.digest())); } ``` ### Bytes to Hex **Archivo:** `ElcanoAuth.java:65-76` ```java public String bytesToHex(byte[] bytes) { char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for (int i = 0; i < bytes.length; i++) { int v = bytes[i] & 0xFF; hexChars[i * 2] = hexArray[v >>> 4]; hexChars[i * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars).toLowerCase(); } ``` --- ## Claves Secretas ### Ubicación **Archivo:** `com/adif/commonKeys/GetKeysHelper.java` ```java public final class GetKeysHelper { static { System.loadLibrary("api-keys"); // Carga libapi-keys.so } private final native String getAccessKeyPro(); private final native String getSecretKeyPro(); public final String a() { return getAccessKeyPro(); } public final String b() { return getSecretKeyPro(); } } ``` **Librería nativa:** - `lib/x86_64/libapi-keys.so` (446 KB) - `lib/arm64-v8a/libapi-keys.so` (503 KB) - `lib/x86/libapi-keys.so` (416 KB) - `lib/armeabi-v7a/libapi-keys.so` (366 KB) **Funciones JNI:** ```cpp Java_com_adif_commonKeys_GetKeysHelper_getAccessKeyPro Java_com_adif_commonKeys_GetKeysHelper_getSecretKeyPro ``` ### Extracción de Claves **Opción 1: Ghidra / IDA Pro** ```bash # Abrir libapi-keys.so en Ghidra # Buscar las funciones JNI # Analizar el código assembly para encontrar los strings ``` **Opción 2: Frida (runtime)** ```javascript Java.perform(function() { var GetKeysHelper = Java.use('com.adif.commonKeys.GetKeysHelper'); console.log('[+] Access Key: ' + GetKeysHelper.f4297a.a()); console.log('[+] Secret Key: ' + GetKeysHelper.f4297a.b()); }); ``` **Opción 3: Strings + Análisis manual** ```bash strings libapi-keys.so | grep -E "^[A-Za-z0-9+/=]{32,}$" ``` --- ## Implementación en Python ```python import hashlib import hmac from datetime import datetime import json class AdifAuthenticator: def __init__(self, access_key, secret_key): self.access_key = access_key self.secret_key = secret_key def get_timestamp(self, date=None): if date is None: date = datetime.utcnow() return date.strftime('%Y%m%dT%H%M%SZ') def get_date(self, date=None): if date is None: date = datetime.utcnow() return date.strftime('%Y%m%d') def format_payload(self, payload): if payload is None: return "" if isinstance(payload, dict): payload = json.dumps(payload, separators=(',', ':')) return payload.replace('\r', '').replace('\n', '').replace(' ', '') def sha256_hash(self, text): return hashlib.sha256(text.encode('utf-8')).hexdigest() def hmac_sha256(self, key, data): if isinstance(key, str): key = key.encode('utf-8') return hmac.new(key, data.encode('utf-8'), hashlib.sha256).digest() def get_signature_key(self, date_simple, client): k_date = self.hmac_sha256(self.secret_key, date_simple) k_client = self.hmac_sha256(k_date, client) k_signing = self.hmac_sha256(k_client, "elcano_request") return k_signing def prepare_canonical_request(self, method, path, params, payload, content_type, host, client, timestamp, user_id): # Formatear payload formatted_payload = self.format_payload(payload) payload_hash = self.sha256_hash(formatted_payload) # Headers canónicos (en orden alfabético, minúsculas) canonical_headers = ( f"content-type:{content_type}\n" f"x-elcano-client:{client}\n" f"x-elcano-date:{timestamp}\n" f"x-elcano-host:{host}\n" f"x-elcano-userid:{user_id}\n" ) signed_headers = "content-type;x-elcano-client;x-elcano-date;x-elcano-host;x-elcano-userid" canonical_request = ( f"{method}\n" f"{path}\n" f"{params}\n" f"{canonical_headers}" f"{signed_headers}\n" f"{payload_hash}" ) return canonical_request, signed_headers def prepare_string_to_sign(self, timestamp, date_simple, client, user_id, canonical_request): canonical_hash = self.sha256_hash(canonical_request) string_to_sign = ( f"HMAC-SHA256\n" f"{timestamp}\n" f"{date_simple}/{client}/{user_id}/elcano_request\n" f"{canonical_hash}" ) return string_to_sign def calculate_signature(self, string_to_sign, date_simple, client): signing_key = self.get_signature_key(date_simple, client) signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest() return signature def build_authorization_header(self, signature, date_simple, client, user_id, signed_headers): return ( f"HMAC-SHA256 " f"Credential={self.access_key}/{date_simple}/{client}/{user_id}/elcano_request," f"SignedHeaders={signed_headers}," f"Signature={signature}" ) def get_auth_headers(self, method, url, payload=None, user_id=None): # Parse URL from urllib.parse import urlparse parsed = urlparse(url) host = parsed.netloc path = parsed.path params = parsed.query or "" # Defaults if user_id is None: import uuid user_id = str(uuid.uuid4()) client = "AndroidElcanoApp" content_type = "application/json;charset=utf-8" # Timestamps now = datetime.utcnow() timestamp = self.get_timestamp(now) date_simple = self.get_date(now) # 1. Canonical Request canonical_request, signed_headers = self.prepare_canonical_request( method, path, params, payload, content_type, host, client, timestamp, user_id ) # 2. String to Sign string_to_sign = self.prepare_string_to_sign( timestamp, date_simple, client, user_id, canonical_request ) # 3. Signature signature = self.calculate_signature(string_to_sign, date_simple, client) # 4. Authorization Header authorization = self.build_authorization_header( signature, date_simple, client, user_id, signed_headers ) # Return all headers return { "Content-Type": content_type, "X-Elcano-Host": host, "X-Elcano-Client": client, "X-Elcano-Date": timestamp, "X-Elcano-UserId": user_id, "Authorization": authorization } # USO: # auth = AdifAuthenticator(access_key="ACCESS_KEY_AQUI", secret_key="SECRET_KEY_AQUI") # headers = auth.get_auth_headers("POST", "https://circulacion.api.adif.es/path", payload={...}) ``` --- ## Próximos Pasos ### 1. Extraer las Claves ⏳ **Método recomendado: Ghidra** ```bash # 1. Instalar Ghidra wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.0_build/ghidra_11.0_PUBLIC_20231222.zip # 2. Abrir libapi-keys.so ./ghidra # 3. Buscar funciones: # - getAccessKeyPro # - getSecretKeyPro # 4. Analizar el código assembly # 5. Encontrar los strings hardcodeados ``` ### 2. Probar el Algoritmo ✅ Una vez tengamos las claves, podemos probar con el script Python. ### 3. Validar contra API Real ⏳ Hacer peticiones y confirmar que funcionan. --- ## Referencias - **ElcanoAuth.java:** `serviceNetworking/interceptors/auth/ElcanoAuth.java` - **ElcanoClientAuth.java:** `serviceNetworking/interceptors/auth/ElcanoClientAuth.java` - **GetKeysHelper.java:** `commonKeys/GetKeysHelper.java` - **libapi-keys.so:** `lib/*/libapi-keys.so` --- **Última actualización:** 2025-12-04 **Status:** Algoritmo completo ✅ | Claves pendientes ⏳