PWR Túnel

Proxy cifrado post-cuantico para trafico HTTP entre sedes. Cifra peticiones y respuestas con doble capa criptografica (ChaCha20-Poly1305 + AES-256-GCM) y handshake ML-KEM-768 (Kyber) para resistencia ante ordenadores cuanticos. PHP puro, sin dependencias externas.

Instalacion

Túnel es un unico archivo PHP. Copialo a tu proyecto e incluyelo donde necesites cifrar trafico HTTP entre sedes.

PHP
require_once __DIR__ . '/core/tunel.php';

Requisitos minimos

  • PHP 7.4 o superior
  • Extension sodium (incluida por defecto en PHP 7.2+)
  • Extension openssl con soporte AES-256-GCM
  • Opcional: ext-ffi + liboqs para ML-KEM-768 nativo
Sin liboqs: Túnel funciona sin liboqs. Utiliza un fallback con X25519 + HKDF para el intercambio de claves. Las capas de cifrado simetrico (ChaCha20-Poly1305 + AES-256-GCM) son identicas en ambos modos.

Uso rapido

Flujo completo en 5 pasos: generar claves, handshake, crear sesion, empaquetar request, desempaquetar en destino.

PHP — Flujo completo
require_once 'core/tunel.php';

// 1. Cada sede genera sus claves
$sede_a = pwr_tunel_generar_claves();
$sede_b = pwr_tunel_generar_claves();

// 2. Handshake: A encapsula con pk de B
$hs = pwr_tunel_handshake($sede_b['pk']);

// 3. B decapsula y obtiene el mismo shared_secret
$dec = pwr_tunel_decapsular($sede_b['sk'], $hs['ciphertext']);

// 4. Ambos crean sesion con el shared_secret
$sesion = pwr_tunel_crear_sesion($hs['shared_secret']);

// 5. A envia request cifrado a B
$paquete = pwr_tunel_empaquetar_request(
    'POST',
    'https://sede-b.ejemplo.es/api/datos',
    ['Content-Type' => 'application/json'],
    '{"expediente":"2026/001"}',
    $sesion
);

// B desempaqueta
$request = pwr_tunel_desempaquetar_request(
    $paquete['paquete'],
    $sesion
);

Generar claves

Cada sede genera su propio par de claves (publica y secreta). La clave publica se comparte con la otra sede; la secreta se guarda en local.

pwr_tunel_generar_claves()

Firma
pwr_tunel_generar_claves(): array

Retorna:

ClaveTipoDescripcion
okboolSiempre true
pkstringClave publica (base64)
skstringClave secreta (base64)
modostringml_kem_768 o simulado_x25519
Ejemplo
$claves = pwr_tunel_generar_claves();

// Guardar la pk para enviarla a la otra sede
file_put_contents('mi_pk.b64', $claves['pk']);

// Guardar la sk en lugar seguro (nunca compartir)
file_put_contents('mi_sk.b64', $claves['sk']);
Importante: La clave secreta (sk) nunca debe salir del servidor. Solo comparte la clave publica (pk) con la sede remota.

Handshake PQC

El handshake establece un secreto compartido entre ambas sedes. Utiliza ML-KEM-768 (Kyber) si liboqs esta disponible, o X25519 + HKDF como fallback. En ambos casos, el resultado es un shared_secret de 32 bytes.

pwr_tunel_handshake()

La sede origen encapsula un secreto con la clave publica del destino.

Firma
pwr_tunel_handshake(string $pk_destino): array
ClaveTipoDescripcion
okboolExito o fallo
shared_secretstringSecreto compartido (base64, 32 bytes)
ciphertextstringCiphertext para enviar al destino (base64)
modostringAlgoritmo utilizado

pwr_tunel_decapsular()

La sede destino recupera el secreto compartido con su clave secreta.

Firma
pwr_tunel_decapsular(string $sk, string $ciphertext): array
Ejemplo — Handshake completo
// Sede A: encapsula con pk de B
$hs = pwr_tunel_handshake($pk_sede_b);
// Enviar $hs['ciphertext'] a la sede B

// Sede B: decapsula con su sk
$dec = pwr_tunel_decapsular($sk_sede_b, $hs['ciphertext']);

// Ambos tienen el mismo shared_secret
// $hs['shared_secret'] === $dec['shared_secret']

Sesiones

Una sesion encapsula las claves simetricas derivadas del handshake, junto con metadatos de control (TTL, contadores, estado).

pwr_tunel_crear_sesion()

Firma
pwr_tunel_crear_sesion(string $shared_secret, array $opciones = []): array
OpcionTipoDefectoDescripcion
ttlint3600Tiempo de vida en segundos
idstringaleatorioIdentificador de sesion

La sesion contiene:

  • clave_chacha — Clave para la capa ChaCha20-Poly1305
  • clave_aes — Clave para la capa AES-256-GCM
  • sal — Sal utilizada en la derivacion
  • creada, expira, ttl — Control temporal
  • bytes_enviados, bytes_recibidos, paquetes — Contadores
  • rotaciones — Numero de rotaciones de claves realizadas

pwr_tunel_estado()

Consulta el estado actual de una sesion.

Firma
pwr_tunel_estado(array $sesion): array

Retorna: activa (bool), razon (string), ttl_restante (int), bytes_total (int), paquetes (int), rotaciones (int).

Cifrar / Descifrar

El cifrado usa doble capa: primero ChaCha20-Poly1305 (libsodium), despues AES-256-GCM (OpenSSL). El descifrado es en orden inverso. Cada capa usa su propia clave, nonce/IV y datos asociados (AD) para autenticacion AEAD.

pwr_tunel_cifrar()

Firma
pwr_tunel_cifrar(string $datos, array &$sesion): array

pwr_tunel_descifrar()

Firma
pwr_tunel_descifrar(array $datos_cifrados, array &$sesion): array
Ejemplo — Cifrado/descifrado directo
$texto = 'Datos sensibles del expediente municipal';

// Cifrar con doble capa
$cifrado = pwr_tunel_cifrar($texto, $sesion);

// Descifrar
$descifrado = pwr_tunel_descifrar($cifrado, $sesion);
// $descifrado['datos'] === $texto
AEAD: Ambas capas utilizan cifrado autenticado con datos asociados (AEAD). Esto significa que cualquier modificacion del ciphertext sera detectada automaticamente durante el descifrado, protegiendo contra ataques de manipulacion.

Empaquetado HTTP

Las funciones de empaquetado serializan peticiones y respuestas HTTP completas (metodo, URL, headers, body) en un paquete cifrado que puede transmitirse por cualquier canal.

pwr_tunel_empaquetar_request()

Firma
pwr_tunel_empaquetar_request(
    string $method,
    string $url,
    array $headers,
    string $body,
    array &$sesion
): array

pwr_tunel_desempaquetar_request()

Firma
pwr_tunel_desempaquetar_request(string $paquete, array &$sesion): array

pwr_tunel_empaquetar_response()

Firma
pwr_tunel_empaquetar_response(
    int $status,
    array $headers,
    string $body,
    array &$sesion
): array

pwr_tunel_desempaquetar_response()

Firma
pwr_tunel_desempaquetar_response(string $paquete, array &$sesion): array
Ejemplo — Request + Response
// Sede A envia request
$req = pwr_tunel_empaquetar_request(
    'POST',
    'https://sede-b.palma.es/api/registro',
    ['Content-Type' => 'application/json'],
    '{"ciudadano":"12345678A"}',
    $sesion
);
// Enviar $req['paquete'] por HTTPS a la sede B

// Sede B recibe y desempaqueta
$request = pwr_tunel_desempaquetar_request($paquete_recibido, $sesion);
// $request['method'] === 'POST'
// $request['body'] === '{"ciudadano":"12345678A"}'

// Sede B responde
$res = pwr_tunel_empaquetar_response(
    201,
    ['Content-Type' => 'application/json'],
    '{"ok":true,"id":"REG-001"}',
    $sesion
);

Rotacion de claves

La rotacion de claves permite renovar las claves simetricas de la sesion sin necesidad de repetir el handshake. Deriva nuevas claves a partir del shared_secret original y una sal fresca, garantizando forward secrecy parcial dentro de la sesion.

pwr_tunel_rotar_claves()

Firma
pwr_tunel_rotar_claves(array &$sesion): array
Ejemplo
// Rotar claves cada N mensajes o periodicamente
if ($sesion['paquetes'] % 100 === 0) {
    $rot = pwr_tunel_rotar_claves($sesion);
    // $rot['rotacion'] indica el numero de rotacion
}
Sincronizacion: Ambas sedes deben rotar claves de forma sincronizada. Si una sede rota y la otra no, los mensajes no podran descifrarse. Implementa un protocolo de senalizacion (ej: un mensaje de tipo rotate) para coordinar la rotacion.

API de funciones

Resumen de todas las funciones publicas del modulo Túnel.

FuncionDescripcion
pwr_tunel_info()Informacion del modulo: version, capacidades, algoritmos
pwr_tunel_generar_claves()Genera par de claves ML-KEM-768 (o X25519 fallback)
pwr_tunel_handshake($pk)Encapsula shared_secret con clave publica destino
pwr_tunel_decapsular($sk, $ct)Recupera shared_secret con clave secreta propia
pwr_tunel_crear_sesion($ss, $opts)Crea sesion con claves derivadas del shared_secret
pwr_tunel_cifrar($datos, &$sesion)Cifra con doble capa ChaCha20 + AES-256
pwr_tunel_descifrar($cifrado, &$sesion)Descifra en orden inverso AES-256 + ChaCha20
pwr_tunel_empaquetar_request(...)Empaqueta y cifra peticion HTTP completa
pwr_tunel_desempaquetar_request(...)Descifra y desempaqueta peticion HTTP
pwr_tunel_empaquetar_response(...)Empaqueta y cifra respuesta HTTP completa
pwr_tunel_desempaquetar_response(...)Descifra y desempaqueta respuesta HTTP
pwr_tunel_estado($sesion)Verifica estado de sesion (activa, expirada, bytes)
pwr_tunel_rotar_claves(&$sesion)Rota claves simetricas sin repetir handshake

Arquitectura

PWR Túnel implementa un modelo de proxy cifrado punto a punto entre sedes, con tres capas de seguridad independientes.

Flujo de datos

Diagrama
Sede A                           Sede B
  |                                |
  |  1. Generar claves (pk_a, sk_a)  1. Generar claves (pk_b, sk_b)
  |                                |
  |  2. Intercambiar pks           |
  |  ─────── pk_a ───────────────> |
  |  <─────── pk_b ─────────────── |
  |                                |
  |  3. Handshake PQC              |
  |  encapsular(pk_b) → ss + ct   |
  |  ─────── ct ─────────────────> |
  |                      decapsular(sk_b, ct) → ss
  |                                |
  |  4. Crear sesion(ss)           4. Crear sesion(ss)
  |                                |
  |  5. Empaquetar request         |
  |  [HTTP req] → cifrar(2 capas)  |
  |  ─── paquete cifrado ────────> |
  |                      desempaquetar → [HTTP req]
  |                                |
  |                      6. Empaquetar response
  |       descifrar ← [paquete] ── |
  |                                |

Capas de cifrado

CapaAlgoritmoLibreriaFuncion
1 (interna) ChaCha20-Poly1305 IETF libsodium Cifrado autenticado AEAD con nonce de 12 bytes
2 (externa) AES-256-GCM OpenSSL Cifrado autenticado AEAD con IV de 12 bytes + tag de 16 bytes
KEM ML-KEM-768 / X25519 liboqs / libsodium Intercambio de claves post-cuantico

Derivacion de claves

Las claves simetricas se derivan del shared_secret mediante HKDF (RFC 5869) con SHA-256, usando contextos independientes para cada capa:

  • pwr_tunel_chacha_v1 — Contexto para la clave ChaCha20-Poly1305
  • pwr_tunel_aes_v1 — Contexto para la clave AES-256-GCM
Forward secrecy: Cada sesion usa claves efimeras derivadas de un handshake unico. Comprometer una sesion no compromete las anteriores ni las futuras. La rotacion de claves anade forward secrecy intra-sesion.