O Tink usa conjuntos de chaves para ativar a rotação de chaves. Formalmente, um conjunto de chaves é uma lista não vazia1 de chaves em que uma é designada como principal (a chave usada, por exemplo, para assinar e criptografar novos textos simples). Além disso, as chaves em um conjunto de chaves recebem um ID exclusivo2 e um status que permite desativar chaves sem removê-las de um conjunto.
Os conjuntos de chaves são a principal maneira de os usuários acessarem chaves (pela classe
KeysetHandle). Isso garante que todos os usuários tenham código para processar várias chaves
ao mesmo tempo. Para a maioria dos usuários de criptografia, o processamento de várias chaves é uma necessidade: é preciso poder mudar as chaves (as antigas podem vazar, por exemplo), e quase nunca há uma "troca para a próxima chave" atômica que possa ser aplicada às máquinas em que o código é executado e a todos os textos criptografados, globalmente e em um instante. Portanto, o usuário precisa escrever um código que
funcione quando uma chave é trocada por outra.
Exemplo: AEAD
Considere um conjunto de chaves AEAD, que contém várias chaves para a primitiva AEAD. Como explicado antes, cada chave especifica de maneira exclusiva duas funções: \(\mathrm{Enc}\) e \(\mathrm{Dec}\). O conjunto de chaves agora também especifica duas novas funções: \(\mathrm{Enc}\) e \(\mathrm{Dec}\) . \(\mathrm{Enc}\) é simplesmente igual à função \(\mathrm{Enc}\) da chave primária do conjunto de chaves, enquanto a função \(\mathrm{Dec}\) tenta descriptografar com todas as chaves, passando por elas em alguma ordem. Consulte abaixo para saber como o Tink melhora o desempenho disso.
É interessante observar que os conjuntos de chaves são chaves completas: eles são uma descrição completa das funções \(\mathrm{Enc}\) e\(\mathrm{Dec}\) usadas. Isso significa que os usuários podem escrever uma classe que recebe um KeysetHandle como entrada, expressando a ideia de que a classe precisa de uma descrição completa de objetos \(\mathrm{Enc}\) e \(\mathrm{Dec}\) para funcionar corretamente. Isso permite que o usuário escreva APIs que comunicam o seguinte: para usar
essa classe, você precisa me fornecer a descrição de uma primitiva criptográfica.
Rotação de chaves
Considere um usuário do Tink, escrevendo um programa que primeiro extrai um conjunto de chaves de um KMS, depois cria um objeto AEAD desse conjunto e, por fim, usa esse objeto para criptografar e descriptografar textos criptografados.
Esse usuário é preparado automaticamente para a rotação de chaves e para a troca de algoritmos caso a escolha atual não atenda mais ao padrão.
No entanto, é preciso ter cuidado ao implementar essa rotação de chaves: Primeiro, o KMS precisa adicionar uma nova chave ao conjunto de chaves, mas ainda não a definir como primária. Em seguida, o novo conjunto de chaves precisa ser lançado para todos os binários, para que cada binário que usa esse conjunto tenha a chave mais recente nele. Só então a nova chave deve ser definida como principal, e o conjunto de chaves resultante é distribuído novamente para todos os binários que usam o conjunto de chaves.
Identificadores de chave em textos criptografados
Considere novamente o exemplo de um conjunto de chaves AEAD. Se feita de forma ingênua, a descriptografia de um texto cifrado exige que o Tink tente descriptografar com todas as chaves no conjunto de chaves, já que não há como saber qual chave foi usada para criptografar o conjunto de chaves. Isso pode causar um grande overhead de desempenho.
Por isso, a Tink permite prefixar textos criptografados com uma string de 5 bytes derivada do ID. Seguindo a filosofia de "Chaves completas" acima, esse prefixo faz parte da chave, e todos os textos criptografados derivados com essa chave precisam ter esse prefixo. Ao criar chaves, os usuários podem escolher se elas devem usar esse prefixo ou se um formato de texto criptografado sem ele deve ser usado.
Quando uma chave está em um conjunto de chaves, o Tink calcula essa tag com base no ID que a chave tem no conjunto de chaves. O fato de os IDs serem exclusivos2 em um conjunto de chaves implica que as tags são exclusivas. Portanto, se apenas chaves marcadas forem usadas, não haverá perda de desempenho em comparação com a descriptografia com uma única chave: o Tink só precisa tentar uma das chaves ao descriptografar.
No entanto, como a tag faz parte da chave, isso também implica que a chave só pode estar em um conjunto de chaves se tiver um ID específico. Isso tem algumas implicações ao descrever a implementação de objetos principais em diferentes linguagens.
Chaves com um requisito de ID, mas sem prefixo de saída
Algumas chaves precisam ter um ID específico, mas não adicionam prefixo à saída. Por exemplo, chaves de assinatura com a variante NO_PREFIX_WITH_PREHASH_ID (armazenadas com o tipo de prefixo de saída WITH_ID_REQUIREMENT) produzem assinaturas sem um prefixo. Quando você usa uma chave desse tipo com a primitiva
Prehash, a Tink
grava o ID da chave no valor de pré-hash. Assim, um assinante remoto sabe com qual
das chaves assinar.
Assim como as chaves que usam um prefixo, uma chave desse tipo só pode estar em um conjunto de chaves com esse ID. O ID da chave no valor pré-hash é um metadado simples, assim como o prefixo de saída: a assinatura não o vincula, e os verificadores nunca o veem. Para o layout no nível de byte, consulte Formato de fio do Tink.
-
Algumas partes do Tink ainda tratam os conjuntos de chaves como um conjunto. No entanto, isso precisa ser mudado. O motivo é que a ordem geralmente é importante. Por exemplo, considere o ciclo de vida típico de uma rotação de chaves com Aead. Primeiro, uma nova chave é adicionada a um conjunto de chaves. Essa chave ainda não é primária, mas está ativa. O novo conjunto de chaves é lançado para todos os binários. Quando todos os binários conhecerem a nova chave, ela será definida como primária. Somente nesse momento o uso dela será seguro. Nesta segunda etapa, a rotação de chaves precisa saber qual foi a última chave adicionada. ↩
-
Para compatibilidade com uma biblioteca interna do Google, o Tink permite ter conjuntos de chaves em que os IDs são repetidos. Essa compatibilidade será removida no futuro. ↩