AEAD de streaming HMAC AES-CTR

Este documento define formalmente a função matemática representada por chaves de streaming HMAC AES-CTR (codificadas no formato proto como type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey).

Essa criptografia é baseada em [HRRV15]1. Para uma análise da segurança, consulte [HS20]2. Observe também que os testes de vários idiomas do Tink têm um teste aes_ctr_hmac_streaming_key_test.py que contém test_manually_created_test_vector com um tutorial completo sobre como receber um texto criptografado.

Chave e parâmetros

As chaves são descritas pelas seguintes partes (todos os tamanhos neste documento estão em bytes):

  • \(\mathrm{InitialKeyMaterial}\), uma string de bytes: o material da chave inicial.
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacTagSize} \in \mathbb{N}\).

Além disso, as chaves válidas atendem às seguintes propriedades:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA1}\) então \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA256}\) então \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Se \(\mathrm{HmacHashType} = \mathrm{SHA512}\) então \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (é igual a \(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , como explicado mais adiante).

As chaves que não atendem a nenhuma dessas propriedades são rejeitadas pelo Tink, quando a chave é analisada ou quando a primitiva correspondente é criada.

Função de criptografia

Para criptografar uma mensagem \(\mathrm{Msg}\) com dados associados \(\mathrm{AssociatedData}\), criamos um cabeçalho, dividimos a mensagem em segmentos, criptografamos cada um deles e concatenamos os segmentos. Explicamos essas etapas a seguir.

Como criar o cabeçalho

Para criar o cabeçalho, primeiro escolhemos uma string aleatória uniforme \(\mathrm{Salt}\) de comprimento \(\mathrm{DerivedKeySize}\). Em seguida, escolhemos uma string aleatória uniforme \(\mathrm{NoncePrefix}\) de comprimento 7.

Em seguida, definimos \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), em que o comprimento do cabeçalho é codificado como um único byte. Observamos que \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Em seguida, usamos o HKDF3 com a função hash \(\mathrm{HkdfHashType}\) para calcular o material da chave de comprimento \(\mathrm{DerivedKeySize} + 32\) para esta mensagem: \(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). As entradas são usadas nas entradas correspondentes de \(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) é \(\mathrm{ikm}\), \(\mathrm{Salt}\) é o sal e \(\mathrm{AssociatedData}\) é usado como \(\mathrm{info}\).

A string \(k\) é dividida em duas partes \(k_1 \| k_2 := k\), de modo que \(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) e \(\mathrm{len}(k_2) = 32\).

Como dividir a mensagem

A mensagem \(M\) é dividida em partes: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).

As durações são escolhidas para atender aos seguintes requisitos:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
  • Se \(n > 1\), então \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Se \(n > 1\), então \(M_{0}, \ldots, M_{n-2}\) precisa ter o comprimento máximo de acordo com as restrições acima.

Nessa divisão, \(n\) pode ser no máximo \(2^{32}\). Caso contrário, a criptografia falha.

Como criptografar os blocos

Para criptografar o segmento \(M_i\), primeiro calculamos \(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), em que codificamos \(\mathrm{i}\) em 4 bytes usando a codificação big-endian e definimos o byte $b$ como 0x00 se $i < n-1$ e 0x01, caso contrário.

Em seguida, criptografamos \(M_i\) usando a chave AES CTR \(k_1\)e o vetor de inicialização \(\mathrm{IV}_i\). Em outras palavras, as entradas para as invocações de AES são \(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), em que \(\mathrm{IV}_i\) é interpretado como um número inteiro big-endian. Isso gera \(C'_i\).

Calculamos a tag usando o HMAC com a função hash fornecida por \(\mathrm{HmacHashType}\) e com a chave \(k_2\) sobre a concatenação \(\mathrm{IV}_i \| C'_i\).

Em seguida, concatenamos o texto criptografado seguido pela tag para receber \(C_i\).

Concatenar os segmentos

Por fim, todos os segmentos são concatenados como \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), que é o texto criptografado final.

Função de descriptografia

A descriptografia simplesmente inverte a criptografia. Usamos o cabeçalho para extrair o valor de uso único e descriptografar cada segmento de texto criptografado individualmente.

As APIs podem (e normalmente permitem) acesso aleatório ou acesso ao início de um arquivo sem inspecionar o final dele. Isso é intencional, já que é possível descriptografar \(M_i\) de \(C_i\), sem descriptografar todos os blocos de texto criptografado anteriores e restantes.

No entanto, as APIs precisam ter cuidado para não permitir que os usuários confundam erros de fim de arquivo e de descriptografia: em ambos os casos, a API provavelmente precisa retornar um erro, e ignorar a diferença pode fazer com que um invasor consiga truncar arquivos.

Serialização e análise de chaves

Para serializar uma chave no formato "Tink Proto", primeiro mapeamos os parâmetros de maneira óbvia para o proto fornecido em aes_ctr_hmac_streaming.proto. O campo version precisa ser definido como 0. Em seguida, serializamos isso usando a serialização de proto normal e incorporamos a string resultante no valor do campo de um proto KeyData. Definimos o campo type_url como type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Em seguida, definimos key_material_type como SYMMETRIC e incorporamos isso a um conjunto de chaves. Normalmente, definimos output_prefix_type como RAW. A exceção é que, se a chave for analisada com um valor diferente definido para output_prefix_type, o Tink poderá gravar RAW ou o valor anterior.

Para analisar uma chave, invertemos o processo acima (da maneira usual ao analisar protos). O campo key_material_type é ignorado. O valor de output_prefix_type pode ser ignorado, ou chaves que têm output_prefix_type diferente de RAW podem ser rejeitadas. As chaves com um version diferente de 0 são rejeitadas.

Referências


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Criptografia autenticada on-line e resistência a uso indevido de uso de valores únicos. CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Segurança da criptografia de streaming na biblioteca Tink do Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 

  3. [HKDF] Função de derivação de chaves (HKDF, na sigla em inglês) baseada em HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869