Files
adif-api-reverse-engineering/README.md

6.4 KiB

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: Use originStationCode and destinationStationCode only.
  • onestation: Requires token: "0" and a detailedInfo object.
  • 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


Known Limitations

  • allControlPoints: true in /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.