| # 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](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^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](./aggregatable_report.cc). 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](https://cbor.io/) map |
| with the format described in the |
| [spec](https://patcg-individual-drafts.github.io/private-aggregation-api/pr-preview/refs/pull/128/merge/index.html#obtain-the-plaintext-payload)[^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: |
| |
| ```jsonc |
| "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): |
| |
| ```jsonc |
| 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](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^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 spec that reflects |
| what is currently implemented as of the latest update to this file. The |
| [up-to-date spec](https://patcg-individual-drafts.github.io/private-aggregation-api/#obtain-the-plaintext-payload) |
| 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. |