This page describes Tink's wire format for keys and primitive output. The
documentation is aimed at cryptographers who want to add additional languages to
Tink and maintainers of other high-level crypto libraries who want a wire
compatible mode. It is not intended for general audiences.

## Keyset serialization

Tink uses [Google protobuf](https://protobuf.dev/programming-guides/encoding/) to serialize its keysets.

- A binary serialized keyset is a serialized Keyset proto defined in [tink.proto](https://github.com/tink-crypto/tink-java/blob/main/proto/tink.proto). The KeyData value property of a key is a serialized proto of the corresponding key type.
- A JSON serialized keyset is a Keyset proto serialized in [JSON](https://datatracker.ietf.org/doc/html/rfc8259) format. Note that the KeyData value is still a *binary* serialized proto.
- An encrypted keyset is a serialized EncryptedKeyset proto defined in [tink.proto](https://github.com/tink-crypto/tink-java/blob/main/proto/tink.proto). It contains an encrypted binary serialized keyset and optionally some unencrypted KeysetInfo metadata.

## Tink Output Prefix

Most Tink primitives support a 5 byte output prefix consisting of:

- 1 byte version: `0x01`
- 4 bytes key hint: This is the key ID of the key used.

Some legacy keys may also support the version byte `0x00`.

The [Prehash](https://developers.google.com/tink/prehash-signing) primitive
uses the version byte `0xff`, see [External Mu ML-DSA prehash values](https://developers.google.com/tink/wire-format#prehash).

Note that these prefixes are not authenticated and cannot be relied on for
security purposes. Tink uses them as a hint to speed up decryption or
verification, or to pick the signing key in a sign prehash scenario.

### AEAD

In general, Tink formats AEAD ciphertexts as:

    prefix || IV || ciphertext || tag

unless otherwise specified in the corresponding RFC. `prefix` is either empty
or a 5 byte Tink output prefix.

#### AES-CTR-HMAC

For AES-CTR-HMAC, Tink computes the MAC with associated data (AD) as follows:

    AD || IV || ciphertext || bitlen(AD)

where `bitlen(AD)` is AD's length in bits represented as 64-bit big-endian
unsigned integer. This HMAC scheme follows the [draft for AES-CBC-HMAC from
Mcgrew](https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05).

### Deterministic AEAD

Tink implements [RFC 5297](https://www.rfc-editor.org/rfc/rfc5297) for AES-SIV, putting the synthetic
initialization vector (SIV) at the beginning of the ciphertext.
The primitive may add a 5 byte Tink output prefix.

While RFC 5297 supports a list of associated datas, Tink only supports exactly
one associated data, which corresponds to a list with one element in RFC 5297.
An empty associated data is a list with one empty element, and not an empty
list.

### Streaming AEAD

See [AES-CTR HMAC](https://developers.google.com/tink/streaming-aead/aes_ctr_hmac_streaming) and
[AES-GCM-HKDF](https://developers.google.com/tink/streaming-aead/aes_gcm_hkdf_streaming).

### Envelope encryption

Envelope encryption encrypts the data with a data encryption key `DEK` using
Tink's AEAD primitives. The encryption works as follows:

- A new `DEK` is generated, using a given key template (or key parameters).
- The `DEK` is serialized into a byte string. The serialization format the protocol buffer serialization of the key type proto. For example, this is a serialized `AesGcmKey` protocol buffer message defined in [aes_gcm.proto](https://github.com/tink-crypto/tink-java/blob/main/proto/aes_gcm.proto) for DEK of key type AES GCM. See [protocol buffer serialization](https://protobuf.dev/programming-guides/encoding/) for how to serialize a protocol buffer.
- The serialized `DEK` is encrypted by an external provider (for example, GCP), into an `encrypted DEK`.
- The `DEK` is used to encrypt the plaintext with the associated data into `ciphertext`. So `ciphertext` has the exact same format as the AEAD primitive corresponding to the `DEK`.

The output format of envelope encryption is as follows:

    encrypted DEK length || encrypted DEK || ciphertext

The `encrypted DEK length` is 4 bytes, storing the length of the `encrypted DEK`
as a 32-bit big-endian integer.

### MAC

Tink follows the corresponding RFCs. Primitives may add a 5 byte Tink output
prefix to the tag.

### PRF set

Tink follows the corresponding RFCs. Note that for PRF Set the key type differs
from the MAC key type of the same algorithm by not including the output length.
PRF Set keys never add a Tink output prefix. This ensures the output is actually
a PRF.

### Hybrid encryption

The general wire format for Tink hybrid encryption is the following:

    prefix || encapsulated_key || encrypted_data

`prefix` is either empty or a 5 byte Tink output prefix. Each key type contains
the information on how many bytes to parse, and how to parse those bytes from
`encapsulated_key`.

#### HPKE (Hybrid Public Key Encryption)

Tink follows the HPKE standard defined in [RFC 9180](https://www.rfc-editor.org/rfc/rfc9180.html).
An HPKE ciphersuite includes the following three primitives.

- Key encapsulation mechanism (KEM)
- Key derivation function (KDF)
- Authenticated encryption with associated data (AEAD)

The HPKE standard does not define a general wire format in [RFC 9180, Section
10](https://www.rfc-editor.org/rfc/rfc9180.html#name-message-encoding). Tink's HPKE implementation uses the following
`encapsulated_key` and `encrypted_data` values.

- `encapsulated_key`
  - Sender's serialized public key
  - Defined as `enc` in [RFC 9180, Section 4.1](https://www.rfc-editor.org/rfc/rfc9180.html#name-dh-based-kem-dhkem)
  - Format determined by the specific HPKE KEM used
- `encrypted_data`
  - Ciphertext and tag (i.e., `ciphertext || tag` without the IV)
  - Defined as `ct` in [RFC 9180, Section 4](https://www.rfc-editor.org/rfc/rfc9180.html#name-cryptographic-dependencies)
  - Format determined by the specific HPKE AEAD used

##### X25519 Diffie-Hellman-based KEM

For X25519 DHKEMs, the value `enc` is the sender's 32-byte Diffie-Hellman public
key.

#### ECIES-AEAD-HKDF

For Tink's ECIES-AEAD-HKDF implementation, `encapsulated_key` is the output
of the Key Encapsulation Mechanism (KEM) and `encrypted_data` is the output
of the Data Encapsulation Mechanism (DEM).

##### KEM

Depending on the key type, Tink uses compressed and uncompressed elliptic curve
points, following [RFC 8422](https://www.rfc-editor.org/rfc/rfc8842)/`ANSI.X9-62.2005` encoding standards. For
uncompressed points, the byte `0x04` is followed by the `x` and the `y`
coordinate as fixed size integers. For compressed coordinates, the byte `0x02`
or `0x03` and the `x` coordinate as a fixed size integer is used. For `X25519`,
the [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748) definition is used (`x` coordinate as fixed size integer).

##### DEM

For `encrypted_data`, Tink uses the same format as the AEAD. This includes
specifying an IV.

##### Key derivation

First the x coordinate `x_ss` of the shared point is computed. The key for the
AEAD is then set to:

    HKDF(ikm = encapsulated_key || x_ss, salt = salt_of_key, info = context_info, length = dem_key_size)

where `encapsulated_key` is the full KEM output as bytes.

### Digital signatures

Tink follows the corresponding RFCs. Primitives may add a 5 byte Tink output
prefix to the tag that is generated.

#### ECDSA

Depending on the [EcdsaSignatureEncoding field](https://github.com/tink-crypto/tink-java/blob/main/proto/ecdsa.proto) in the in the key,
the format of a ECDSA signature is either `IEEE P1363` or `ASN.1 DER`.

The `IEEE P1363` signature's format is `r || s`, where `r` and `s` are
zero-padded and have the same size in bytes as the order of the curve. For
example, for `NIST P-256` curve, `r` and `s` are zero-padded to 32 bytes.

The DER signature is encoded using [`ASN.1`](https://tools.ietf.org/html/rfc5480#appendix-A):

    ECDSA-Sig-Value :: = SEQUENCE { r INTEGER, s INTEGER }

In particular, the encoding is:

    0x30 || totalLength || 0x02 || r's length || r || 0x02 || s's length || s

Tink follows the best practices for signature verification, by only accepting
DER encoded ECDSA signatures (alternative BER encoded signatures are invalid).

This helps prevent signature malleability attacks, which often [affect
cryptocurrency systems](https://en.wikipedia.org/wiki/Transaction_malleability_problem).

#### External Mu ML-DSA prehash values

The [Prehash](https://developers.google.com/tink/prehash-signing) primitive turns a message into a *prehash
value* , which the SignPrehash primitive then signs. For ML-DSA in *External Mu*
mode, as described in [RFC 9881](https://www.rfc-editor.org/rfc/rfc9881.html#externalmu), the prehash value is 69 bytes with
the following layout:

    0xff || key_id (4 bytes, big-endian) || mu (64 bytes)

- `mu` is the ML-DSA message representative, computed as `SHAKE256(tr || 0x00 || 0x00 || message, 64)`, where `tr` is the 64-byte hash of the public key, the first `0x00` is the FIPS 204 domain separator for pure ML-DSA, and the second `0x00` is the length of the (empty) context string. Because `tr` is derived from the public key, `mu` is cryptographically bound to that key -- though checking the binding requires the original message.
- The 5-byte prefix is Tink framing, not part of `mu`. It associates the prehash value with one specific key, so that SignPrehash knows which key to sign with and can reject a prehash value it has no key for. The prefix is plain metadata and binds nothing cryptographically: anyone can rewrite it.

Signing rejects any input that is not exactly 69 bytes, that does not start
with `0xff`, or whose key ID does not match an enabled key in its keyset.

> [!NOTE]
> **Note:** **This is not HashML-DSA.** FIPS 204 also defines a pre-hash variant (`HashML-DSA`) that produces signatures that are *not* interchangeable with pure ML-DSA signatures. External Mu is different: it produces exactly the signature that pure ML-DSA would have produced for the same message.