Partilhar via


Cabeçalhos de contexto no ASP.NET Core

Contexto 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 uma ID exclusiva (um GUID) e carrega com ela informações algorítmicas e material entrópico. A intenção é que cada chave tenha uma entropia exclusiva, mas o sistema não pode impor isso, e também precisamos levar em conta 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 entrópico em vários algoritmos criptográficos.

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

Recuando, decidimos que estávamos nos aproximando do problema de uma direção errada. Um OID informa qual é o algoritmo, mas não é isso o que realmente importa para nós. Se precisarmos usar um único valor de entropia com segurança em dois algoritmos diferentes, não será necessário saber quais são esses algoritmos. O que realmente importa é como eles se comportam. Qualquer algoritmo de codificação de bloco simétrico decente também é uma PRP (permutação pseudo-aleatória) forte: corrija as entradas (chave, modo de encadeamento, IV, texto não criptografado) e a saída de texto cifrado com probabilidade esmagadora será distinta de qualquer outro algoritmo de codificação de bloco simétrico, sendo dadas as mesmas entradas. Da mesma forma, qualquer função de hash com chave decente também é uma PRF (função pseudo-aleatória) forte e, dado um conjunto de entrada fixo, sua saída será esmagadoramente distinta de qualquer outra função de hash com chave.

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 específica 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 no modo CBC + autenticação HMAC

Esse exemplo 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 codificação de bloco simétrico.

  • [32 bits] O tamanho do bloco (em bytes, big-endian) do algoritmo de codificação 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 da mensagem.)

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

  • EncCBC(K_E, IV, ""), que é a saída do algoritmo de codificação de bloco simétrico, se for dada uma entrada de cadeia de caracteres vazia e em que IV é um vetor nulo. A construção de K_E é descrita abaixo.

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

Idealmente, poderíamos passar todos os vetores nulos 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), o que impede o uso de um padrão simples ou repetível, como um vetor nulo.

Em vez disso, usamos o NIST SP800-108 KDF no modo de contador (consulte NIST SP800-108, seção 5.1) com uma chave de comprimento zero, rótulo e contexto e HMACSHA512 como o PRF subjacente. Derivamos bytes de saída de | K_E | + | K_H | e, em seguida, decompomos o resultado em K_E e K_H. Matematicamente, isso é representado como visto abaixo.

( 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 codificação 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, deixe ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = ""), onde | K_E | = 192 bits e | K_H | = 256 bits por algoritmo especificado. 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 considerando IV = 0* e K_E como acima.

result := F474B1872B3B53E4721DE19C0841DB6F

Em seguida, compute MAC(K_H, "") para HMACSHA256 considerando 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 autenticado (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 do bloco (00 00 00 18)

  • o tamanho do bloco de codificação de bloco (00 00 00 10)

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

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

  • a saída PRP de codificação de bloco (F4 74 - DB 6F) e

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

Observação

O cabeçalho de contexto de criptografia no modo CBC + autenticação HMAC é criado da mesma maneira, independentemente das implementações de algoritmos serem fornecidas pelo CNG do Windows ou pelos tipos SymmetricAlgorithm e KeyedHashAlgorithm gerenciados. Isso permite que os aplicativos em execução em diferentes sistemas operacionais produzam de forma confiável o mesmo cabeçalho de contexto, embora as implementações dos algoritmos defiram entre os OSes. (Na prática, o KeyedHashAlgorithm não precisa ser um HMAC adequado. Pode ser qualquer tipo de algoritmo de hash com 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 por algoritmo especificado. 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 considerando IV = 0* e K_E como acima.

result := ABB100F81E53E10E

Em seguida, compute MAC(K_H, "") para HMACSHA1 considerando K_H como acima.

result := 76EB189B35CF03461DDF877CD9F4B1B4D63A7555

Isso produz o cabeçalho de contexto completo, que é uma impressão digital do par de algoritmos de criptografia autenticado (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 do bloco (00 00 00 18)

  • o tamanho do bloco de codificação de bloco (00 00 00 08)

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

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

  • a saída PRP de codificação de bloco (AB B1 - E1 0E) e

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

Criptografia Modo Galois/Contador (GCM) + autenticação

Esse exemplo 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 codificação de bloco simétrico.

  • [32 bits] O tamanho do nonce (em bytes, big-endian) usado durante operações de criptografia autenticadas. (Para nosso sistema, isso é corrigido no tamanho de nonce = 96 bits).

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

  • [32 bits] O tamanho da marca de autenticação (em bytes, big-endian) produzido pela função de criptografia autenticada. (Para nosso sistema, isso é fixo no tamanho da marca = 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, considerando uma entrada de cadeia de caracteres e onde nonce é um vetor nulo 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 | = 0 e o algoritmo entra em colapso na forma abaixo.

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

Exemplo: AES-256-GCM

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

K_E := 22BC6F1B171C08C4AE2F27444AF8FC8B3087A90006CAEA91FDCFB47C1B8733B8

Em seguida, compute a marca de autenticação de Enc_GCM (K_E, nonce, "") para AES-256-GCM considerando nonce = 096 e K_E conforme 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 do bloco (00 00 00 20)

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

  • o tamanho do bloco de codificação de bloco (00 00 00 10)

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

  • a marca de autenticação da execução da codificação de bloco (E7 DC - end) .