Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 1 | # Aggregation service payload encryption details |
| 2 | |
| 3 | Here, we briefly describe the precise details of payload encryption for |
| 4 | aggregatable reports used in the aggregation service. The format of the payload |
| 5 | itself is provided in the |
Andrew Paseltiner | a5e25ee | 2022-06-02 16:13:26 | [diff] [blame] | 6 | [explainer](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1], |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 7 | but some of these details aren’t. Note that these details are subject to change, |
| 8 | but reflect the current API offered by `aggregation_service/`. |
| 9 | |
| 10 | ## Implementation links |
| 11 | |
| 12 | The generic function used to encrypt data, including specifying various |
| 13 | parameter choices (e.g. AEAD function), is `EncryptWithHpke()` and is defined in |
| 14 | [this file](./aggregatable_report.cc). The inputs to that function are provided |
| 15 | in the `CreateFromRequestAndPublicKeys()` function in the same file. |
| 16 | |
| 17 | ## Inputs |
| 18 | |
| 19 | ### Plaintext |
| 20 | |
| 21 | The unencrypted payload is first generated as a [CBOR](https://cbor.io/) map |
| 22 | with the format described in the |
Alex Turner | e9dfb8b | 2024-07-08 19:55:21 | [diff] [blame] | 23 | [spec](https://patcg-individual-drafts.github.io/private-aggregation-api/pr-preview/refs/pull/128/merge/index.html#obtain-the-plaintext-payload)[^1]. |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 24 | This map is serialized to binary and used as the plaintext input. |
| 25 | |
| 26 | ### Associated data |
| 27 | |
| 28 | The associated data is a string encoded as UTF-8. It consists of a prefix and a |
| 29 | variable body. The prefix is a constant and is used for domain separation[^2]; |
Alex Turner | 57a65e03 | 2022-06-08 20:57:37 | [diff] [blame] | 30 | its value is "`aggregation_service`". (In an earlier version, this prefix also |
| 31 | included a null character at the end.) The body is exactly the value of the |
| 32 | `shared_info` string provided in the report plaintext. This is generated by the |
| 33 | browser and encodes information both needed by the aggregation service and |
| 34 | available for use by the reporting origin. |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 35 | |
Charlie Harrison | ef5308d | 2022-06-07 15:41:42 | [diff] [blame] | 36 | An example shared\_info field for use in the Attribution Reporting API is: |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 37 | |
| 38 | ```jsonc |
Charlie Harrison | ef5308d | 2022-06-07 15:41:42 | [diff] [blame] | 39 | "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]\"}" |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 40 | ``` |
| 41 | |
Alex Turner | 57a65e03 | 2022-06-08 20:57:37 | [diff] [blame] | 42 | The corresponding associated data would then be the following (encoded as |
| 43 | UTF-8): |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 44 | |
| 45 | ```jsonc |
Alex Turner | 57a65e03 | 2022-06-08 20:57:37 | [diff] [blame] | 46 | 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]"}" |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 47 | ``` |
| 48 | |
| 49 | Note that, for the decryption to succeed, the associated data used must be |
| 50 | byte-for-byte identical to what was used for encryption. |
| 51 | |
| 52 | ### Public key |
| 53 | |
| 54 | The public key is a 32-byte (256-bit) bytestring. The browser downloads public |
| 55 | keys from the aggregation service according to the process described in the |
Andrew Paseltiner | a5e25ee | 2022-06-02 16:13:26 | [diff] [blame] | 56 | [explainer](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1] |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 57 | and picks one uniformly at random (if multiple are specified). Note that this |
| 58 | key must be base64 decoded by the client. The browser provides the matching `id` |
| 59 | of the key used to encrypt the payload as `key_id`. |
| 60 | |
| 61 | ## Encryption process and parameters |
| 62 | |
| 63 | The encryption context is first set up (using `EVP_HPKE_CTX_setup_sender()`) |
| 64 | with the following algorithms used for each encryption primitive: |
| 65 | |
| 66 | * Key encapsulation mechanism (KEM): DHKEM(X25519, HKDF-SHA256) (i.e. |
| 67 | `EVP_hpke_x25519_hkdf_sha256()`) |
| 68 | * Key derivation function (KDF): HKDF-SHA256 (i.e. `EVP_hpke_hkdf_sha256()`) |
| 69 | * Authenticated encryption with additional data (AEAD) encryption function: |
| 70 | ChaCha20Poly1305 (i.e. `EVP_hpke_chacha20_poly1305()`) |
| 71 | |
| 72 | The public key is provided in this call. The associated data is also provided |
| 73 | while setting up this encryption context, i.e. as the `info` and `info_len` |
| 74 | parameters.[^3] Setting up the encryption context generates an encapsulated |
| 75 | shared secret, which should have length 32 bytes. |
| 76 | |
| 77 | Then, the plaintext is symmetrically encrypted as a single message with this |
| 78 | context (using `EVP_HPKE_CTX_seal()`[^4]). No associated data is provided here, |
| 79 | i.e. `ad_len` is 0. |
| 80 | |
| 81 | The ciphertext, i.e. encrypted payload, returned is a single bytestring |
| 82 | consisting of the encapsulated shared string concatenated with the symmetrically |
| 83 | encrypted message. This bytestring is then base64 encoded before inclusion in |
| 84 | the report. |
| 85 | |
| 86 | ## Notes |
| 87 | |
Alex Turner | e9dfb8b | 2024-07-08 19:55:21 | [diff] [blame] | 88 | [^1]: Note that these links point to a specific commit of the spec that reflects |
| 89 | what is currently implemented as of the latest update to this file. The |
| 90 | [up-to-date spec](https://patcg-individual-drafts.github.io/private-aggregation-api/#obtain-the-plaintext-payload) |
Alex Turner | 26a95e1 | 2022-03-24 17:04:00 | [diff] [blame] | 91 | may have recent changes that have not yet been implemented. |
| 92 | |
| 93 | [^2]: This ensures that ciphertexts for one API cannot be accepted for a |
| 94 | different API, even if an encryption key is reused. |
| 95 | |
| 96 | [^3]: To decrypt, this data should therefore be provided in |
| 97 | `EVP_HPKE_CTX_setup_receipient()`. |
| 98 | |
| 99 | [^4]: The equivalent decryption call is `EVP_HPKE_CTX_open()`. As during |
| 100 | encryption, no associated data should be provided. |