Encapsulating Security Payload
Chapter Seventy-Four
Syllabus topic Module 2, "IP Security: Encapsulating Security Payload"
Pages 490 to 495 of 678
In one line
ESP encrypts the contents of an IP packet and, usually, checks that nothing was changed, so that an observer on the path sees who is talking but nothing of what is said, not even which application is talking.
In the words an answer should use: the Encapsulating Security Payload (ESP), specified in RFC 4303, provides confidentiality, data origin authentication, connectionless integrity, an anti-replay service and limited traffic flow confidentiality for IP datagrams; its integrity check covers the ESP header, the payload and the ESP trailer, but not the IP header in front of it.
What ESP adds that AH did not
The Authentication Header proves a packet is genuine. ESP also keeps it secret. And since ESP can also provide integrity, and can be used with no encryption at all, it does everything AH does except protect the outer IP header, which is exactly the part that NAT needs to change. That is why RFC 4301 requires every IPsec implementation to support ESP and only permits AH.
The packet format
The IP header's protocol field is set to 50 for ESP. What follows is RFC 4303's layout:
| Field | Size | Encrypted | Covered by the ICV |
|---|---|---|---|
| Security Parameters Index | 32 bits | no | yes |
| Sequence Number | 32 bits | no | yes |
| Payload Data, beginning with an IV if the cipher needs one | variable | yes (not the IV) | yes |
| Padding | 0 to 255 bytes | yes | yes |
| Pad Length | 8 bits | yes | yes |
| Next Header | 8 bits | yes | yes |
| Integrity Check Value | variable | no | it is the check |
The SPI and sequence number stay in the clear because the receiver must read them before it can do anything: the SPI to find the SA and its keys, the sequence number to reject replays before spending effort on decryption.
Padding, Pad Length and Next Header are the ESP trailer. They sit at the end and are encrypted with the payload, so an observer cannot even tell which protocol the packet carries.
Two rules about coverage come straight from RFC 4303: if integrity is selected, it covers the SPI, the sequence number, the payload and the trailer; if confidentiality is selected, the ciphertext is the payload and the trailer, but not the IV, which the receiver needs in the clear to decrypt.
Why padding exists
RFC 4303 s.2.4 gives the reasons, and a question on ESP expects them.
- The cipher's block size. A block cipher in CBC mode encrypts whole blocks, so payload, padding, pad length and next header together must be a multiple of the block size: 16 bytes for AES.
- Alignment. Even with a cipher that needs no blocks, the Pad Length and Next Header fields must end on a four-byte boundary, so that the ICV that follows is aligned.
- Hiding the length. Extra padding could hide the true size of the payload. RFC 4303 notes that the padding field, at most 255 bytes, is "too limited to be effective" for this and provides a separate mechanism, traffic flow confidentiality (TFC) padding, inside the payload.
Encapsulating Security Payload
The sender may add from 0 to 255 bytes of padding, and by default fills it with the bytes 1, 2, 3 and so on, so the receiver can check it.
Worked example. A 20-byte payload under AES-CBC: 20 + 2 = 22 bytes of payload and trailer; the next multiple of 16 is 32; so 10 bytes of padding. Under AES-GCM, which needs only four-byte alignment: 22 rounds up to 24, so 2 bytes of padding.
ESP in the two modes
Figure 74.1 ESP encrypts what follows its header and authenticates from its header to its trailer; the IP header in front is covered by neither.
Transport mode. The ESP header follows the original IP header. The TCP or UDP segment and the trailer are encrypted; the original IP header is neither encrypted nor covered by ESP's check. An observer sees the real addresses, and nothing else.
Tunnel mode. The whole original packet, header included, is encrypted inside a new packet whose header names the two IPsec endpoints. An observer sees only that two gateways are exchanging ESP; which hosts behind them are talking, and on which ports, is hidden. This is the standard arrangement for a VPN.
The run: ESP built byte by byte
The listing assembles ESP packets exactly as RFC 4303 lays them out, with HMAC-SHA2-256-128 as the integrity check. It works the padding for the two ciphers RFC 8221 requires, then shows what an observer sees, runs ESP with the NULL cipher, and passes a packet through a NAT. The cipher itself is a stand-in, but every size is the size AES would give.
# ESP built byte by byte: padding, what an observer sees, NULL encryption, and NAT.
import hashlib, hmac, random
rng = random.Random(740)
KEY_E, KEY_A, SPI = rng.randbytes(16), rng.randbytes(32), 0x0000B002
def keystream(iv, n): # a stand-in for AES; the sizes below are AES's
return b''.join(hashlib.sha256(KEY_E + iv + i.to_bytes(4, 'big')).digest()
for i in range(n // 32 + 1))[:n]
def esp(payload, next_header, seq, block=16, iv_len=16, cipher=True):
"""RFC 4303: SPI, sequence, [IV], payload, padding, pad length, next header, ICV."""
pad = (-(len(payload) + 2)) % block
trailer = bytes(range(1, pad + 1)) + bytes([pad, next_header]) # default padding 1, 2, 3
plain = payload + trailer
iv = rng.randbytes(iv_len) if cipher else b''
body = iv + (bytes(a ^ b for a, b in zip(plain, keystream(iv, len(plain)))) if cipher
else plain)
head = SPI.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
icv = hmac.new(KEY_A, head + body, hashlib.sha256).digest()[:16] # HMAC-SHA2-256-128
return head + body + icv, pad
def esp_open(packet, iv_len=16, cipher=True):
head_body, icv = packet[:-16], packet[-16:]
if not hmac.compare_digest(icv, hmac.new(KEY_A, head_body, hashlib.sha256).digest()[:16]):
return None
body = head_body[8:]
iv, data = body[:iv_len], body[iv_len:]
plain = bytes(a ^ b for a, b in zip(data, keystream(iv, len(data)))) if cipher else data
pad, nh = plain[-2], plain[-1]
return nh, plain[:-2 - pad]
# ---- 1. padding, for the ciphers RFC 8221 names ----------------------------------------
print('PADDING: payload + padding + 2 must fill whole cipher blocks, and end on 4 bytes')
print(' payload AES-CBC (16-byte blocks) AES-GCM (4-byte alignment only)')
for n in (1, 14, 20, 100, 1400):
_, p_cbc = esp(bytes(n), 6, 1, block=16, iv_len=16)
_, p_gcm = esp(bytes(n), 6, 1, block=4, iv_len=8)
cbc = 8 + 16 + n + p_cbc + 2 + 16
gcm = 8 + 8 + n + p_gcm + 2 + 16
print(' %5d pad %2d, ESP adds %2d bytes pad %d, ESP adds %2d bytes'
% (n, p_cbc, cbc - n, p_gcm, gcm - n))
# ---- 2. what an observer on the path sees ----------------------------------------------
tcp = (8080).to_bytes(2, 'big') + (80).to_bytes(2, 'big') + bytes(16) + b'GET /marks HTTP/1.1\r\n'
packet, _ = esp(tcp, 6, seq=7)
print()
print('WHAT AN OBSERVER SEES OF ONE ESP PACKET (%d bytes)' % len(packet))
print(' SPI %s sequence %d : in clear, so the receiver can find the SA and check replays'
% (packet[:4].hex(), int.from_bytes(packet[4:8], 'big')))
print(' the TCP ports 8080 and 80 are visible :', tcp[:4] in packet)
print(' the text "GET /marks" is visible :', b'GET /marks' in packet)
print(' the receiver recovers protocol and data :', esp_open(packet) == (6, tcp))
# ---- 3. ESP with NULL encryption: integrity without secrecy ----------------------------
null, _ = esp(tcp, 6, seq=8, block=4, iv_len=0, cipher=False)
print()
print('ESP WITH THE NULL CIPHER (RFC 8221: MUST be implemented)')
print(' the text is visible :', b'GET /marks' in null)
print(' the receiver verifies and recovers it :',
esp_open(null, iv_len=0, cipher=False) == (6, tcp))
altered = bytearray(null)
altered[30] ^= 1
print(' one changed byte is detected :', esp_open(bytes(altered), 0, False) is None)
# ---- 4. a NAT on the path ---------------------------------------------------------------
def outer_ip(src, dst, protocol, payload):
h = bytearray(20)
h[0], h[8], h[9] = 0x45, 64, protocol
h[2:4] = (20 + len(payload)).to_bytes(2, 'big')
h[12:16], h[16:20] = bytes(map(int, src.split('.'))), bytes(map(int, dst.split('.')))
return bytes(h) + payload
sent = outer_ip('10.1.4.7', '203.0.113.2', 50, packet)
translated = sent[:12] + bytes([198, 51, 100, 1]) + sent[16:] # the NAT's new source
print()
print('A NAT REWRITES THE SOURCE ADDRESS 10.1.4.7 TO 198.51.100.1')
print(' the outer header changed :', translated[12:16] != sent[12:16])
print(' ESP still verifies and decrypts :', esp_open(translated[20:]) == (6, tcp))
print(' (AH covers the source address, so the same NAT breaks it: the previous chapter)')Encapsulating Security Payload
PADDING: payload + padding + 2 must fill whole cipher blocks, and end on 4 bytes
payload AES-CBC (16-byte blocks) AES-GCM (4-byte alignment only)
1 pad 13, ESP adds 55 bytes pad 1, ESP adds 35 bytes
14 pad 0, ESP adds 42 bytes pad 0, ESP adds 34 bytes
20 pad 10, ESP adds 52 bytes pad 2, ESP adds 36 bytes
100 pad 10, ESP adds 52 bytes pad 2, ESP adds 36 bytes
1400 pad 6, ESP adds 48 bytes pad 2, ESP adds 36 bytes
WHAT AN OBSERVER SEES OF ONE ESP PACKET (88 bytes)
SPI 0000b002 sequence 7 : in clear, so the receiver can find the SA and check replays
the TCP ports 8080 and 80 are visible : False
the text "GET /marks" is visible : False
the receiver recovers protocol and data : True
ESP WITH THE NULL CIPHER (RFC 8221: MUST be implemented)
the text is visible : True
the receiver verifies and recovers it : True
one changed byte is detected : True
A NAT REWRITES THE SOURCE ADDRESS 10.1.4.7 TO 198.51.100.1
the outer header changed : True
ESP still verifies and decrypts : True
(AH covers the source address, so the same NAT breaks it: the previous chapter)Encapsulating Security Payload
What the run establishes, in order.
Padding depends on the cipher. Under AES-CBC every payload is padded to a multiple of 16: a 1-byte payload gets 13 bytes of padding, a 20-byte payload 10, and a 14-byte payload none, because 14 + 2 is exactly 16. Under AES-GCM only four-byte alignment is needed, so padding never exceeds 3 bytes. Counting the SPI, sequence number, IV, padding, trailer and ICV, ESP adds 42 to 55 bytes under AES-CBC and 34 to 36 under AES-GCM, one reason RFC 8221 now makes GCM a MUST.
An observer learns almost nothing. Of an 88-byte packet only the SPI and the sequence number are readable. Even the TCP ports are hidden, which AH could never do; the web request is invisible; and the receiver recovers the protocol and the data exactly.
ESP with the NULL cipher is AH's job done by ESP. The text is readable, but the receiver verifies it, and a single changed byte is detected.
A NAT does not break ESP. Rewriting the outer source address leaves ESP verifying and decrypting correctly, because ESP's check starts at the ESP header. In the previous chapter the same rewrite broke AH.
ESP against AH
| AH | ESP | |
|---|---|---|
| IP protocol number | 51 | 50 |
| Confidentiality | no | yes |
| Integrity and data origin authentication | yes | yes, when selected |
| Anti-replay | yes | yes |
| Limited traffic flow confidentiality | no | yes, in tunnel mode and with TFC padding |
| Covers the IP header in front of it | yes, apart from mutable fields | no |
| Hides the transport protocol and ports | no | yes |
| Survives NAT | no | yes, and inside UDP on port 4500 (RFC 3948) |
| Required by RFC 4301 | MAY | MUST |
Encapsulating Security Payload
Why the modern world uses ESP alone
It does everything needed. Confidentiality and integrity together, or integrity alone with the NULL cipher.
It survives NAT. AH does not, and NAT is everywhere.
Combined algorithms do both jobs in one pass. AES in GCM mode encrypts and authenticates at once, producing its own integrity tag. RFC 8221 made AES-GCM a MUST for ESP; used with it, ESP needs no separate integrity algorithm.
Current algorithm requirements, RFC 8221 (October 2017):
| Algorithm | What it does | RFC 8221 |
|---|---|---|
| AES-GCM with a 16-byte tag | encryption with integrity | MUST |
| AES-CBC | encryption | MUST |
| NULL | no encryption | MUST |
| ChaCha20-Poly1305 | encryption with integrity | SHOULD |
| AES-CCM with an 8-byte tag | encryption with integrity | SHOULD, for IoT devices |
| Triple DES | encryption | SHOULD NOT |
| DES | encryption | MUST NOT |
| HMAC-SHA2-256-128 | integrity | MUST |
| HMAC-SHA2-512-256 | integrity | SHOULD |
| HMAC-SHA1-96 | integrity | MUST- |
| AES-XCBC-96 | integrity | SHOULD, for IoT devices |
| HMAC-MD5-96 | integrity | MUST NOT |
| none, with a cipher that has no integrity | integrity | MUST NOT |
The textbooks' ESP, with DES or Triple DES and HMAC-MD5-96, is today's MUST NOT and SHOULD NOT.
What beginners get wrong here
Saying ESP always authenticates. Integrity is a service ESP provides when it is selected; an SA without it is legal but unwise. RFC 8221 forbids the combination of a non-combined cipher with no integrity algorithm.
Saying ESP protects the IP header. In transport mode the IP header is outside ESP; in tunnel mode the outer header is. Neither is encrypted or covered by ESP's check.
Forgetting what padding is for. It completes the cipher's blocks and aligns the trailer; hiding the length is TFC's job.
Thinking the sequence number is encrypted. The SPI and sequence number must be read before decryption and travel in the clear, protected only by the integrity check.
Quoting DES and MD5 as ESP's algorithms. RFC 8221 says DES and HMAC-MD5 must not be used.
Quick revision
- ESP (RFC 4303): IP protocol 50; confidentiality, integrity, origin authentication, anti-replay, limited traffic flow confidentiality.
- Format: SPI, Sequence Number (both clear), Payload Data (with IV), Padding (0 to 255), Pad Length, Next Header, ICV.
- Integrity covers SPI to trailer; encryption covers payload and trailer; the IP header in front is covered by neither.
- Padding: block size, four-byte alignment; not for hiding length (use TFC padding). AES-CBC pads to 16; AES-GCM to 4.
- Transport hides the payload and ports; tunnel hides the whole inner packet, real addresses included.
- ESP alone: does AH's job with NULL encryption, survives NAT (RFC 3948, UDP port 4500), and AES-GCM does both jobs in one pass. ESP MUST, AH MAY.
- RFC 8221: AES-GCM, AES-CBC, NULL MUST; HMAC-SHA2-256-128 MUST; 3DES SHOULD NOT; DES and HMAC-MD5 MUST NOT.
Encapsulating Security Payload
Test yourself
1. Draw the ESP packet format and state which fields are encrypted and which are authenticated. It consists of the Security Parameters Index and the Sequence Number, each 32 bits; the Payload Data, beginning with an initialisation vector when the cipher needs one; the Padding, from 0 to 255 bytes; the 8-bit Pad Length; the 8-bit Next Header; and the Integrity Check Value. The payload and the trailer (padding, pad length and next header) are encrypted, the IV excepted. The integrity check covers everything from the SPI to the end of the trailer. The SPI and sequence number are not encrypted, and the IP header before ESP is covered by neither.
2. Give the reasons for the Padding field. To make the plaintext, payload plus padding plus pad length plus next header, a whole number of blocks for a block cipher; and to make the trailer end on a four-byte boundary so that the ICV is aligned, even when the cipher has no block size. Extra padding could also hide the payload's length, but RFC 4303 says the field is too limited for that and provides separate TFC padding.
3. How much padding does a 20-byte payload need under AES-CBC, and under AES-GCM? Under AES-CBC, 20 bytes of payload and 2 bytes of trailer make 22, and the next multiple of the 16-byte block is 32, so 10 bytes of padding. Under AES-GCM only four-byte alignment is needed, so 22 is rounded up to 24, and 2 bytes of padding are added.
4. Compare ESP with AH. ESP provides confidentiality as well as integrity, origin authentication and anti-replay, and can hide the transport protocol and ports; AH provides no confidentiality. AH's check covers the unchanging fields of the IP header in front of it, which ESP's does not. Because of that, AH fails through NAT while ESP passes through it. RFC 4301 requires ESP and makes AH optional.
5. Why is ESP now used on its own? Because it can do everything required: with the NULL cipher it provides integrity and origin authentication alone, which is AH's job; it passes through NAT, which AH cannot; and combined algorithms such as AES-GCM give encryption and integrity in one operation. RFC 8221 keeps NULL encryption mandatory for exactly this purpose.
6. What does an observer on the path see of an ESP packet in tunnel mode? The outer IP header, which names only the two IPsec endpoints, usually gateways, and the SPI and sequence number. The original packet, including the real source and destination addresses, the transport protocol, the ports and the data, is encrypted.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.