Esta actualización reorganiza el proyecto de reverse engineering de la API de ADIF con los siguientes cambios: Estructura del proyecto: - Movida documentación principal a carpeta docs/ - Consolidados archivos markdown redundantes en CLAUDE.md (contexto completo del proyecto) - Organización de tests en carpeta tests/ con README explicativo - APK renombrado de base.apk a adif.apk para mayor claridad Archivos de código: - Movidos adif_auth.py y adif_client.py a la raíz (antes en api_testing_scripts/) - Eliminados scripts de testing obsoletos y scripts de Frida no utilizados - Nuevos tests detallados: test_endpoints_detailed.py y test_onepaths_with_real_trains.py Descubrimientos: - Documentados nuevos hallazgos en docs/NEW_DISCOVERIES.md - Actualización de onePaths funcionando con commercialNumber real (devuelve 200) - Extraídos 1587 códigos de estación en station_codes.txt Configuración: - Actualizado .gitignore con mejores patrones para Python e IDEs - Eliminados archivos temporales de depuración y logs
13 KiB
Adif Elcano API - Ingeniería Reversa
Documentación completa de la API de ADIF (El Cano Móvil) obtenida mediante ingeniería reversa de la aplicación móvil.
Estado: ✅ Proyecto completado con éxito Última actualización: 2025-12-05
🎯 Resumen de Funcionalidad
| Característica | Estado | Notas |
|---|---|---|
| Autenticación HMAC-SHA256 | ✅ Implementada | Algoritmo completo y validado |
| Endpoints funcionales | ✅ 4/8 (50%) | departures, arrivals, onepaths, stationsobservations |
| Endpoints bloqueados | ⚠️ 4/8 | 401 Unauthorized por permisos limitados |
| Códigos de estación | ✅ 1587 | Extraídos de assets/stations_all.json |
| Claves extraídas | ✅ Completas | ACCESS_KEY y SECRET_KEY de libapi-keys.so |
URLs Base
BASE_URL_STATIONS = https://estaciones.api.adif.es
BASE_URL_CIRCULATION = https://circulacion.api.adif.es
BASE_URL_ELCANOWEB = https://elcanoweb.adif.es/api/
BASE_URL_AVISA = https://avisa.adif.es/avisa-ws/api/
Headers de Autenticación
Para API de Circulaciones y Composiciones
Content-Type: application/json;charset=utf-8
User-key: f4ce9fbfa9d721e39b8984805901b5df
Para API de Estaciones
Content-Type: application/json;charset=utf-8
User-key: 0d021447a2fd2ac64553674d5a0c1a6f
Para Avisa Login
Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
Para Suscripciones
Authorization: Basic ZGVpbW9zOmRlaW1vc3R0
X-CanalMovil-Authentication: <token>
X-CanalMovil-deviceID: <device_id>
X-CanalMovil-pushID: <push_id>
Tokens
REGISTRATION_TOKEN = Bearer b9034774-c6e4-4663-a1a8-74bf7102651b
Endpoints
Estaciones
Obtener todas las estaciones
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones
Obtener detalles de una estación
POST /portroyalmanager/secure/stations/onestation/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones
Body:
{
"stationCode": "string"
}
Observaciones de estación
POST /portroyalmanager/secure/stationsobservations/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones
Body:
{
"stationCodes": ["string"] // Array de códigos de estación (requerido)
}
Ejemplo:
{
"stationCodes": ["60000", "71801"]
}
Circulaciones (Trenes)
Salidas (Departures)
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body:
{
"commercialService": "YES|NOT|BOTH", // Estado del servicio comercial
"commercialStopType": "YES|NOT|BOTH", // Tipo de parada comercial
"destinationStationCode": "string|null", // Código estación destino (opcional)
"originStationCode": "string|null", // Código estación origen (opcional)
"page": {
"pageNumber": number // Número de página
},
"stationCode": "string|null", // Código estación (opcional)
"trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL" // Tipo de tráfico
}
Ejemplo:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": null,
"originStationCode": null,
"page": {
"pageNumber": 0
},
"stationCode": "60000",
"trafficType": "ALL"
}
Llegadas (Arrivals)
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Mismo formato que departures (TrafficCirculationPathRequest)
Entre estaciones
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Mismo formato que departures (TrafficCirculationPathRequest)
Una ruta específica
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body:
{
"allControlPoints": boolean|null, // Todos los puntos de control (opcional)
"commercialNumber": "string|null", // Número comercial del tren (opcional)
"destinationStationCode": "string|null", // Código estación destino (opcional)
"launchingDate": number|null, // Fecha de lanzamiento en timestamp (Long) (opcional)
"originStationCode": "string|null" // Código estación origen (opcional)
}
Ejemplo:
{
"allControlPoints": true,
"commercialNumber": "04138",
"destinationStationCode": "60000",
"launchingDate": 1733356800000,
"originStationCode": "71801"
}
Varias rutas
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Mismo formato que onepaths (OneOrSeveralPathsRequest)
Composiciones
Composición de tren
POST /portroyalmanager/secure/circulationpaths/compositions/path/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Same as onepaths request
Avisa (Sistema de incidencias)
Login
POST /avisa-ws/api/token
Base: https://avisa.adif.es
Headers: Basic auth token para Avisa
Query params:
- grant_type: "password"
- username: <username>
- password: <password>
Registrar cliente
POST /avisa-ws/api/v1/client
Base: https://avisa.adif.es
Estaciones Avisa
GET /avisa-ws/api/v1/station
Base: https://avisa.adif.es
Categorías de estación
GET /avisa-ws/api/v1/category
Base: https://avisa.adif.es
Incidencias
GET /avisa-ws/api/v1/incidence
POST /avisa-ws/api/v1/incidence
GET /avisa-ws/api/v1/incidence/{incidenceId}
Base: https://avisa.adif.es
Suscripciones
Listar suscripciones
GET /api/subscriptions?platform=300
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers
Crear suscripción
POST /api/subscriptions
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers
Silenciar suscripción
PUT /api/subscriptions/{id}/mute
Base: https://elcanoweb.adif.es
Headers: Basic auth + X-CanalMovil headers
📊 Estructura de Respuestas
Respuesta de Departures/Arrivals
{
"commercialPaths": [
{
"commercialPathInfo": {
"timestamp": 1764927847100,
"commercialPathKey": {
"commercialCirculationKey": {
"commercialNumber": "90399",
"launchingDate": 1764889200000
},
"originStationCode": "10620",
"destinationStationCode": "60004"
},
"commercialOriginStationCode": "10620",
"commercialDestinationStationCode": "60004",
"line": null,
"core": null,
"observation": null,
"trafficType": "CERCANIAS",
"opeProComPro": {
"operator": "RF",
"product": "C",
"commercialProduct": " "
},
"compositionData": {
"compositionLenghtType": null,
"compositionFloorType": null,
"accesible": false
},
"announceableStations": ["60004"]
},
"passthroughStep": {
"stopType": "NO_STOP",
"announceable": false,
"stationCode": "10200",
"arrivalPassthroughStepSides": null,
"departurePassthroughStepSides": {
"plannedTime": 1764927902000,
"forecastedOrAuditedDelay": 2175,
"timeType": "FORECASTED",
"plannedPlatform": "2",
"sitraPlatform": null,
"ctcPlatform": null,
"operatorPlatform": null,
"resultantPlatform": "PLANNED",
"preassignedPlatform": null,
"observation": null,
"circulationState": "RUNNING",
"announceState": "NORMAL",
"technicalCirculationKey": {
"technicalNumber": "90399",
"technicalLaunchingDate": 1764889200000
},
"visualEffects": {
"inmediateDeparture": false,
"countDown": false,
"showDelay": true
}
}
}
}
]
}
Campos importantes:
commercialNumber: Número comercial del trenlaunchingDate: Fecha de salida en milisegundos (timestamp)plannedTime: Hora planificada en milisegundosforecastedOrAuditedDelay: Retraso en segundoscirculationState: Estado del tren (RUNNING, PENDING_TO_CIRCULATE, etc.)plannedPlatform: Andén planificado
Respuesta de OnePaths (Ruta Completa)
{
"commercialPaths": [
{
"commercialPathInfo": { /* Igual que en departures */ },
"passthroughSteps": [ // ← Array con TODAS las paradas
{
"stopType": "COMMERCIAL",
"announceable": false,
"stationCode": "10620",
"arrivalPassthroughStepSides": null,
"departurePassthroughStepSides": {
"plannedTime": 1764918000000,
"forecastedOrAuditedDelay": 430,
"timeType": "AUDITED",
"plannedPlatform": "1",
"circulationState": "RUNNING",
"showDelay": false
}
},
{
"stopType": "NO_STOP",
"stationCode": "C1062",
"arrivalPassthroughStepSides": { /* ... */ },
"departurePassthroughStepSides": { /* ... */ }
}
// ... más paradas
]
}
]
}
Diferencia clave:
departures/arrivals→passthroughStep(singular, solo la estación consultada)onepaths→passthroughSteps(plural, array con todas las paradas del recorrido)
Respuesta de Station Observations
{
"stationObservations": [
{
"stationCode": "10200",
"observation": "Texto de la observación"
}
]
}
Status Codes
| Código | Significado | Causa |
|---|---|---|
| 200 | ✅ Success | Petición exitosa con datos |
| 204 | ⚠️ No Content | Autenticación correcta pero sin datos disponibles |
| 400 | ❌ Bad Request | Payload incorrecto, campo requerido faltante o formato inválido |
| 401 | ❌ Unauthorized | Sin permisos (claves con perfil limitado) |
Nota importante sobre 204: Un status 204 NO es un error. Significa que la autenticación y el payload son correctos, pero no hay datos disponibles para esa consulta específica.
Tipos de Datos
TrafficType (Tipos de tráfico)
CERCANIAS- Trenes de cercaníasAVLDMD- Alta Velocidad, Larga y Media DistanciaOTHERS- Otros tipos de tráficoTRAVELERS- ViajerosGOODS- MercancíasALL- Todos los tipos
State (Estados para comercialService y comercialStopType)
YES- SíNOT- NoBOTH- Ambos
PageInfoDTO
{
"pageNumber": 0
}
Notas de Seguridad
- La app usa certificate pinning con claves públicas específicas
- Los tokens están hardcodeados en la aplicación
- Las User-keys son diferentes para cada servicio (estaciones vs circulaciones)
- El token de registro
b9034774-c6e4-4663-a1a8-74bf7102651bestá en el código
🗺️ Códigos de Estación
Total: 1587 estaciones disponibles
Archivo: station_codes.txt (raíz del proyecto)
Fuente: apk_extracted/assets/stations_all.json
Formato
código nombre tipos_tráfico
Top Estaciones
10200 Madrid Puerta de Atocha AVLDMD
10302 Madrid Chamartín-Clara Campoamor AVLDMD
71801 Barcelona Sants AVLDMD,CERCANIAS
60000 València Nord AVLDMD
11401 Sevilla Santa Justa AVLDMD
50003 Alacant Terminal AVLDMD,CERCANIAS
54007 Córdoba Central AVLDMD
79600 Zaragoza Portillo AVLDMD,CERCANIAS
03216 València J.Sorolla AVLDMD
04040 Zaragoza Delicias AVLDMD,CERCANIAS
Notas de Implementación
Esta documentación se ha obtenido mediante ingeniería reversa del código decompilado de la aplicación Android de ADIF Elcano.
Herramientas utilizadas:
- Ghidra: Extracción de claves de
libapi-keys.so - JADX: Decompilación del APK
- Python 3: Implementación del cliente
- Frida: Análisis dinámico (opcional)
Clases principales analizadas:
com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficCirculationPathRequestcom.adif.elcanomovil.serviceNetworking.circulations.model.request.OneOrSeveralPathsRequestcom.adif.elcanomovil.serviceNetworking.stationObservations.model.StationObservationsRequestcom.adif.elcanomovil.serviceNetworking.circulations.model.request.CirculationPathRequest(interface)com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficType(enum)com.adif.elcanomovil.serviceNetworking.interceptors.auth.ElcanoAuth(algoritmo HMAC)
Archivos clave:
apk_extracted/lib/x86_64/libapi-keys.so- Claves de autenticaciónapk_extracted/assets/stations_all.json- Base de datos de estacionesapk_decompiled/sources/com/adif/elcanomovil/- Código fuente decompilado
Última actualización: 2025-12-05 Estado: ✅ Proyecto completado con éxito