Skip to main content
  1. Blog/

ESP cifra, AH no: IPsec visto dal vivo

Alessio Barnini
Author
Alessio Barnini
Table of Contents

TL;DR
  • IPsec Suite: Una suite di protocolli di rete sicuri (IKE + ESP + AH) implementata a livello IP per garantire autenticazione, integrità e riservatezza.
  • IKE (Internet Key Exchange): Negozia gli algoritmi di sicurezza e stabilisce le Security Association (SA) scambiando chiavi tramite Diffie-Hellman (UDP 500/4500).
  • ESP (Encapsulating Security Payload): Cifra il payload dei pacchetti (ad es. con AES-256) garantendo riservatezza ed autenticazione. Supporta il NAT tramite incapsulamento NAT-T.
  • AH (Authentication Header): Firma crittograficamente i pacchetti per garantirne l'integrità, ma non cifra il payload, lasciando i dati in chiaro ed esposti allo sniffing.
  • Tunnel vs Transport: Tunnel mode cifra l'intero pacchetto originale aggiungendo un nuovo header IP (ideale per VPN Site-to-Site); Transport mode cifra solo il payload (ideale per host-to-host).
$ history
  • apt install strongswan -y
  • ipsec version
  • ipsec restart
  • ipsec up mustache
  • ipsec statusall
  • tcpdump -i enp0s1 udp port 500
  • tcpdump -i enp0s1 proto 50
  • tcpdump -i enp0s1 proto 51 -v
  • ping 192.168.64.3

Configurare IPsec host-to-host con StrongSwan e vedere con tcpdump la differenza tra ESP e AH. ESP cifra il payload - AH no. Questa distinzione è una domanda classica Security+ e fondamentale per la sicurezza di rete.

Analisi interna del protocollo IPsec: ESP vs AH e Transport vs Tunnel Mode

Obiettivo del laboratorio
#

In questo laboratorio configureremo un collegamento IPsec host-to-host sicuro tra due macchine virtuali e ne analizzeremo in profondità i pacchetti di rete sul canale fisico:

Ubuntu (192.168.64.3)   → StrongSwan concentrator (responder)
Kali   (192.168.64.200) → StrongSwan initiator (client)

VPN Concentrator
#

In questo lab Ubuntu è il concentrator - il punto di terminazione del tunnel.

Un concentrator è il dispositivo che:

  • sta in ascolto su UDP 500 (IKE)
  • accetta connessioni da uno o più client
  • termina il tunnel: riceve ESP cifrato, decapsula, passa il payload alla rete interna

In produzione sono appliance dedicati: Cisco ASA, Palo Alto, Fortinet. Qui usiamo StrongSwan su Ubuntu - funzione identica, hardware diverso.

Il client (Kali) non è un concentrator: apre una connessione, non ne accetta. È asimmetrico per design - il concentrator gestisce N client simultanei, ogni client gestisce 1 tunnel.


Installazione
#

IPsec è uno standard - una suite di protocolli (IKE + ESP + AH). Non è software, è una specifica RFC. Il kernel Linux capisce ESP e AH nativamente, ma qualcuno deve gestire la negoziazione IKE: scambiare chiavi, creare le SA, autenticarsi.

Prima di installare, i tre protocolli che compongono IPsec:

IPsec
├── IKE - Internet Key Exchange        UDP 500 / 4500
│   │     Negozia algoritmi, scambia chiavi, crea le SA.
│   │     Sparisce dopo il setup. Non trasporta dati.
│   │
│   └── NAT-T - NAT Traversal
│               Se rileva NAT, incapsula IKE in UDP 4500.
│               Necessario perché ESP non ha porte TCP/UDP
│               e i NAT non saprebbero a chi girare i pacchetti.
├── SA  - Security Association
│         L'accordo tra due host: quale algoritmo usare,
│         quale chiave, per quanto tempo. Unidirezionale:
│         una SA per ogni direzione (inbound + outbound).
│         Identificata dall'SPI (Security Parameter Index).
├── ESP - Encapsulating Security Payload    IP proto 50
│         Cifra il payload (AES-256) + autentica (HMAC).
│         È quello che vedi sul cavo dopo il tunnel up.
│         Funziona con NAT (NAT-T lo incapsula in UDP 4500).
└── AH  - Authentication Header            IP proto 51
           Autentica il pacchetto ma NON cifra nulla.
           Payload visibile in tcpdump.
           Non funziona con NAT (firma anche gli IP header).
           Praticamente mai usato in produzione.

StrongSwan è il daemon che fa quella parte. Quando fai sudo ipsec up mustache, stai dicendo a StrongSwan di avviare una negoziazione IKE verso l'altro host. StrongSwan poi parla con il kernel per installare le Security Association, e da quel momento il kernel gestisce ESP direttamente senza che StrongSwan sia in mezzo.

Il comando ipsec che usi è il CLI di StrongSwan - è lui che lo installa.

┌─────────────────────────────────────────┐
│  sudo ipsec up mustache                 │
│         │                               │
│   StrongSwan daemon                     │  ← installa strongswan
│   (gestisce IKE su UDP 500/4500)        │
│         │                               │
│   kernel Linux                          │  ← già capisce ESP/AH
│   (gestisce ESP/AH sul cavo)            │
└─────────────────────────────────────────┘

Esistono altre implementazioni dello stesso standard: Libreswan (RHEL/Fedora), OpenSwan (predecessore), Cisco IOS (enterprise). Tutti implementano IPsec e si parlano tra loro perché seguono lo stesso RFC - come nginx e Apache che implementano entrambi HTTP.

sudo apt install strongswan -y
ipsec version

Su Kali il primo tentativo dava 404 su strongswan-starter - repo in aggiornamento. Soluzione: sudo apt update e riprovare.


Configurazione Ubuntu - responder
#

/etc/ipsec.conf:

config setup

conn mustache
    left=192.168.64.3
    right=192.168.64.200
    type=tunnel
    esp=aes256-sha256
    ikelifetime=1h
    keylife=30m
    authby=secret
    auto=add

/etc/ipsec.secrets - shared secret PSK:

192.168.64.3 192.168.64.200 : PSK "mustache2026"
sudo ipsec restart
# Stopping strongSwan IPsec...
# Starting strongSwan 5.9.13 IPsec [starter]...

Configurazione Kali - initiator
#

Stessa struttura di Ubuntu ma left e right invertiti.

/etc/ipsec.conf:

config setup

conn mustache
    left=192.168.64.200
    right=192.168.64.3
    type=tunnel
    esp=aes256-sha256
    ikelifetime=1h
    keylife=30m
    authby=secret
    auto=add

/etc/ipsec.secrets:

192.168.64.200 192.168.64.3 : PSK "mustache2026"
sudo ipsec restart

Left e right in IPsec: left è sempre "io", right è sempre "l'altro". Ubuntu si mette a sinistra e vede Kali a destra. Kali si mette a sinistra e vede Ubuntu a destra. StrongSwan capisce da solo chi è il responder e chi l'initiator in base a chi apre la connessione.

Initiator e Responder
#

Concentrator vs Responder: il termine "concentrator" è quello che trovi su Security+ - "responder" è quello che usa StrongSwan nei log. Stessa funzione, vocabolario diverso. Ubuntu qui fa entrambe le cose.

In IKE qualcuno deve fare la prima mossa:

  • Initiator (Kali): manda il primo pacchetto IKE_SA_INIT. È lui che vuole aprire il tunnel.
  • Responder (Ubuntu): sta in ascolto, risponde. Non sa che Kali esiste finché non arriva quel primo pacchetto.
        INITIATOR                        RESPONDER
     Kali (192.168.64.200)          Ubuntu (192.168.64.3)

     [ ipsec up mustache ]           [ in ascolto... ]
              │                               │
              │── IKE_SA_INIT ──────────────►│  "qualcuno vuole parlare"
              │◄─────────────── response ────│  "rispondo"
              │                               │
              │── IKE_AUTH ────────────────►│  "ecco il mio PSK firmato"
              │◄─────────────── response ────│  "confermato, tunnel su"
              │                               │
     [ tunnel stabilito ]            [ tunnel stabilito ]
              │                               │
              │══ ESP (proto 50) ════════════►│
              │◄════ ESP (proto 50) ══════════│
                   (da qui: simmetrico)

Una volta stabilito il tunnel la distinzione sparisce - il traffico ESP va in entrambe le direzioni.

conn mustache - conn sta per connection, il nome del blocco di configurazione. mustache è il nome che abbiamo dato noi a questa connessione - poteva essere vpn-lab o qualsiasi cosa. StrongSwan usa il nome per riferirsi alla connessione nei comandi (ipsec up mustache, ipsec down mustache).

PSK "mustache2026" - PSK è Pre-Shared Key, la chiave condivisa in anticipo. È letteralmente una password che entrambi gli host devono conoscere prima che IKE inizi. "Pre-shared" significa che non viene negoziata sul cavo - la metti a mano su entrambe le macchine.

Il PSK è usato solo per autenticazione - dimostrare "sono io, conosco il segreto". Non viene usato per cifrare il traffico ESP.

Le chiavi di cifratura vengono da ECDH (lo stesso meccanismo di WireGuard) - entrambi gli host generano coppie di chiavi temporanee, si scambiano le pubbliche, e derivano la stessa chiave condivisa senza mai trasmetterla sul cavo.

IKE_SA_INIT:
  Kali e Ubuntu si scambiano chiavi pubbliche DH
  → entrambi calcolano la stessa chiave condivisa (mai sul cavo)
  → da questa derivano le chiavi di cifratura per IKE

IKE_AUTH:
  Kali firma un messaggio con il PSK → "sono Kali"
  Ubuntu verifica → "confermato"
  → PSK ha finito il suo lavoro, non serve più

ESP:
  Chiavi di sessione derivate da ECDH, non dal PSK
  → il PSK non cifra nulla

È come la parola "Fidelio" in Eyes Wide Shut - ti apre la porta, dimostra che appartieni. Ma quello che succede dentro la stanza è separato dalla parola. Per questo il PSK deve essere forte: se qualcuno lo indovina, può entrare - anche se non può decifrare il traffico già catturato.

Errore: no private key found
#

Al primo tentativo, ipsec up mustache falliva con:

no private key found for '192.168.64.200'
establishing connection 'mustache' failed

IKE partiva correttamente (il tcpdump su UDP 500 lo confermava) ma l'autenticazione falliva. StrongSwan di default cerca un certificato X.509 - mancava authby=secret nel config per dirgli di usare il PSK.

Fix: aggiungere authby=secret al blocco conn mustache su entrambe le VM.


IKE - la negoziazione delle chiavi
#

IKE è un handshake - più elaborato di quello TCP, ma stessa idea: prima di trasferire dati, i due host si mettono d'accordo. TCP handshake stabilisce solo la connessione. IKE stabilisce chi sei, come cifriamo e quale chiave usiamo in 2 round trip, poi sparisce.

Kali                              Ubuntu
  │                                  │
  │── IKE_SA_INIT ──────────────────►│  "propongo questi algoritmi"
  │◄── IKE_SA_INIT response ─────────│  "accetto, ecco la mia chiave pubblica"
  │                                  │
  │   [entrambi derivano la stessa chiave senza mai trasmetterla - ECDH]
  │                                  │
  │── IKE_AUTH ────────────────────►│  "sono Kali - ecco il PSK firmato"
  │◄── IKE_AUTH response ────────────│  "confermato, tunnel aperto"
  │                                  │
  │══ ESP - dati cifrati ════════════►│  IKE non esiste più sul cavo

Dopo IKE, le SA sono installate nel kernel e il traffico viaggia direttamente in ESP. IKE non è in mezzo - è servito solo per il setup.

Curiosità: IKE come nome e protocollo è specifico di IPsec (RFC 7296). Ma il concetto che implementa - negozia algoritmi, scambia chiavi, autenticati, poi sparisci - esiste in ogni protocollo sicuro:

ProtocolloIl suo "IKE"Meccanismo
IPsecIKE (UDP 500)ECDH + PSK o certificati
TLS/HTTPSTLS HandshakeECDH + certificati X.509
WireGuardHandshake (2 messaggi)Noise Protocol + Curve25519
SSHKey ExchangeDiffie-Hellman
WPA2 WiFi4-way handshakeEAPOL + PMK
WPA3 WiFiSAE (Dragonfly)Diffie-Hellman su curva ellittica

Il meccanismo matematico sottostante è sempre ECDH. Il nome cambia, l'idea no.

Prima di mandare dati cifrati, IPsec deve stabilire le Security Association (SA) - gli accordi su algoritmi, chiavi e parametri. Questo è il lavoro di IKE (Internet Key Exchange) su UDP 500.

Su Ubuntu, tcpdump in ascolto:

sudo tcpdump -i enp0s1 udp port 500

Da Kali, avvia il tunnel:

sudo ipsec up mustache

Output completo:

initiating IKE_SA mustache[1] to 192.168.64.3
sending packet: from 192.168.64.200[500] to 192.168.64.3[500] (924 bytes)
received packet: from 192.168.64.3[500] to 192.168.64.200[500] (280 bytes)
selected proposal: IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
authentication of '192.168.64.200' (myself) with pre-shared key
sending packet: from 192.168.64.200[4500] to 192.168.64.3[4500] (448 bytes)
received packet: from 192.168.64.3[4500] to 192.168.64.200[4500] (272 bytes)
authentication of '192.168.64.3' with pre-shared key successful
IKE_SA mustache[1] established between 192.168.64.200...192.168.64.3
selected proposal: ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ
CHILD_SA mustache{1} established with SPIs c36742cc_i c2359b1c_o and TS 192.168.64.200/32 === 192.168.64.3/32
connection 'mustache' established successfully

Cosa è successo - IKEv2 in 2 fasi
#

Fase 1 - IKE_SA_INIT (UDP 500): Kali e Ubuntu negoziano gli algoritmi e si autenticano con il PSK. IKE crea le SA - gli accordi su algoritmi, chiavi e durata. Proposta selezionata: AES_CBC_128 / HMAC_SHA2_256 / ECP_256.

Fase 2 - IKE_AUTH (UDP 4500): Noti il cambio di porta da 500 a 4500 - è NAT-T (NAT Traversal). IPsec ha rilevato di essere dietro NAT (UTM) e ha incapsulato il traffico in UDP 4500. Senza NAT-T, ESP non passerebbe il NAT: non ha porte TCP/UDP, il NAT non sa a chi girare i pacchetti.

Risultato: CHILD_SA stabilita con due SPI:

  • c36742cc_i - inbound
  • c2359b1c_o - outbound

Ogni pacchetto ESP porta il suo SPI nell'header - il ricevitore lo usa per trovare la SA giusta e sapere come decifrare.

Traffic Selector: 192.168.64.200/32 === 192.168.64.3/32 - solo il traffico tra questi due IP entra nel tunnel.

Kali (192.168.64.200)                   Ubuntu (192.168.64.3)
      │                                         │
      │── IKE_SA_INIT ──────── UDP 500 ────────►│
      │   924 byte - negozia algoritmi          │
      │◄── IKE_SA_INIT response ────────────────│
      │   280 byte - proposta accettata         │
      │                                         │
      │        [NAT rilevato → switch a UDP 4500]
      │                                         │
      │── IKE_AUTH ─────────── UDP 4500 ───────►│
      │   448 byte - PSK auth + richiede CHILD  │
      │◄── IKE_AUTH response ───────────────────│
      │   272 byte - CHILD_SA stabilita         │
      │                                         │
      │   SPI outbound Kali:   0xc2359b1c       │
      │   SPI outbound Ubuntu: 0xc36742cc       │
      │                                         │
      │══ traffico ESP (proto 50) ══════════════►│

ESP - il traffico cifrato sul cavo
#

sudo tcpdump -i enp0s1 proto 50
ping 192.168.64.3   # da Kali
192.168.64.200 > barno-server: ESP(spi=0xc2359b1c,seq=0x1), length 136
barno-server > 192.168.64.200: ESP(spi=0xc36742cc,seq=0x1), length 136
192.168.64.200 > barno-server: ESP(spi=0xc2359b1c,seq=0x2), length 136
barno-server > 192.168.64.200: ESP(spi=0xc36742cc,seq=0x2), length 136
192.168.64.200 > barno-server: ESP(spi=0xc2359b1c,seq=0x3), length 136

Tre cose visibili nell'output:

1. SPI in ogni pacchetto - 0xc2359b1c è l'SPI outbound di Kali, 0xc36742cc è l'SPI outbound di Ubuntu. Matchano esattamente i valori che ipsec up aveva stampato: SPIs c36742cc_i c2359b1c_o. Il ricevitore usa l'SPI per trovare la SA giusta.

2. seq che incrementa - anti-replay. Se qualcuno intercetta il pacchetto seq=0x1 e lo rimanda, Ubuntu lo scarta: quel sequence number è già stato processato.

3. Nessun payload visibile - solo length 136. ESP ha cifrato tutto con AES-256. A differenza di AH che vedremo dopo, non c'è nulla da leggere dentro.

Pacchetto ESP sul cavo (enp0s1) - vista dell'attaccante:

┌────────────────────────────────────────────────┐
│  IP header: 192.168.64.200 → 192.168.64.3     │  ← visibile
│  proto: 50 (ESP)                               │  ← visibile
├──────────────┬─────────────────────────────────┤
│  SPI         │  0xc2359b1c                     │  ← visibile (identifica la SA)
│  seq         │  0x01, 0x02, 0x03...            │  ← visibile (anti-replay)
├──────────────┴─────────────────────────────────┤
│  Encrypted payload                             │  ← cifrato con AES-256
│  [ ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ]    │
│                                                │
│  dentro c'è: ICMP echo → 192.168.64.3          │  ← illeggibile dall'esterno
└────────────────────────────────────────────────┘

AH - autenticazione senza cifratura
#

AH (Authentication Header, IP protocol 51) firma ogni pacchetto ma non cifra nulla. Il payload viaggia in chiaro sul cavo.

Aggiungo una connessione AH in /etc/ipsec.conf su entrambe le VM:

conn mustache-ah
    left=192.168.64.3
    right=192.168.64.200
    type=transport
    ah=sha256
    ikelifetime=1h
    keylife=30m
    authby=secret
    auto=add

Su Kali: stessa struttura con left=192.168.64.200 and right=192.168.64.3.

sudo ipsec restart

Avvio il tunnel AH da Kali:

sudo ipsec up mustache-ah

Su Ubuntu, tcpdump in ascolto su proto 51 (AH):

sudo tcpdump -i enp0s1 -n proto 51 -v

Da Kali, ping:

ping 192.168.64.3

Output tcpdump:

192.168.64.200 > 192.168.64.3: AH(spi=0x...,seq=0x1):
    192.168.64.200 > 192.168.64.3: ICMP echo request, id 1, seq 1, length 64
192.168.64.3 > 192.168.64.200: AH(spi=0x...,seq=0x1):
    192.168.64.3 > 192.168.64.200: ICMP echo reply, id 1, seq 1, length 64

Differenza netta con ESP: tcpdump legge ICMP echo request dentro il pacchetto AH. Il payload non è cifrato - chiunque annusi il cavo vede cosa passa. AH garantisce solo che il pacchetto non sia stato manomesso (integrità) e che venga da chi dice di essere (autenticazione). Non garantisce riservatezza.

AH e NAT non vanno d'accordo: AH include nell'HMAC anche gli IP header. Se un NAT modifica l'IP sorgente durante il transito, la firma non batte più e il pacchetto viene scartato. ESP non ha questo problema perché firma solo il payload cifrato, non gli IP header esterni.


ESP vs AH - il confronto
#

ESP (proto 50)AH (proto 51)
CifraturaSi (AES-256)No
AutenticazioneSiSi
Funziona con NATSi (via NAT-T, UDP 4500)No
Payload visibile in tcpdumpNoSi
Usato in praticaSempreQuasi mai

AH esiste ancora come standard ma nessuno lo usa in produzione: non cifra, e non passa il NAT. Security+ lo chiede perché è un esame, non perché lo vedrai in un firewall reale.


Tunnel mode vs Transport mode
#

Finora la connessione mustache usava type=tunnel. Esiste anche type=transport - cambio l'unica riga e riavvio:

conn mustache
    ...
    type=transport   # era: tunnel
    ...
sudo ipsec restart
sudo ipsec up mustache

tcpdump su enp0s1 con tunnel mode:

192.168.64.200 > 192.168.64.3: ESP(spi=0xc2359b1c,seq=0x1), length 136

tcpdump su enp0s1 con transport mode:

192.168.64.200 > 192.168.64.3: ESP(spi=0x...,seq=0x1), length 112

Il payload è più corto: in transport mode manca l'inner IP header cifrato.

Tunnel mode - wrappa tutto il pacchetto originale:

  ┌── outer IP ──┬── ESP ──┬─── cifrato ─────────────────┐
  │ .200 → .3   │ header  │ inner IP (.200→.3) + ICMP   │
  └─────────────┴─────────┴─────────────────────────────┘
  ↑ aggiunge un nuovo IP header                IP originale nascosto ↑

Transport mode - protegge solo il payload:

  ┌── IP ────────┬── ESP ──┬── cifrato ──┐
  │ .200 → .3   │ header  │ ICMP        │
  └─────────────┴─────────┴─────────────┘
  ↑ IP header originale rimane           payload nascosto ↑

Tunnel mode: usato nei VPN gateway - nasconde la topologia interna (gli IP interni sono dentro il tunnel cifrato). Transport mode: usato host-to-host su LAN dove gli IP sono già noti - overhead minore perché non duplica l'IP header.


exit 0
#

Tre protocolli, un lab. IKE negozia in segreto su UDP 500, passa a 4500 se c'è NAT, poi sparisce. Sul cavo rimane solo ESP o AH.

ESP cifra: tcpdump vede SPI e seq, il payload è ??. AH autentica: tcpdump vede tutto, incluso l'ICMP dentro. Per questo AH non si usa - nessuno vuole mandare dati in chiaro solo perché sono "firmati".

La domanda Security+ più comune su IPsec: quale protocollo fornisce confidentiality? - ESP. AH no.


Comandi usati: ipsec · tcpdump · ping Concetti correlati: ipsec · cryptography · tcp-udp Approfondimento: cap-04-securing-your-network · il-tunnel-che-sceglie

Related