Message Encryption for Web Push
Extracted elements (19)
If the encryption scheme needs to be replaced, a new content coding scheme can be defined. The user agent can expose supported content coding schemes (e.g., via the 'supportedContentEncodings' parameter of the Push API) to manage progressive deployment.
The nonce XOR with record sequence number is omitted because push messages contain only a single record (sequence number zero), so the nonce derivation is simplified accordingly.
Web Push is an intermediated protocol where messages pass through a push service, requiring end-to-end encryption to protect against inspection, modification, and forgery by the push service itself.
A push service is not required to support more than 4096 octets of payload body per RFC 8030. After subtracting the 86-octet header, 1-octet padding delimiter, and 16-octet AEAD tag, at most 3993 octets of plaintext can be delivered.
The 'keyid' parameter containing the ECDH public key (65-octet uncompressed form) will not be valid UTF-8, contrary to the recommendation in RFC 8188. Implementations must handle this binary value correctly.
A push message MUST include the application server ECDH public key in the 'keyid' parameter of the encrypted content coding header, encoded as a 65-octet uncompressed point form (starting with 0x04) per X9.62.
A user agent MUST generate and provide a hard-to-guess sequence of 16 octets for authentication of push messages, and this SHOULD be generated by a cryptographically strong random number generator.
An application MUST use an authenticated, confidentiality-protected communications medium (e.g., HTTPS) when distributing the subscription public key and authentication secret to the application server.
An application server MUST encrypt a push message with a single record, and MUST set the 'rs' parameter in the 'aes128gcm' header to a value greater than the sum of plaintext length, padding delimiter (1 octet), any padding, and authentication tag (16 octets).
An application server MUST NOT use content encodings other than 'aes128gcm' for push messages; in particular, compressing content encodings could leak message contents.
The padding delimiter octet MUST be checked; values other than 0x02 MUST cause the message to be discarded.
The timing and length of communication cannot be hidden from the push service, which can observe which application server communicates with which user agent and on which subscription. Message length is only obscured if padding is used.
For each subscription, the user agent generates a P-256 ECDH key pair and a 16-octet authentication secret. The ECDH public key and authentication secret are provided to the application server as part of the push subscription.
The ECDH shared secret and authentication secret are combined using HKDF (HMAC-SHA-256) with info string 'WebPush: info' || 0x00 || ua_public || as_public (both public keys in uncompressed X9.62 point form), producing 32 octets of IKM.
The encryption scheme uses ECDH on the P-256 curve to establish a shared secret, combined with a symmetric authentication secret, to derive content encryption keys via HKDF with SHA-256.
The encryption summary pseudocode shows the full key derivation chain: ECDH shared secret → PRK_key via HKDF-Extract(auth_secret, ecdh_secret) → IKM via HKDF-Expand → PRK via RFC 8188 HKDF → CEK (16 octets) and NONCE (12 octets).
HTTP header fields are not protected by the content encoding scheme; a user agent MUST consider HTTP header fields to have come from the push service and MUST NOT rely on them for secure processing of push messages.
The user agent and application MUST verify that received ECDH public keys are on the P-256 curve (checking Y is not the point at infinity, coordinates are in valid range, and the point satisfies the curve equation), as failure to validate allows an attacker to extract private keys.
The key derivation info field is constructed as: key_info = 'WebPush: info' (ASCII, not NUL-terminated) || 0x00 || ua_public (65 octets, uncompressed) || as_public (65 octets, uncompressed). Output length L is 32 octets.