Neste documento, definimos formalmente a função matemática representada pelas chaves de streaming HMAC AES-CTR (codificadas no formato proto como type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
).
Essa criptografia é vagamente baseada em [HRRV15]1. Para uma análise da
segurança, consulte [HS20]2. Observe também que os testes de várias linguagens 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 partes a seguir (todos os tamanhos neste documento sã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}\).
As chaves válidas também 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\) (Isso é igual a \(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , conforme explicado posteriormente).
As chaves que não atendem a nenhuma dessas propriedades são rejeitadas pelo Tink (quando a chave é analisada ou quando o primitivo correspondente é criado).
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 segmento e concatenamos os segmentos. Explicamos essas etapas a seguir.
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 HKDF3 com função hash \(\mathrm{HkdfHashType}\) para calcular o tamanho da chave \(\mathrm{DerivedKeySize} + 32\) para esta mensagem: \(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). As entradas são usadas nas respectivas entradas correspondentes de \(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) is \(\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\), por exemplo, \(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) e \(\mathrm{len}(k_2) = 32\).
Dividir a mensagem
A seguir, a mensagem \(M\) será dividida em partes: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Os comprimentos são escolhidos de modo que satisfaçam:
- \(\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 o acima para restrições.
Nessa divisão, \(n\) pode ser no máximo \(2^{32}\). Caso contrário, a criptografia falhará.
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 de CTR do AES \(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 HMAC com a função de hash fornecida por \(\mathrm{HmacHashType}\) e com key \(k_2\) sobre a concatenação\(\mathrm{IV}_i \| C'_i\).
Em seguida, concatenamos o texto criptografado seguido pela tag para extrair \(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 do texto criptografado individualmente.
As APIs podem permitir (e normalmente permitem) acesso aleatório ou ao início de um arquivo sem inspecionar o fim do arquivo. 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 descriptografia: em ambos os casos, a API provavelmente precisa retornar um erro, e ignorar a diferença pode fazer com que um adversário consiga truncar arquivos com eficácia.
Serialização e análise de chaves
Para serializar uma chave no formato "Tink Proto", primeiro mapeamos os parâmetros da maneira óbvia para o proto em aes_ctr_hmac_streaming.proto.
O campo version
precisa ser definido como 0.
Em seguida, serializamos isso usando a serialização 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 o incorporamos a um conjunto de chaves. Normalmente,
definimos o output_prefix_type
como RAW
. A exceção é que, se a chave
tiver sido 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 normal ao analisar
protos). O campo key_material_type
é ignorado. O valor de output_prefix_type
pode ser ignorado, ou as chaves com output_prefix_type
diferentes de RAW
podem ser rejeitadas.
As chaves com um version
diferente de 0 serão rejeitadas.
Referências
-
[HRRV15] Hoang, Reyhanitabar, Rogaway e Vizar. Criptografia autenticada on-line e a resistência ao uso indevido ao reutilizar o valor de uso único. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
[HS20] Segurança da criptografia de streaming na biblioteca Tink do Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] Função de derivação de chaves com extração e expansão baseada em HMAC (HKDF, na sigla em inglês), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩