60388529c120a2e5f0e41676d4a9f2ffe0f64687
ADIF API - Reverse Engineering
Complete Python client to access the ADIF API (El Cano Móvil) through reverse engineering.
All 9/9 endpoints functional · HMAC-SHA256 authentication implemented · 1587 station codes extracted
Quick Start
# Install dependencies
pip install requests
# Run demo
python3 adif_client.py
Basic Usage
from adif_client import AdifClient
# Initialize client
client = AdifClient(
access_key="and20210615",
secret_key="Jthjtr946RTt"
)
# Get departures from Madrid Atocha
trains = client.get_departures("10200", "AVLDMD")
for train in trains:
info = train['commercialPathInfo']
print(f"Train {info['commercialPathKey']['commercialCirculationKey']['commercialNumber']}")
# Get complete train route
route = client.get_train_route(
commercial_number="03194",
launching_date=1764889200000,
origin_station_code="10200",
destination_station_code="71801"
)
Available Endpoints (9/9 working)
| Endpoint | Status | Description |
|---|---|---|
/departures/traffictype/ |
200 | Departures from a station |
/arrivals/traffictype/ |
200 | Arrivals to a station |
/stationsobservations/ |
200 | Station observations |
/onepaths/ |
200 | Complete route of a train |
/betweenstations/traffictype/ |
200 | Trains between two stations |
/onestation/ |
200 | Station details |
/allstations/reducedinfo/{token}/ |
200 | Complete station list (GET) |
/severalpaths/ |
204 | Details of multiple circulations (returns 204 when no data) |
/compositions/path/ |
204* | Train compositions |
*compositions works without allControlPoints; including it requires elevated permissions (401).
Important notes
- Omit optional fields instead of sending
null(the server rejects explicit nulls). betweenstations: UseoriginStationCodeanddestinationStationCodeonly.onestation: Requirestoken: "0"and adetailedInfoobject.allstations: GET with the token in the path (initially"0").
Project Structure
adif-api-reverse-engineering/
├── README.md # This file
├── LICENSE
│
├── Core scripts
│ ├── adif_auth.py # HMAC-SHA256 implementation
│ ├── adif_client.py # Complete API client
│ └── query_api.py # Interactive CLI
│
├── Data
│ ├── station_codes.txt # 1587 station codes
│ └── extracted_keys.txt # Extracted keys
│
├── Tests (/tests)
│ ├── test_endpoints.py # Smoke test of main endpoints
│ ├── test_endpoints_detailed.py # Exhaustive test with debug
│ ├── test_onepaths_with_real_trains.py # End-to-end with live trains
│ └── test_api_authenticated.py # Minimal signed calls
│
├── Documentation (/docs)
│ ├── API_DOCUMENTATION.md # API documentation
│ ├── AUTHENTICATION_ALGORITHM.md # HMAC algorithm
│ └── API_REQUEST_BODIES.md # Request body references
│
└── APK Analysis
├── apk_decompiled/ # Decompiled code (JADX)
└── apk_extracted/ # Extracted APK
├── assets/stations_all.json # Station source
└── lib/x86_64/libapi-keys.so # Library with keys
Authentication
Extracted Keys
ACCESS_KEY = "and20210615"
SECRET_KEY = "Jthjtr946RTt"
USER_KEY_CIRCULATION = "f4ce9fbfa9d721e39b8984805901b5df"
USER_KEY_STATIONS = "0d021447a2fd2ac64553674d5a0c1a6f"
Source: apk_extracted/lib/x86_64/libapi-keys.so (extracted with Ghidra)
HMAC-SHA256 Algorithm
Implementation based on AWS Signature v4.
CRITICAL: Header order is not alphabetical:
canonical_headers = (
f"content-type:application/json;charset=utf-8\n"
f"x-elcano-host:{host}\n" # host before client
f"x-elcano-client:AndroidElcanoApp\n"
f"x-elcano-date:{timestamp}\n"
f"x-elcano-userid:{user_id}\n"
)
See adif_auth.py for complete implementation.
Station Codes
Total: 1587 stations
File: station_codes.txt
Format: code TAB name TAB traffic_types
Top 10 Stations
10200 Madrid Puerta de Atocha AVLDMD
10302 Madrid Chamartin-Clara Campoamor AVLDMD
71801 Barcelona Sants AVLDMD,CERCANIAS
60000 Valencia Nord AVLDMD
11401 Sevilla Santa Justa AVLDMD
50003 Alicante Terminal AVLDMD,CERCANIAS
54007 Cordoba Central AVLDMD
79600 Zaragoza Portillo AVLDMD,CERCANIAS
03216 Valencia J.Sorolla AVLDMD
04040 Zaragoza Delicias AVLDMD,CERCANIAS
Use Cases
1. Delay Monitor
import time
from adif_client import AdifClient
client = AdifClient(ACCESS_KEY, SECRET_KEY)
while True:
trains = client.get_departures("10200", "ALL")
for train in trains:
passthrough = train.get('passthroughStep', {})
dep_sides = passthrough.get('departurePassthroughStepSides', {})
delay = dep_sides.get('forecastedOrAuditedDelay', 0)
if delay > 300: # More than 5 minutes
print(f"Warning: {delay//60} min delay")
time.sleep(30)
2. Query Complete Routes
trains_with_routes = client.get_all_departures_with_routes(
station_code="10200",
traffic_type="AVLDMD",
max_trains=5
)
for train in trains_with_routes:
print(f"Train {train['commercial_number']}")
print(f" Stops: {len(train['route'])}")
3. Interactive CLI
python3 query_api.py
Tools Used
- Ghidra - Key extraction from
libapi-keys.so - JADX - APK decompilation
- Python 3 - Client implementation
- Frida (optional) - Dynamic analysis
Documentation
- API_DOCUMENTATION.md - Complete API documentation
- AUTHENTICATION_ALGORITHM.md - Detailed HMAC algorithm
- GHIDRA_GUIDE.md - Step-by-step Ghidra tutorial
Known Limitations
allControlPoints: truein/compositions/path/requires elevated permissions (returns 401). Leave it out to get 204/200 responses.- 204 responses simply mean “no data right now” with valid auth.
License
MIT License - See LICENSE
Disclaimer: Project for educational and research purposes. Use responsibly.
Description
Reverse-engineered toolkit for the ADIF (El Cano Móvil) API. Includes a Python client with HMAC-SHA256 signing, interactive CLI, higher-level client, and endpoint tests (real-train scenarios). Docs cover endpoints, request bodies, auth algorithm, and APK analysis. APK artifacts and 1,587 station codes are included.
https://github.com/Dasemu/adif-api-reverse-engineering
Languages
Java
99.9%
Python
0.1%