In many libraries, keys are identified by byte sequences (e.g. consider OpenSSL
functions such as `EVP_EncryptInit_ex`

, or javax.crypto methods such as
"Cipher.init" taking both a key and a parameter spec). Tink aims to be
different, and expects a key to always consist of the key material itself plus
the metadata -- we call this a "full key".

A full AEAD key for example specifies in exact detail how a plaintext is encrypted, how the ciphertext is encoded (e.g.. initialization vector followed by the encryption followed by the tag), and how decryption exactly works, i.e., it specifies two functions \(\mathrm{Enc}\) and \(\mathrm{Dec}\) for encryption and decryption.

This is in particular different from a full AES key, which describes a permutation on 128, 192, or 256 bits. Hence, a full AES key and a full AES-GCM key are very different objects in Tink -- in many cryptographic libraries, both would just be bit strings of length 128, 192, or 256 bits.

## Formal definitions

We now return and use the language of the formal definitions introduced when we discussed primitives and interfaces

Our goal is to define a full key formally. In order to do this, we will need to ensure that the key always specifies the complete cryptographic object.

This is a bit more subtle than it appears at first. Intuitively, a full key
'fully specifies the cryptographic operation', and using a byte sequence of
length
16 for an AES GCM key does not do that. However, this intuition is not quite
formally correct: such a byte sequence *does* fully specify the operation if one
has the additional context that the only cryptographic function in existence is
AES GCM. Because of this, the definition of 'full key' is with respect to a
set of primitives.

Tink aims to have full keys with respect to *all* primitives which will ever be
implemented in the future -- i.e., Tink wants that the key spaces of all
primitives is also disjoint. For example, a Tink AEAD key should select a unique
pair of functions

\[\mathrm{Enc} : {\bf R} \times {\bf I}_{\mathrm{Enc}} \to {\bf O}_{\mathrm{Enc}} \\ \mathrm{Dec} : {\bf R} \times {\bf I}_{\mathrm{Dec}} \to {\bf O}_{\mathrm{Dec}}\]

to which the interfaces above then gives access to -- and only this pair of functions.

This may seem like a rather obvious principle, but it seems to be important enough to be specified explicitly. Furthermore, it suggests that one should annotate each key type with a description of these functions when we add them -- work which unfortunately was not always done in Tink.