Loading content...
Integrity protection is the cornerstone of the Authentication Header protocol. Every packet protected by AH carries mathematical proof that its contents haven't been altered since leaving the sender—a guarantee that enables trust in an inherently untrusted network environment.
But how does a sequence of bytes prove its own authenticity? The answer lies in the elegant application of cryptographic hash functions and message authentication codes. Understanding these mechanisms is essential for anyone implementing, configuring, or troubleshooting IPSec security.
This page explores the complete integrity protection lifecycle: from the cryptographic primitives used, through the careful handling of mutable header fields, to the precise validation procedures that determine whether a packet is accepted or rejected.
By the end of this page, you will: understand how HMAC provides authenticated integrity; know exactly which packet fields are protected and how mutable fields are handled; be able to trace the ICV computation and validation process step-by-step; and recognize common integrity-related failures and their causes.
AH's integrity protection relies on Message Authentication Codes (MACs)—specifically, the HMAC (Hash-based Message Authentication Code) construction defined in RFC 2104. To understand why this choice was made, we must first understand the alternatives and their limitations.
Hash Functions Alone Are Insufficient:
A cryptographic hash function (like SHA-256) produces a fixed-size digest from arbitrary input. If the input changes, the digest changes unpredictably. This seems perfect for integrity verification:
The fatal flaw: anyone can compute a hash. An attacker who modifies the packet simply recomputes the hash and replaces the old one. There's no secret, so there's no authentication.
HMAC: Keyed Integrity
HMAC solves this by incorporating a secret key into the hash computation. Only parties possessing the key can compute valid HMACs. The construction is:
HMAC(K, M) = H((K ⊕ opad) || H((K ⊕ ipad) || M))
Where:
The double-hashing with key-dependent padding provides several security properties:
HMAC in AH Context:
When AH uses HMAC-SHA-256-128:
Truncation reduces overhead while maintaining adequate security margin. For HMAC-SHA-256, 128 bits of ICV provides roughly 2^64 security against collision attacks—far beyond practical attack capabilities.
MD5 and SHA-1 based HMACs (HMAC-MD5-96, HMAC-SHA1-96) are deprecated for new deployments due to theoretical weaknesses in the underlying hash functions. While practical attacks against HMAC specifically remain difficult, best practice is to use SHA-256 or stronger. Ensure your IKE configuration prefers modern algorithms.
One of AH's unique capabilities—and one of its operational challenges—is protection of the IP header itself. Unlike ESP, which only protects its own header and payload, AH authenticates most of the outer IP header, detecting any unauthorized modifications.
However, certain IP header fields legitimately change as packets traverse the network. AH must handle these mutable fields carefully to avoid false authentication failures.
IPv4 Header Field Treatment:
| Field | Mutable? | ICV Treatment | Rationale |
|---|---|---|---|
| Version (4 bits) | No | Include as-is | Always 4 for IPv4 |
| IHL (4 bits) | No | Include as-is | Header length fixed after creation |
| DSCP/TOS (8 bits) | Yes | Zero before computation | QoS remarking by routers |
| ECN (2 bits) | Yes | Zero before computation | Congestion notification changes |
| Total Length (16 bits) | No | Include as-is | Fixed after creation |
| Identification (16 bits) | No | Include as-is | Set by sender |
| Flags (3 bits) | Partially | DF included, MF/reserved zeroed | Fragmentation may change |
| Fragment Offset (13 bits) | Yes | Zero before computation | Changes during fragmentation |
| TTL (8 bits) | Yes | Zero before computation | Decrements at each hop |
| Protocol (8 bits) | No | Include as-is | Always 51 (AH) |
| Header Checksum (16 bits) | Yes | Zero before computation | Recomputed when TTL changes |
| Source Address (32 bits) | No* | Include (but see routing) | *May change with loose source routing |
| Destination Address (32 bits) | No* | Include (but see routing) | *May change with source routing |
IPv6 Header Field Treatment:
IPv6's streamlined design simplifies mutable field handling:
| Field | Mutable? | ICV Treatment | Rationale |
|---|---|---|---|
| Version (4 bits) | No | Include as-is | Always 6 for IPv6 |
| Traffic Class (8 bits) | Yes | Zero before computation | QoS may be remarked |
| Flow Label (20 bits) | No* | Include as-is | *Immutable once set |
| Payload Length (16 bits) | No | Include as-is | Fixed after creation |
| Next Header (8 bits) | No | Include as-is | Points to AH (51) |
| Hop Limit (8 bits) | Yes | Zero before computation | Decrements at each hop |
| Source Address (128 bits) | No | Include as-is | No source routing typically |
| Destination Address (128 bits) | No* | Include as-is or predicted | *May change with routing header |
IPv6 Extension Header Considerations:
IPv6 extension headers between the main header and AH require careful handling:
The Mutable Field Algorithm:
During ICV computation, the algorithm is:
1. Create a copy of the IP header
2. For each mutable field:
- If predictable: set to predicted final value
- If unpredictable: set to zero
3. Compute HMAC over modified header + AH header (ICV zeroed) + payload
4. Insert resulting ICV into AH header
Receivers follow the identical process, comparing their computed ICV to the received one.
If AH authentication consistently fails, capture packets at sender and receiver. Compare the IP headers—if mutable fields are being handled differently (different DSCP expectations, TTL not zeroed properly), the ICVs won't match. Most failures trace to asymmetric mutable field handling.
The ICV computation follows a precise sequence that ensures consistent results at sender and receiver. Let's trace through this process step by step for an IPv4 packet in transport mode.
Step 1: Prepare the IP Header
The sender creates a modified copy of the IP header for ICV computation:
123456789101112131415161718192021
function prepareIPv4HeaderForICV(originalHeader) { // Create working copy header = copy(originalHeader) // Zero mutable fields header.DSCP = 0x00 // TOS byte, DSCP portion header.ECN = 0b00 // TOS byte, ECN portion header.flags.MF = 0 // More Fragments flag header.flags.reserved = 0 // Reserved flag header.fragmentOffset = 0 // Fragment offset header.TTL = 0 // Time to Live header.headerChecksum = 0 // IP checksum // Keep immutable fields as-is // - Version, IHL, Total Length, Identification // - DF flag (Don't Fragment) // - Protocol (51 for AH) // - Source Address, Destination Address return header}Step 2: Prepare the AH Header
The AH header is constructed with the ICV field set to zero:
12345678910111213
function prepareAHHeaderForICV(sa, sequenceNumber, payloadProtocol) { ah = new AHHeader() // Set required fields ah.nextHeader = payloadProtocol // e.g., 6 for TCP ah.payloadLength = calculatePayloadLength(sa.icvSize) ah.reserved = 0x0000 // Always zero ah.spi = sa.spi // From Security Association ah.sequenceNumber = sequenceNumber ah.icv = zeros(sa.icvSize) // CRITICAL: ICV is zeroed! return ah}Step 3: Assemble and Compute
The complete input to HMAC is assembled and the ICV computed:
123456789101112131415161718192021222324
function computeICV(sa, ipHeader, ahHeader, payload) { // Prepare inputs preparedIP = prepareIPv4HeaderForICV(ipHeader) preparedAH = ahHeader // Already has ICV zeroed // Concatenate all protected data icvInput = concatenate( preparedIP.toBytes(), // Modified IP header preparedAH.toBytes(), // AH header with zeroed ICV payload // Upper layer data (TCP, etc.) ) // Compute HMAC using SA parameters fullHMAC = HMAC( algorithm = sa.authAlgorithm, // e.g., SHA-256 key = sa.authKey, message = icvInput ) // Truncate to specified ICV length icv = truncate(fullHMAC, sa.icvSize) // e.g., 128 bits for HMAC-SHA-256-128 return icv}Step 4: Transmit
The packet is assembled with the computed ICV and transmitted:
Note that the transmitted IP header contains the original values—not the zeroed values used for computation. The receiver will perform the same zeroing process before validating.
Computation Complexity:
For a 1500-byte packet with HMAC-SHA-256:
On modern CPUs with cryptographic extensions, HMAC-SHA-256 can process several gigabytes per second per core. The computational overhead of AH is rarely a bottleneck for typical network traffic. However, for very high-speed links (40Gbps+), dedicated cryptographic offload hardware may be necessary.
When an AH-protected packet arrives, the receiver must validate its integrity before processing. This validation is the mirror image of computation, with additional security checks.
Validation Sequence:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
function validateAHPacket(packet) { // Step 1: Parse packet structure ipHeader = parseIPHeader(packet) ahHeader = parseAHHeader(packet, ipHeader) payload = extractPayload(packet, ahHeader) // Step 2: Lookup Security Association sa = lookupSA( destinationIP = ipHeader.destinationAddress, protocol = 51, // AH spi = ahHeader.spi ) if (sa == null) { log("No SA found for SPI: " + ahHeader.spi) return REJECT_NO_SA } // Step 3: Check sequence number (anti-replay) if (!isWithinReplayWindow(sa, ahHeader.sequenceNumber)) { log("Sequence number outside window: " + ahHeader.sequenceNumber) return REJECT_REPLAY } if (wasAlreadyReceived(sa, ahHeader.sequenceNumber)) { log("Duplicate sequence number: " + ahHeader.sequenceNumber) return REJECT_REPLAY } // Step 4: Extract received ICV and zero the field receivedICV = ahHeader.icv ahHeader.icv = zeros(sa.icvSize) // Step 5: Prepare IP header (zero mutable fields) preparedIP = prepareIPHeaderForICV(ipHeader) // Step 6: Compute expected ICV expectedICV = computeICV(sa, preparedIP, ahHeader, payload) // Step 7: Constant-time comparison (prevents timing attacks) if (!constantTimeEquals(receivedICV, expectedICV)) { log("ICV mismatch!") return REJECT_AUTH_FAILED } // Step 8: Update replay window markAsReceived(sa, ahHeader.sequenceNumber) // Step 9: Pass to next layer return ACCEPT(payload, ahHeader.nextHeader)}Critical Validation Points:
1. SA Lookup
The triplet (destination IP, protocol 51, SPI) must map to an existing SA. If no SA exists:
2. Anti-Replay Check
Before expensive cryptographic verification, check if the sequence number is:
This check is computationally cheap and prevents replay-based denial-of-service.
3. Constant-Time Comparison
The ICV comparison MUST be constant-time—taking the same amount of time regardless of where mismatches occur. Variable-time comparison enables timing attacks where attackers measure response times to deduce correct ICV bytes.
// WRONG: Variable-time comparison
for (i = 0; i < length; i++) {
if (received[i] != expected[i]) return false; // Early exit leaks info!
}
// CORRECT: Constant-time comparison
result = 0;
for (i = 0; i < length; i++) {
result |= received[i] ^ expected[i]; // No early exit
}
return result == 0;
4. Replay Window Update
Only after ICV validation succeeds should the sequence number be marked as received. This prevents attackers from corrupting the window by sending invalid packets with specific sequence numbers.
The validation order—SA lookup, anti-replay check, ICV verification, then window update—is security-critical. Verifying ICV before anti-replay allows replay-based DoS. Updating window before ICV allows window corruption. Follow the RFC 4302 specified order exactly.
IP fragmentation presents a significant challenge for AH integrity protection. When a packet is fragmented, the ICV computed over the original packet becomes invalid for individual fragments.
The Fragmentation Problem:
Consider a 3000-byte packet protected with AH, traversing a path with 1500-byte MTU:
IPv4 Fragmentation Handling:
RFC 4302 specifies two approaches:
Approach 1: Transport Mode
Approach 2: Tunnel Mode
Best Practice: Avoid Fragmentation
The recommended approach is to prevent fragmentation entirely:
IPv6 Advantage:
IPv6 does not perform router fragmentation—only the source host can fragment. This simplifies AH processing:
Fragment Authentication Attacks:
Without per-fragment authentication, attackers can:
These attacks are mitigated by fragment reassembly timeouts and anomaly detection, but avoiding fragmentation remains the robust solution.
For tunnel mode AH with HMAC-SHA-256-128: Effective MTU = Physical MTU - 20 (outer IPv4) - 24-32 (AH with ICV) - 20 (inner IPv4). For 1500-byte Ethernet, effective payload MTU is roughly 1424-1436 bytes.
When AH authentication fails, packets are silently discarded (with possible logging). Understanding common failure causes enables faster troubleshooting.
Failure Category 1: Configuration Mismatches
| Issue | Symptom | Diagnosis | Resolution |
|---|---|---|---|
| Key mismatch | All packets fail | Verify key on both ends | Regenerate/redistribute keys via IKE |
| Algorithm mismatch | All packets fail | Check SA algorithm negotiation | Align allowed algorithms in IKE policy |
| SPI conflict | Intermittent failures | Multiple SAs using same SPI | Ensure unique SPIs per destination/protocol |
| SA expired | Sudden failure after working | Check SA lifetime | Verify IKE rekeyling is functioning |
Failure Category 2: Network Issues
| Issue | Symptom | Diagnosis | Resolution |
|---|---|---|---|
| Packet corruption | Random failures | Compare raw bytes at sender/receiver | Check physical layer, switch ports |
| NAT modification | Consistent failure through specific path | Trace packet headers through NAT | Use ESP with NAT-T instead of AH |
| DSCP remarking | Failures after QoS boundary | Check if DSCP handling differs | Ensure both ends zero DSCP same way |
| Middlebox modification | Failures through specific device | Inspect packets before/after device | Configure middlebox to pass AH unchanged |
Failure Category 3: Implementation Bugs
| Issue | Symptom | Diagnosis | Resolution |
|---|---|---|---|
| Mutable field handling | Fails between different vendors | Compare mutable field treatment | Update to compliant implementation |
| Byte order issues | All inter-platform comms fail | Check endianness handling | Verify RFC-compliant byte ordering |
| Padding calculation | Fails with specific ICV sizes | Check header alignment | Verify Payload Length calculation |
| Extension header handling | IPv6 failures only | Check extension header ICV inclusion | Ensure RFC 4302 compliance for IPv6 |
Diagnostic Approach:
1. Enable IPSec debugging on both endpoints
2. Capture traffic with tcpdump/Wireshark at both ends
3. Compare captures:
- Are IP headers identical except mutable fields?
- Are AH headers identical?
- Is payload byte-for-byte identical?
4. Manually compute ICV on captured packet
5. Compare manual computation with received ICV
6. Identify the divergence point
Using Wireshark for Diagnosis:
Wireshark can decode AH headers but cannot validate ICVs (it doesn't have the secret key). However, it can reveal:
AH is fundamentally incompatible with NAT because NAT modifies IP addresses (and ports for NAPT)—fields that AH authenticates. If packets must traverse NAT, use ESP with NAT-Traversal (NAT-T) instead. No amount of configuration can make AH work through NAT.
IPSec implementations maintain two databases that govern AH processing: the Security Policy Database (SPD) and the Security Association Database (SAD). Understanding their interaction clarifies the complete processing flow.
Outbound Processing (Sending):
SPD Outbound Lookup:
The SPD matches packets against policies based on:
Matching policy specifies:
Inbound Processing (Receiving):
Post-Validation SPD Check:
After successful AH validation, the SPD is consulted again to verify:
This prevents "downgrade" attacks where an attacker sends unprotected traffic that should be protected.
Processing Priority:
IPSec processing must occur at the correct point in the IP stack:
For tunnel mode, this effectively happens twice (inner and outer processing).
The SPD defines security policy (what should happen). The SAD contains operational state (parameters for how it happens). SPD entries reference or trigger creation of SAD entries. This separation allows policy-driven security with dynamic SA management.
We've thoroughly examined how AH achieves its integrity protection guarantee. Let's consolidate the essential concepts:
What's Next:
With integrity protection understood, we move to the anti-replay mechanism—how sequence numbers and sliding windows prevent attackers from capturing and retransmitting valid packets. This protection is crucial for preventing replay-based attacks on authenticated sessions.
You now understand the cryptographic foundations of AH integrity protection, how mutable fields are handled, the complete ICV computation and validation process, and common failure causes and diagnostics. This knowledge enables effective implementation, configuration, and troubleshooting of AH-based security.