Aggregation service payload encryption details

Here, we briefly describe the precise details of payload encryption for aggregatable reports used in the aggregation service. The format of the payload itself is provided in the explainer[^1], but some of these details aren’t. Note that these details are subject to change, but reflect the current API offered by aggregation_service/.

Implementation links

The generic function used to encrypt data, including specifying various parameter choices (e.g. AEAD function), is EncryptWithHpke() and is defined in this file. The inputs to that function are provided in the CreateFromRequestAndPublicKeys() function in the same file.

Inputs

Plaintext

The unencrypted payload is first generated as a CBOR map with the format described in the explainer[^1]. This map is serialized to binary and used as the plaintext input.

Associated data

The associated data is a string encoded as UTF-8. It consists of a prefix and a variable body. The prefix is a constant and is used for domain separation[^2]; its value is “aggregation_service”. (In an earlier version, this prefix also included a null character at the end.) The body is exactly the value of the shared_info string provided in the report plaintext. This is generated by the browser and encodes information both needed by the aggregation service and available for use by the reporting origin.

An example shared_info field for use in the Attribution Reporting API is:

"shared_info": "{\"attribution_destination\":\"https://advertiser.example\",\"report_id\":\"[UUID]\",\"reporting_origin\":\"https://reporter.example\",\"scheduled_report_time\":\"[timestamp in seconds]\",\"source_registration_time\":\"[timestamp in seconds]\",\"version\":\"[api version]\"}"

The corresponding associated data would then be the following (encoded as UTF-8):

aggregation_service{"attribution_destination":"https://advertiser.example","report_id":"[UUID]","reporting_origin":"https://reporter.example","scheduled_report_time":"[timestamp in seconds]","source_registration_time":"[timestamp in seconds]","version":"[api version]"}"

Note that, for the decryption to succeed, the associated data used must be byte-for-byte identical to what was used for encryption.

Public key

The public key is a 32-byte (256-bit) bytestring. The browser downloads public keys from the aggregation service according to the process described in the explainer[^1] and picks one uniformly at random (if multiple are specified). Note that this key must be base64 decoded by the client. The browser provides the matching id of the key used to encrypt the payload as key_id.

Encryption process and parameters

The encryption context is first set up (using EVP_HPKE_CTX_setup_sender()) with the following algorithms used for each encryption primitive:

  • Key encapsulation mechanism (KEM): DHKEM(X25519, HKDF-SHA256) (i.e. EVP_hpke_x25519_hkdf_sha256())
  • Key derivation function (KDF): HKDF-SHA256 (i.e. EVP_hpke_hkdf_sha256())
  • Authenticated encryption with additional data (AEAD) encryption function: ChaCha20Poly1305 (i.e. EVP_hpke_chacha20_poly1305())

The public key is provided in this call. The associated data is also provided while setting up this encryption context, i.e. as the info and info_len parameters.[^3] Setting up the encryption context generates an encapsulated shared secret, which should have length 32 bytes.

Then, the plaintext is symmetrically encrypted as a single message with this context (using EVP_HPKE_CTX_seal()[^4]). No associated data is provided here, i.e. ad_len is 0.

The ciphertext, i.e. encrypted payload, returned is a single bytestring consisting of the encapsulated shared string concatenated with the symmetrically encrypted message. This bytestring is then base64 encoded before inclusion in the report.

Notes

[^1]: Note that these links point to a specific commit of the explainer that reflects what is currently implemented as of the latest update to this file. The up-to-date explainer may have recent changes that have not yet been implemented.

[^2]: This ensures that ciphertexts for one API cannot be accepted for a different API, even if an encryption key is reused.

[^3]: To decrypt, this data should therefore be provided in EVP_HPKE_CTX_setup_receipient().

[^4]: The equivalent decryption call is EVP_HPKE_CTX_open(). As during encryption, no associated data should be provided.