Encrypted Content-Encoding for HTTP
Extracted elements (27)
A single fixed cipher suite (AES-128-GCM) is used rather than negotiable ciphers. Cipher agility is achieved by defining a new content-coding scheme for each algorithm, ensuring that only the HTTP Accept-Encoding header field is needed to negotiate encryption.
Record size is a trade-off: smaller records allow faster release of decrypted data (better for streaming and random access) but increase per-record overhead; larger records (or a single record) reduce overhead and processing cost for non-streaming applications.
The fixed record structure enables range requests (RFC 7233) and random access to encrypted payload bodies at record-size granularity. Partial records at range boundaries cannot be decrypted, so range requests should start and end on record boundaries.
The format was not adapted from existing message-based encryption formats (OpenPGP, CMS, JWE, XML Encryption) because those formats are not suited to stream processing, which is necessary for HTTP. The format instead follows the lower-level constructs of RFC 5116.
The nonce is constructed by XORing an HKDF-derived base nonce with the record sequence number rather than transmitting nonces per-record. This prevents record removal and reordering without adding per-record nonce overhead on the wire.
The 'aes128gcm' content coding is congruent with a sequence of JWE (RFC 7516) compact-serialized objects using { "alg": "dir", "enc": "A128GCM" }. The JWE Protected Header and IV are implied rather than transmitted; the ciphertext and authentication tag are concatenated without base64url encoding or '.' separators.
TLS (RFC 5246) only encrypts the channel between client and server and does not satisfy use cases requiring payload encryption at rest or when replicated across servers. This content coding is complementary to, not a replacement for, transport security.
A decrypter MUST fail if a record contains no non-zero octet (i.e., no padding delimiter is present). A decrypter MUST fail if the last record's padding delimiter is not 0x02, or if any non-last record's padding delimiter is not 0x01.
A partially delivered message MUST NOT be processed as though the entire message was successfully delivered; for instance, it cannot be cached as though it were complete.
If the same input-keying material is reused, the salt parameter MUST be unique each time to ensure the content-encryption key is not reused. An implementation SHOULD generate a random salt for every message.
Recipients that depend on content-origin authentication using this mechanism MUST reject messages that don't include the 'aes128gcm' content coding, because the coding could be automatically removed by an intermediary without the ultimate consumer's knowledge.
The keyid parameter SHOULD be a UTF-8-encoded string (RFC 3629), particularly where the identifier might need to be rendered in a textual form.
The same salt parameter value MUST NOT be reused for two different payload bodies that have the same input-keying material. Generating a random salt for every application of the content coding ensures content-encryption key reuse is highly unlikely.
To preserve a 2^-40 probability of IND-CPA security, the total plaintext enciphered with a key derived from the same IKM and salt MUST be less than 2^44.5 blocks of 16 octets (approximately 398 terabytes for record sizes that are multiples of 16 octets).
The 96-bit nonce for each record is derived via HKDF with info 'Content-Encoding: nonce' || 0x00 and L=12, then XORed with the 96-bit record sequence number (starting at zero in network byte order). This construction prevents record removal or reordering.
The 'aes128gcm' HTTP content coding indicates that a payload has been encrypted using AES-128-GCM (AEAD_AES_128_GCM per RFC 5116 Section 5.1) with a 128-bit content-encryption key. The coding uses a fixed set of encryption primitives; cipher agility is achieved by defining new content-coding schemes rather than negotiating within this one.
The content-encryption key (CEK) is derived per-message using HKDF (RFC 5869) with SHA-256. The salt parameter is the HKDF salt, the IKM identified by keyid is the HKDF input-keying material, and the info string is 'Content-Encoding: aes128gcm' followed by a zero octet. The output length L is 16 octets: CEK = HMAC-SHA-256(PRK, cek_info || 0x01).
This document registers the 'aes128gcm' entry in the IANA HTTP Content Coding Registry. Description: AES-GCM encryption with a 128-bit content-encryption key. Reference: RFC 8188.
Automatic decryption by an intermediary can silently strip the 'aes128gcm' content coding before the message reaches the ultimate consumer. Recipients relying on content-origin authentication must explicitly check for the coding's presence and reject messages that lack it.
Header fields (e.g., Content-Type) are not encrypted and can leak information about the payload. Mitigations include omitting or normalizing sensitive headers, always sending Content-Type: application/octet-stream, or encapsulating the full HTTP message as application/http and encrypting it as the outer payload.
Message truncation is a threat: an attacker may exploit willingness to process partial messages to hold a receiver in an intermediate state. Processing of partial messages must not advantage an attacker, and partial messages must not be cached as complete.
Reusing a content-encryption key and nonce in AES-GCM is catastrophic: it exposes both the plaintext and the authentication key, nullifying all protection. A unique salt per message when IKM is reused is the required mitigation.
The size and timing of encrypted messages, as well as HTTP methods and URIs, can leak sensitive information (citing NETFLIX and CLINIC traffic-analysis research). Mitigations include padding, splitting content into separately stored segments, or combining HTTP/2 with TLS to obscure individual message sizes.
This mechanism provides only data-origin authentication, not sender authentication or authorization. Any entity holding the content-encryption key can produce valid ciphertext, including all recipients of the same message. Separate HTTP authentication (RFC 7235) is still required to authenticate the message creator.
When an HTTP PUT request is accepted without decrypting the payload, an unauthenticated third party can poison the stored content or deny service. HTTP authentication (RFC 7235) must be applied independently of this content coding.
Each plaintext record is padded with a single delimiter octet (0x01 for all records except the last, 0x02 for the last), followed by zero or more 0x00 octets, to fill up to rs-16 octets before AES-GCM encryption. AEAD_AES_128_GCM adds a 16-octet authentication tag, making each full record exactly rs octets.
The encryption content-coding header precedes all encrypted records and contains: salt (16 octets), rs/record-size (4-octet unsigned 32-bit integer in network byte order), idlen (1-octet unsigned integer giving the length of keyid), and keyid (idlen octets). Values of rs smaller than 18 are invalid.