Compartilhar via


Cabeçalhos de contexto no ASP.NET Core

Plano de fundo e teoria

No sistema de proteção de dados, uma "chave" significa um objeto que pode fornecer serviços de criptografia autenticados. Cada chave é identificada por um identificador único (um GUID) e traz consigo informações algorítmicas e material entrópico. A intenção é que cada chave tenha entropia exclusiva, mas o sistema não pode impor isso e também precisamos considerar os desenvolvedores que podem alterar o anel de chaves manualmente modificando as informações algorítmicas de uma chave existente no anel de chaves. Para atingir nossos requisitos de segurança, considerando esses casos, o sistema de proteção de dados tem um conceito de agilidade criptográfica, que permite usar com segurança um único valor trópico em vários algoritmos criptográficos.

A maioria dos sistemas que dão suporte à agilidade criptográfica o fazem incluindo algumas informações de identificação sobre o algoritmo dentro do conteúdo. O OID do algoritmo geralmente é um bom candidato para isso. No entanto, um problema que encontramos é que há várias maneiras de especificar o mesmo algoritmo: as classes "AES" (CNG) e Aes gerenciadas, AesManaged, AesCryptoServiceProvider, AesCng e RijndaelManaged (determinados parâmetros específicos) são, na verdade, a mesma coisa, e precisaríamos manter um mapeamento de todas elas para a OID correta. Se um desenvolvedor quisesse fornecer um algoritmo personalizado (ou até mesmo outra implementação do AES!), ele teria que nos dizer sua OID. Essa etapa de registro extra torna a configuração do sistema particularmente dolorosa.

Recuando, decidimos que estávamos nos aproximando do problema na direção errada. Uma OID informa qual é o algoritmo, mas não nos importamos com isso. Se precisarmos usar um único valor trópico com segurança em dois algoritmos diferentes, não será necessário saber quais são os algoritmos de fato. O que realmente nos importa é como eles se comportam. Qualquer algoritmo de cifra de bloco simétrico decente também é uma PRP (permutação pseudorandom forte): fixar as entradas (chave, modo de encadeamento, IV, texto sem formatação) e a saída de texto cifrado será, com esmagadora probabilidade, distinta de qualquer outro algoritmo de cifra de bloco simétrico, dadas as mesmas entradas. Da mesma forma, qualquer função de hash chaveada decente também é uma função pseudorandom forte (PRF) e, dado um conjunto de entradas fixo, sua saída será esmagadoramente distinta de qualquer outra função de hash chaveada.

Usamos esse conceito de PRPs e PRFs fortes para criar um cabeçalho de contexto. Esse cabeçalho de contexto funciona essencialmente como uma impressão digital estável sobre os algoritmos em uso para qualquer operação determinada e fornece a agilidade criptográfica necessária para o sistema de proteção de dados. Esse cabeçalho é reproduzível e é usado posteriormente como parte do processo de derivação de subchave. Há duas maneiras diferentes de criar o cabeçalho de contexto, dependendo dos modos de operação dos algoritmos subjacentes.

Criptografia de modo CBC + autenticação HMAC

O cabeçalho de contexto consiste nos seguintes componentes:

  • [16 bits] O valor 00 00, que é um marcador que significa "criptografia CBC + autenticação HMAC".

  • [32 bits] O comprimento da chave (em bytes, big-endian) do algoritmo de criptografia de bloco simétrico.

  • [32 bits] O tamanho do bloco (em bytes, big-endian) do algoritmo de criptografia de bloco simétrico.

  • [32 bits] O comprimento da chave, em bytes (big-endian), do algoritmo HMAC. (Atualmente, o tamanho da chave sempre corresponde ao tamanho do resumo.)

  • [32 bits] O tamanho do resumo (em bytes, big-endian) do algoritmo HMAC.

  • EncCBC(K_E, IV, ""), que é a saída do algoritmo de criptografia de bloco simétrico dada uma entrada de string vazia e onde IV é um vetor todo zero. A construção de K_E é descrita abaixo.

  • MAC(K_H, ""), que é a saída do algoritmo HMAC dada uma entrada de cadeia de caracteres vazia. A construção de K_H é descrita abaixo.

Idealmente, poderíamos passar todos os vetores zero para K_E e K_H. No entanto, queremos evitar a situação em que o algoritmo subjacente verifica a existência de chaves fracas antes de executar qualquer operação (notadamente DES e 3DES), que impede o uso de um padrão simples ou repetível como um vetor zero.

Em vez disso, usamos o KDF NIST SP800-108 no Modo Contador (consulte NIST SP800-108, s. 5.1) com uma chave de comprimento zero, um rótulo e um contexto, e HMACSHA512 como o PRF subjacente. Derivamos | K_E | + | K_H | bytes de saída, em seguida, decompomos o resultado em K_E e K_H por si mesmos. Matematicamente, isso é representado da seguinte maneira.

( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

Exemplo: AES-192-CBC + HMACSHA256

Por exemplo, considere o caso em que o algoritmo de criptografia de bloco simétrico é AES-192-CBC e o algoritmo de validação é HMACSHA256. O sistema geraria o cabeçalho de contexto usando as etapas a seguir.

Primeiro, defina ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""), onde | K_E | = 192 bits e | K_H | = 256 bits conforme os algoritmos especificados. Isso leva a K_E = 5BB6..21DD e K_H = A04A..00A9 no exemplo abaixo:

5B B6 C9 83 13 78 22 1D 8E 10 73 CA CF 65 8E B0
61 62 42 71 CB 83 21 DD A0 4A 05 00 5B AB C0 A2
49 6F A5 61 E3 E2 49 87 AA 63 55 CD 74 0A DA C4
B7 92 3D BF 59 90 00 A9

Em seguida, compute Enc_CBC (K_E, IV, "") para AES-192-CBC dado IV = 0* e K_E conforme mencionado acima.

result := F474B1872B3B53E4721DE19C0841DB6F

Em seguida, compute MAC(K_H, "") para HMACSHA256 dado K_H como acima.

result := D4791184B996092EE1202F36E8608FA8FBD98ABDFF5402F264B1D7211536220C

Isso produz o cabeçalho de contexto completo abaixo:

00 00 00 00 00 18 00 00 00 10 00 00 00 20 00 00
00 20 F4 74 B1 87 2B 3B 53 E4 72 1D E1 9C 08 41
DB 6F D4 79 11 84 B9 96 09 2E E1 20 2F 36 E8 60
8F A8 FB D9 8A BD FF 54 02 F2 64 B1 D7 21 15 36
22 0C

Esse cabeçalho de contexto é a impressão digital do par de algoritmos de criptografia autenticada (criptografia AES-192-CBC + validação HMACSHA256). Os componentes, conforme descrito acima , são:

  • o marcador (00 00)

  • o comprimento da chave de codificação de bloco (00 00 00 18)

  • o tamanho do bloco de criptografia de bloco (00 00 00 10)

  • o comprimento da chave HMAC (00 00 00 20)

  • o tamanho do resumo do HMAC (00 00 00 20)

  • a saída PRP de criptografia de bloco (F4 74 - DB 6F) e

  • a saída (D4 79 - end) do HMAC PRF.

Observação

O cabeçalho de contexto de criptografia em modo CBC e autenticação HMAC é criado da mesma maneira, independentemente de as implementações dos algoritmos serem fornecidas pelo CNG do Windows ou pelos tipos SymmetricAlgorithm e KeyedHashAlgorithm gerenciados. Isso permite que os aplicativos em execução em sistemas operacionais diferentes produzam de forma confiável o mesmo cabeçalho de contexto, embora as implementações dos algoritmos diferem entre os OSes. (Na prática, o KeyedHashAlgorithm não precisa ser um HMAC adequado. Pode ser qualquer tipo de algoritmo de hash chave.)

Exemplo: 3DES-192-CBC + HMACSHA1

Primeiro, deixe ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""), onde | K_E | = 192 bits e | K_H | = 160 bits de acordo com os algoritmos especificados. Isso leva a K_E = A219..E2BB e K_H = DC4A..B464 no exemplo abaixo:

A2 19 60 2F 83 A9 13 EA B0 61 3A 39 B8 A6 7E 22
61 D9 F8 6C 10 51 E2 BB DC 4A 00 D7 03 A2 48 3E
D1 F7 5A 34 EB 28 3E D7 D4 67 B4 64

Em seguida, compute Enc_CBC (K_E, IV, "") para 3DES-192-CBC dado IV = 0* e K_E como acima mencionado.

result := ABB100F81E53E10E

Em seguida, compute MAC(K_H, "") para HMACSHA1 dado K_H conforme indicado acima.

result := 76EB189B35CF03461DDF877CD9F4B1B4D63A7555

Isso produz o cabeçalho de contexto completo, que é uma impressão digital do par de algoritmos de criptografia autenticada (criptografia 3DES-192-CBC + validação HMACSHA1), mostrado abaixo:

00 00 00 00 00 18 00 00 00 08 00 00 00 14 00 00
00 14 AB B1 00 F8 1E 53 E1 0E 76 EB 18 9B 35 CF
03 46 1D DF 87 7C D9 F4 B1 B4 D6 3A 75 55

Os componentes são separados da seguinte maneira:

  • o marcador (00 00)

  • o comprimento da chave de codificação de bloco (00 00 00 18)

  • o tamanho do bloco de criptografia de bloco (00 00 00 08)

  • o comprimento da chave HMAC (00 00 00 14)

  • o tamanho do resumo do HMAC (00 00 00 14)

  • a saída (AB B1 - E1 0E) prp de criptografia de bloco e

  • a saída do HMAC PRF (76 EB - end).

Criptografia do Modo Galois/Contador + autenticação

O cabeçalho de contexto consiste nos seguintes componentes:

  • [16 bits] O valor 00 01, que é um marcador que significa "criptografia GCM + autenticação".

  • [32 bits] O comprimento da chave (em bytes, big-endian) do algoritmo de criptografia de bloco simétrico.

  • [32 bits] O tamanho do nonce (em bytes, big-endian) usado durante operações de encriptação autenticada. Para o nosso sistema, o tamanho do nonce é fixado em 96 bits.

  • [32 bits] O tamanho do bloco (em bytes, big-endian) do algoritmo de criptografia de bloco simétrico. (Para GCM, isso é fixado no tamanho do bloco = 128 bits.)

  • [32 bits] O tamanho do tag de autenticação (em bytes, big-endian) produzido pela função de criptografia autenticada. (Para nosso sistema, isso é fixado no tamanho da tag = 128 bits.)

  • [128 bits] A marca de Enc_GCM (K_E, nonce, ""), que é a saída do algoritmo de codificação de bloco simétrico dado uma entrada de cadeia de caracteres vazia e onde nonce é um vetor all-zero de 96 bits.

K_E é derivado usando o mesmo mecanismo que no cenário de criptografia CBC + autenticação HMAC. No entanto, como não há nenhum K_H em jogo aqui, essencialmente temos | K_H | = 0e o algoritmo entra em colapso na forma abaixo.

K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

Exemplo: AES-256-GCM

Primeiro, vamos K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""), onde | K_E | = 256 bits.

K_E := 22BC6F1B171C08C4AE2F27444AF8FC8B3087A90006CAEA91FDCFB47C1B8733B8

Em seguida, compute o tag de autenticação de Enc_GCM (K_E, nonce, "") para AES-256-GCM, dado nonce = 096 e K_E como mencionado acima.

result := E7DCCE66DF855A323A6BB7BD7A59BE45

Isso produz o cabeçalho de contexto completo abaixo:

00 01 00 00 00 20 00 00 00 0C 00 00 00 10 00 00
00 10 E7 DC CE 66 DF 85 5A 32 3A 6B B7 BD 7A 59
BE 45

Os componentes são separados da seguinte maneira:

  • o marcador (00 01)

  • o comprimento da chave de codificação de bloco (00 00 00 20)

  • o tamanho do nonce (00 00 00 0C)

  • o tamanho do bloco de criptografia de bloco (00 00 00 10)

  • o tamanho (00 00 00 10) da marca de autenticação e

  • a marca de autenticação ao executar a cifra de bloco (E7 DC - end).