Conjuntos de claves

Tink usa conjuntos de claves para habilitar la rotación de claves. De manera formal, un conjunto de claves es una lista1 de claves no vacía en la que una clave se designa como primaria (la que se usa, por ejemplo, para firmar y encriptar nuevos textos sin formato). Además, las claves en un conjunto de claves obtienen un ID único2 y un estado de clave que permite inhabilitar claves sin quitarlas de un conjunto.

Los conjuntos de claves son la forma principal en la que los usuarios pueden acceder a las claves (a través de la clase KeysetHandle). Esto garantiza que cada usuario tenga código para manejar varias claves a la vez. Para la mayoría de los usuarios de criptografía, manejar varias claves es necesario: debe ser posible cambiar las claves (por ejemplo, las claves antiguas pueden filtrarse), y casi nunca hay un "cambio a la clave siguiente" atómico que se pueda aplicar a las máquinas que ejecuta el código y a todos los textos cifrados, a nivel global y en un instante. Por lo tanto, el usuario debe escribir un código que funcione cuando una clave cambia de una clave a la siguiente.

Ejemplo: AEAD

Considera un conjunto de claves AEAD, que contiene varias claves para la primitiva AEAD. Como se explicó anteriormente, cada clave especifica de forma única dos funciones: \(\mathrm{Enc}\) y \(\mathrm{Dec}\). El conjunto de claves ahora también especifica dos funciones nuevas: \(\mathrm{Enc}\) y \(\mathrm{Dec}\) - \(\mathrm{Enc}\) simplemente es igual a la función \(\mathrm{Enc}\) de la clave primaria del conjunto de claves, mientras que la función \(\mathrm{Dec}\) intenta desencriptarlas con todas las claves y las analiza en algún orden (consulta a continuación cómo Tink mejora el rendimiento de esto).

Es interesante señalar que los conjuntos de claves son claves completas: son una descripción completa de las funciones \(\mathrm{Enc}\) y\(\mathrm{Dec}\) usadas. Esto significa que los usuarios pueden escribir una clase que tome como entrada un KeysetHandle, lo que expresa la idea de que la clase necesita una descripción completa de los objetos \(\mathrm{Enc}\) y \(\mathrm{Dec}\) para funcionar correctamente. Esto permite que el usuario escriba las APIs que comunican lo siguiente: para usar esta clase, debes proporcionarme la descripción de una primitiva criptográfica.

Rotación de claves

Considera a un usuario de Tink que escribe un programa que primero obtiene un conjunto de claves de un KMS, crea un objeto AEAD a partir de este conjunto de claves y, por último, usa este objeto para encriptar y desencriptar cifrados.

Este usuario se prepara automáticamente para la rotación de claves y el cambio de algoritmos en caso de que su elección actual ya no cumpla con el estándar.

Sin embargo, hay que tener algo de cuidado cuando se implementa esa rotación de claves: primero, el KMS debe agregar una clave nueva al conjunto de claves (pero no establecerla como primaria aún). Luego, el nuevo conjunto de claves debe implementarse en todos los objetos binarios, de modo que todos los que usen este conjunto de claves tengan la clave más reciente en el conjunto. Solo entonces la nueva clave debe convertirse en primaria, y el conjunto de claves resultante se vuelve a distribuir a todos los objetos binarios que usan el conjunto de claves.

Identificadores clave en textos cifrados

Considera nuevamente el ejemplo de un conjunto de claves AEAD. Si se realiza de forma simple, para desencriptar un texto cifrado, Tink debe intentar desencriptarlo con todas las claves del conjunto de claves, ya que no hay forma de saber qué clave se usó para encriptarlo. Esto puede causar una gran sobrecarga de rendimiento.

Por esta razón, Tink permite agregar prefijos a los textos cifrados con una string de 5 bytes derivada del ID. Siguiendo la filosofía de “Claves completas” que se describió anteriormente, este prefijo es parte de la clave y todos los textos cifrados derivados con esta clave deben tenerlo. Cuando los usuarios crean claves, pueden elegir si la clave debe usar ese prefijo o si debe usarse un formato de texto cifrado sin él.

Cuando una clave está en un conjunto de claves, Tink calcula esta etiqueta a partir del ID que tiene la clave en ese conjunto. El hecho de que los IDs sean únicos2 dentro de un conjunto de claves implica que las etiquetas son únicas. Por lo tanto, si solo se usan claves etiquetadas, no hay pérdida de rendimiento en comparación con la desencriptación con una sola clave: Tink solo necesita probar una de las claves durante la desencriptación.

Sin embargo, dado que la etiqueta es parte de la clave, esto también implica que la clave solo puede estar en un conjunto de claves si tiene un ID específico. Esto tiene algunas implicaciones a la hora de describir la implementación de objetos clave en diferentes lenguajes.


  1. Algunas partes de Tink todavía tratan a Keysets como un conjunto. Sin embargo, esto debe cambiarse. Esto se debe a que el orden es en general importante: por ejemplo, considera el ciclo de vida típico de una rotación de claves con Aead. Primero, se agrega una clave nueva a un conjunto de claves. Esta clave aún no se estableció como primaria, sino que está activa. Este nuevo conjunto de claves se implementa en todos los objetos binarios. Una vez que todos los objetos binarios conocen la clave nueva, esta se convierte en la principal (solo en este punto es seguro usar la clave). En este segundo paso, la rotación de claves debe conocer la última clave que se agregó. 

  2. Para la compatibilidad con una biblioteca interna de Google, Tink permite tener conjuntos de claves en los que se repiten los IDs. Esta compatibilidad se quitará en el futuro.