1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100

content / browser / aggregation_service / payload_encryption.md [blame]

# 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.