Primitivas e interfaces

A seguir, definimos (informalmente, mas depois mais formalmente) duas partes importantes da linguagem usada na Tink: o Primitive e a Interface.

Primário

Um primitivo é um objeto matemático correspondente a todos os algoritmos que executam alguma tarefa com segurança. Por exemplo, o primitivo AEAD consiste em todos os algoritmos de criptografia que satisfazem as propriedades de segurança exigidas pelo Tink de um Aead.

Enfatizamos que os primitivos não estão vinculados a uma linguagem de programação ou a uma maneira específica de acessá-los. Em vez disso, pense no primitivo como o objeto puramente matemático. Por exemplo, se considerarmos AEAD, basicamente ele consistirá em pares de funções, uma que executa a criptografia e outra que executa a descriptografia.

Interfaces

Uma interface é uma maneira de fornecer aos usuários acesso a um primitivo. Por exemplo, esperamos que, no futuro, o Tink forneça uma interface Mac, mas também uma interface StreamingMac, que permite calcular o MAC de dados que não é diretamente carregado na memória.

Observe que distinguimos explicitamente interfaces e primitivos aqui. Isso deixa claro que o objeto matemático a que essas duas interfaces dão acesso é o mesmo.

Definições formais

Para a maioria dos leitores, as explicações intuitivas acima provavelmente são suficientes. No entanto, achamos que às vezes é importante fornecer definições formais desses conceitos.

Funções criptográficas

O conceito de uma função criptográfica não é tão importante quanto o conceito de primitivo, mas precisamos apresentá-lo para definir formalmente o primitivo.

Função criptográfica

Uma função criptográfica é um mapa

\[ f: {\bf K} \times {\bf R} \times {\bf I} \to {\bf O}\]

de um conjunto \({\bf K}\) (o espaço da chave), um conjunto \({\bf R} = \{0,1\}^{\infty}\)(aleatoriedade, que consideramos ser o conjunto de bitstrings infinitas), e um conjunto \({\bf I}\) (espaço de entrada) para um conjunto \({\bf O}\) (o espaço de saída).

Mais tarde, ficará claro por que adicionamos um parâmetro específico de aleatoriedade.

Como exemplo, mostramos uma possibilidade de como esses conceitos podem ser mapeados para AES-GCM. Para cada tamanho válido de chave \(s_k\), valor de uso único \(s_n\)e tamanho da tag \(s_t\), o AES-GCM consiste em duas funções criptográficas, uma para criptografia e outra para descriptografia. Ambas terão o mesmo espaço de tecla \({\bf K} = \{0,1\}^{s_k}\).

Para a função de criptografia \(\mathrm{Enc}\), os primeiros \(s_n\) bits de aleatoriedade serão usados para selecionar o valor de uso único.

Permita que \({\bf B} = \{0,1\}^8\) indique um byte. O espaço de entrada da função de criptografia são os pares \({\bf I} = {\bf B}^{*} \times {\bf B}^{*}\) de pares de strings de bytes de comprimento arbitrário. O primeiro elemento do par é a mensagem, e o segundo são os dados associados. O padrão AES-GCM tem um limite máximo nos comprimentos das entradas, mas preferimos permitir comprimentos arbitrários e, em vez disso, adicionar um símbolo de erro especial \(\bot\) ao espaço de saída. O espaço de saída se torna \({\bf O} = {\bf B}^* \cup \{\bot\}\), em que definimos arbitrariamente o resultado de cálculos bem-sucedidos como a concatenação \((\mathrm{IV} \| \mathrm{ciphertext} \| \mathrm{tag})\) , conforme fornecida no padrão, e a saída \(\bot\), caso alguma entrada seja muito longa. Portanto, para uma chave fixa, a função de criptografia passa a ser do tipo \(\mathrm{Enc}_k : {\bf R} \times {\bf B}^* \times {\bf B}^* \rightarrow {\bf B}^* \cup \{\bot\}\).

Para a função de descriptografia, \(\mathrm{Dec}\) o espaço da chave é o mesmo. O espaço de entrada coincidentemente é o mesmo: \({\bf I} ={\bf B}^* \times {\bf B}^*\), mas agora o primeiro elemento será a saída da função de criptografia, enquanto o segundo ainda são os dados associados.

O espaço de saída também é o mesmo \({\bf O} = {\bf B}^* \cup \{\bot\}\) (de novo, uma coincidência). A interpretação é um pouco diferente, porque \(\bot\) geralmente indica um erro de autenticação (embora também seja a saída caso alguma entrada seja muito longa).

Ressaltamos que a formalização acima não é a única opção para formalizar o padrão. Por exemplo, é possível considerar o valor de uso único como parte da entrada, em vez de lê-lo a partir da aleatoriedade, o que resulta em um primitivo muito diferente. Como alternativa, é possível definir a saída como um triplo contendo o valor de uso único, o texto criptografado e a tag (em vez da concatenação). Ou é possível restringir o espaço da chave (de alguma forma arbitrariamente) a \({\bf K} = \{0,1\}^{128} \cup \{0,1\}^{256}\).

Algoritmo criptográfico:

Um algoritmo criptográfico (simétrico) é uma tupla

\[(f_1, ... f_k)\]

de funções criptográficas, em que todas as funções têm o mesmo espaço de chave. O tipo do algoritmo criptográfico é a tupla \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\).

Por exemplo, para cada triplo \((s_k, s_n, s_t)\) de chave, valor de uso único e tamanho da tag, o AES-GCM\({}_{s_k, s_n, s_t}\) é um algoritmo criptográfico com as duas funções \(\mathrm{Enc}\) e \(\mathrm{Dec}\) descritas acima.

Primitivas e interfaces

A seguir, definimos um primitivo criptográfico.

Primário
Um primário é um conjunto de algoritmos criptográficos em que todos os algoritmos têm o mesmo tipo \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\), e os espaços de chave deles são desconexos em pares.

Por exemplo, considere o \(\mathrm{AEAD}\) primitivo no Tink. Ele tem vários algoritmos, entre eles são o AES-GCM para tamanhos de chave de 128 e 256 bits, com tamanho de valor de uso único de 96 bits, AES-EAX com alguns tamanhos de chave e XChaCha20Poly1305. Elas têm espaços de chave desconexos, mas todos fornecem as mesmas funções criptográficas \(\mathrm{Enc}\) e \(\mathrm{Dec}\). Não vemos um propósito em recolher diferentes tamanhos de chave de AES-GCM nesta discussão formal, mas é claro que isso é possível.

Como definir primitivos

A maneira comum de pensar nos primitivos é definir as propriedades das funções criptográficas e, em seguida, simplesmente considerar que o primitivo é todos esses algoritmos.

Por exemplo, para a AEAD, dizemos que \(\mathrm{Dec}_k(\mathrm{Enc}_k(m, a), a) = m\) é "sempre" satisfeito (exceto, por exemplo, se o texto simples \(m\) for muito longo). Além disso, temos propriedades de segurança. Por exemplo, para uma chave aleatória, a criptografia é praticamente segura.

O primitivo AEAD é simplesmente o conjunto de todos os algoritmos criptográficos que satisfazem essas propriedades. Em outras palavras, na prática, quando definimos um primitivo específico, nós o definimos com base nas propriedades. Não fornecemos uma lista de algoritmos, como a definição sugere.

Interfaces

Uma interface no Tink dá acesso a um primitivo, no sentido em que ele permite calcular um elemento do espaço de saída do espaço de entrada. Por exemplo, considere a interface AEAD em Java:

public interface Aead {
  byte[] encrypt(byte[] plaintext, byte[] associated_data) throws GeneralSecurityException;
  byte[] decrypt(byte[] ciphertext, byte[] associated_data) throws GeneralSecurityException;
}

Não damos acesso à aleatoriedade. Em vez disso, permitimos que o usuário forneça elementos do espaço de entrada. Não permitir o acesso à aleatoriedade é, obviamente, de propósito.1

Às vezes, o Tink oferece várias interfaces para um único primitivo. Isso pode ser muito útil, já que os requisitos às vezes são diferentes. Ainda assim, fazer isso tem um preço: em geral, quanto mais interfaces uma oferece, menor é a interoperabilidade. Por exemplo, imagine que alguém grave uma biblioteca baseada no Tink que exija que o usuário transmita um objeto Aead (para criptografar algo internamente). Se o Tink oferecer muitas interfaces diferentes para o \(\mathrm{AEAD}\) primário, é provável que o usuário não tenha uma instância pronta que funcione para a chave que o usuário escolheu e a biblioteca ao mesmo tempo. Portanto, adicionar mais interfaces é uma troca.


  1. As criptografias AEAD têm a propriedade de serem seguras contra ataques de texto criptografado escolhidos, o que é garantido apenas se não houver reutilização do valor de uso único. A interface do Aead no Tink foi projetada para impedir a reutilização do valor de uso único: o usuário não pode fornecer um valor de uso único como entrada para criptografia. Em vez disso, um novo valor de uso único é gerado aleatoriamente para cada operação de criptografia.