Keys

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.