Este documento define formalmente a função matemática representada pelas
chaves de streaming AES-GCM-HKDF, codificadas no formato proto como
type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
.
Essa criptografia é baseada em HRRV151. Para a análise de segurança, consulte a HS202.
Chave e parâmetros
As chaves são descritas pelas seguintes partes (todos os tamanhos neste documento estão em bytes):
- \(\mathrm{KeyValue}\), uma string de bytes.
- \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
- \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
- \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
Além disso, as chaves válidas atendem às seguintes propriedades:
- \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (é igual a \(\mathrm{len}(\mathrm{Header}) + 16\) , conforme 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 criptografados.
Criar o cabeçalho
Escolhemos uma string aleatória uniforme \(\mathrm{Salt}\) de comprimento \(\mathrm{DerivedKeySize}\) e 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. Observe que \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).
Em seguida, usamos HKDF3 com a função hash fornecida por \(\mathrm{HkdfHashType}\) e as entradas \(\mathrm{ikm} := \mathrm{KeyValue}\), \(\mathrm{salt} := \mathrm{Salt}\)e \(\mathrm{info} := \mathrm{AssociatedData}\), com comprimento de saída \(\mathrm{DerivedKeySize}\). Chamamos o resultado de \(\mathrm{DerivedKey}\).
Dividir a mensagem
A mensagem \(\mathrm{Msg}\) é dividida em partes: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\).
As durações são escolhidas para atender a:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\).
- Se \(n>1\), então \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\).
- Se \(n>1\), então \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) precisa ter o comprimento máximo de acordo com as restrições acima.
\(n\) pode ter no máximo \(2^{32}\). Caso contrário, a criptografia falha.
Criptografar os blocos
Para criptografar o segmento \(M_i\), calculamos \(\mathrm{IV}_i := \mathrm{NoncePrefix}
\| \mathrm{i} \| b\), em que \(\mathrm{i}\) é de 4 bytes na codificação big-endian e
o byte $b$ é 0x00
se $i < n-1$ e 0x01
, caso contrário.
Em seguida, criptografamos \(M_i\) usando AES-GCM4, em que a chave é \(\mathrm{DerivedKey}\), o vetor de inicialização é \(\mathrm{IV}_i\)e os dados associados são a string vazia. \(C_i\) é o resultado dessa criptografia, ou seja, a concatenação de \(C\) e \(T\) na seção 5.2.1.2 da referência AES-GCM vinculada.
Concatenar os segmentos criptografados
Por fim, todos os segmentos são concatenados como \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), que é o texto criptografado final.
Descriptografia
A descriptografia inverte a criptografia. Usamos o cabeçalho para receber \(\mathrm{NoncePrefix}\)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 da maneira óbvia para o proto fornecido em
aes_gcm_hkdf_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.AesGcmHkdfStreamingKey
. Em seguida, definimos
key_material_type
como SYMMETRIC
e incorporamos isso 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 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 precisam ser rejeitadas.
Problemas conhecidos
As implementações da função de criptografia acima não são consideradas seguras para fork. Consulte Segurança de bifurcação.
Referências
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. Criptografia autenticada on-line e resistência a uso indevido de uso de valores de uso único. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
Hoang, Shen, 2020. Segurança da criptografia de streaming na biblioteca Tink do Google. https://eprint.iacr.org/2020/1019 ↩
-
RFC 5869. Função de derivação de chaves (HKDF) com extração e expansão com base em HMAC. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D. Recomendação para modos de operação de cifra de bloco: modo Galois/Counter (GCM) e GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final ↩