# 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:**
```
<HTTPMethod>\n
<Path>\n
<QueryString>\n
content-type:<ContentType>\n
x-elcano-host:<Host>\n
x-elcano-client:<Client>\n
x-elcano-date:<Timestamp>\n
x-elcano-userid:<UserId>\n
content-type;x-elcano-host;x-elcano-client;x-elcano-date;x-elcano-userid\n
<SHA256HashOfPayload>
```

**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
<sha256_hash_of_payload_hex>
```

**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
<Timestamp>\n
<DateSimple>/<Client>/<UserId>/elcano_request\n
<SHA256HashOfCanonicalRequest>
```

**Ejemplo:**
```
HMAC-SHA256
20251204T204637Z
20251204/AndroidElcanoApp/a1b2c3d4-e5f6-7890-abcd-ef1234567890/elcano_request
<sha256_hash_of_canonical_request_hex>
```

### 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=<accessKey>/<date>/<client>/<userId>/elcano_request,SignedHeaders=<signedHeaders>,Signature=<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 ⏳
