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 spec[1: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:2]
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
Note that these links point to a specific commit of the spec that reflects what is currently implemented as of the latest update to this file. The up-to-date spec may have recent changes that have not yet been implemented. ↩︎ ↩︎ ↩︎
This ensures that ciphertexts for one API cannot be accepted for a different API, even if an encryption key is reused. ↩︎
To decrypt, this data should therefore be provided in
EVP_HPKE_CTX_setup_receipient(). ↩︎The equivalent decryption call is
EVP_HPKE_CTX_open(). As during encryption, no associated data should be provided. ↩︎