Stan Drapkin é um especialista em segurança e conformidade com mais de 16 anos de experiência com o .NET Framework (começando com o .NET 1.0-beta em 2001). Infelizmente, ele próprio não escreve artigos em russo, então concordamos com ele em
publicar uma tradução de
seu relatório com o DotNext Piter . Este relatório
conquistou o primeiro lugar na conferência!
Criptografia simétrica, assimétrica, híbrida, de alto nível, baixo nível, fluxo e criptografia elíptica moderna. Cinqüenta e seis minutos de vídeo sobre criptografia e muito mais rápido - na forma de texto.

Sob o corte - vídeos, slides e tradução. Boa leitura!
SlidesMeu nome é Stan Drapkin, sou o diretor técnico de uma empresa especializada em segurança da informação e conformidade regulatória. Além disso, sou autor de várias bibliotecas de código aberto, que são muito bem recebidas pela comunidade. Quantos ouviram falar do
Inferno ? Esta biblioteca demonstra a abordagem correta da criptografia no .NET e o
TinyORM implementa o micro-ORM para .NET. Além disso, escrevi vários livros que podem ser relevantes para o tópico do artigo de hoje. Um deles, a edição de 2014, é “Security Driven .NET”, o outro de 2017 é “Application Security in .NET, Succinctly”.
Primeiro, falaremos sobre o que eu chamo de quatro estágios da iluminação criptográfica. Em seguida, dois tópicos principais se seguirão: no primeiro, falaremos sobre criptografia simétrica; no segundo, sobre assimétrico e híbrido. Na primeira parte, comparamos a criptografia de alto e baixo nível e analisamos um exemplo de criptografia de fluxo contínuo. Na segunda parte, teremos muitas "aventuras" com a RSA, após as quais nos familiarizaremos com a moderna criptografia elíptica.
Então, como são esses estágios da cripto-iluminação? A primeira etapa - "XOR é tão legal, olha, mãe, como posso!" Certamente muitos de vocês estão familiarizados com este estágio e estão cientes das maravilhas da função XOR. Mas espero que a maior parte desse estágio tenha crescido e passado para a próxima, ou seja, aprendida a executar criptografia e descriptografia usando o AES (Advanced Encryption Standard), um algoritmo conhecido e altamente considerado. A maioria dos desenvolvedores que não visitam o DotNext está nesse estágio. Porém, como você segue o DotNext e conhece os relatórios sobre os perigos das APIs de baixo nível, você provavelmente está no próximo estágio: "Fiz tudo (a) incorretamente, preciso mudar para as APIs de alto nível". Bem, para completar, vou mencionar também a última etapa - o entendimento de que, com a melhor solução para o problema, a criptografia pode não ser necessária. Esse estágio é o mais difícil de alcançar e há poucas pessoas nele. Um exemplo é Peter G. Neumann, que disse o seguinte: "Se você acha que a solução para o seu problema está na criptografia, não entende exatamente qual é o seu problema".
O fato de que a criptografia de baixo nível é perigosa foi discutido em muitos relatórios sobre o .NET. Você pode consultar o relatório de Vladimir Kochetkov em 2015,
"Armadilhas do System.Security.Cryptography" . Sua idéia principal é que, em cada estágio do trabalho com APIs criptográficas de baixo nível, sem conhecê-lo, tomamos muitas decisões, para muitas das quais simplesmente não temos o conhecimento apropriado. A principal conclusão é que, idealmente, a criptografia de alto nível deve ser usada em vez da criptografia de baixo nível. Essa é uma conclusão maravilhosa, mas nos leva a outro problema - sabemos exatamente como deve ser a criptografia de alto nível? Vamos conversar um pouco sobre isso.
Defina os atributos de uma API criptográfica de nível
não alto. Para começar, essa API não dará a impressão de ser nativa do .NET, mas parecerá um shell de baixo nível. Além disso, essa API será fácil de usar incorretamente, ou seja, não como deveria. Além disso, forçará você a gerar muitas coisas estranhas de baixo nível - nonce, vetores de inicialização e similares. Essa API o forçará a tomar decisões desagradáveis para as quais você não está preparado - escolha algoritmos, modos de preenchimento, tamanhos de chave, nonce etc. Também não terá a API correta para streaming (API de streaming) - falaremos sobre como a última deve ser.
Por outro lado, como deve ser uma API criptográfica de alto nível? Acredito que, antes de tudo, deve ser intuitivo e conciso para o leitor do código e o escritor. Além disso, essa API deve ser fácil de aprender e usar e deve ser extremamente difícil de aplicar da maneira errada. Também deve ser poderoso, isto é, deve permitir-nos alcançar nosso objetivo com um pouco de esforço, uma pequena quantidade de código. Por fim, essa API não deve ter uma lista longa de restrições, cuidados, casos especiais, em geral - deve haver um mínimo de coisas que devem ser lembradas ao trabalhar com ela, em outras palavras - deve ser caracterizada por um baixo nível de interferência (baixo atrito). apenas trabalhe sem reservas.
Tendo lidado com os requisitos para uma API criptográfica de alto nível para .NET, como podemos encontrá-la agora? Você pode tentar apenas o google, mas isso seria muito primitivo - somos desenvolvedores profissionais e esse não é o nosso método. Portanto, estamos investigando esse problema e testando várias alternativas. Mas, para isso, precisamos primeiro criar para nós mesmos a idéia correta do que é a criptografia autenticada e, para isso, precisamos entender os conceitos básicos. Eles são os seguintes: o texto sem formatação P (texto sem formatação), que convertemos em texto cifrado C (texto cifrado) do mesmo comprimento usando alguma chave secreta K (chave). Como você pode ver, até agora estamos trabalhando com um esquema muito simples. Além disso, também temos uma tag de autenticação T e nonce N. Um parâmetro importante é N̅, ou seja, reutilizar o nonce com uma chave. Como muitos de vocês provavelmente sabem, isso leva a uma violação da confidencialidade do texto, o que é obviamente indesejável. Outro conceito importante é o AD (Associated Data), ou seja, dados associados. São dados opcionais que são autenticados, mas não participam da criptografia e descriptografia.

Tendo entendido os conceitos básicos, vamos dar uma olhada nas várias opções de bibliotecas criptográficas para .NET. Vamos começar com a análise do
Libsodium.NET. Quantos de vocês a conhecem? A meu ver, alguns são familiares.
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Aqui está o código C # com o qual a criptografia é realizada com o
Libsodium.NET . À primeira vista, é bastante simples e conciso: na primeira linha, gera-se nonce, que é usado na segunda linha, onde a criptografia ocorre, e na terceira, onde o texto é descriptografado. Parecia - que dificuldades poderiam existir? Para começar, o Libsodium.NET oferece não um, mas três métodos diferentes de criptografia simétrica:
Vezes
nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad);
Dois
nonce = SecretAead.GenerateNonce(); c = SecretAead.Encrypt(p, nonce, key, ad); d = SecretAead.Decrypt(c, nonce, key. ad);
Três
nonce = SecretBox.GenerateNonce(); c = SecretBox.Create(p, nonce, key); d = SecretBox.Open(c, nonce, key);
Obviamente, surge a pergunta - qual deles é melhor em sua situação específica? Para responder, é necessário entrar nesses métodos, o que faremos agora.
O primeiro método,
SecretAeadAes
, usa o AES-GCM com um nonce de 96 bits. É importante que ele tenha uma lista bastante longa de restrições. Por exemplo, ao usá-lo, você não deve criptografar mais de 550 gigabytes com uma chave e não deve haver mais de 64 gigabytes em uma mensagem com no máximo 2
32 mensagens. Além disso, a biblioteca não avisa sobre essas restrições; você mesmo deve rastreá-las, o que cria uma carga adicional para você como desenvolvedor.
O segundo método, o
SecretAead
, usa um conjunto de cifras diferente,
ChaCha20/Poly1305
com um nonce de 64 bits significativamente menor. Um número tão pequeno faz com que as colisões sejam extremamente prováveis e, por esse motivo, você não deve usar esse método - exceto em casos muito raros e desde que você seja muito versado no tópico.
Finalmente, o terceiro método,
SecretBox
. Deve-se notar imediatamente que não há dados associados nos argumentos para esta API. Se você precisar de criptografia autenticada com o AD, esse método não é adequado para você. O algoritmo de criptografia usado aqui é chamado
xSalsa20/Poly1305
, o nonce é grande o suficiente - 192 bits. No entanto, a falta de DA é uma limitação significativa.
Ao usar o
Libsodium.NET , surgem algumas perguntas. Por exemplo, o que exatamente devemos fazer com o nonce gerado pela primeira linha de código nos exemplos acima? A biblioteca não nos diz nada sobre isso, temos que descobrir por conta própria. Provavelmente, adicionaremos manualmente esse nonce ao início ou ao final do texto cifrado. Além disso, podemos ter a impressão de que o AD nos dois primeiros métodos pode ter qualquer tamanho. Mas, de fato, a biblioteca suporta o AD com no máximo 16 bytes - afinal, 16 bytes serão suficientes para todos, certo? Vamos seguir em frente. O que acontece com os erros de descriptografia? Nesta biblioteca, foi decidido nesses casos lançar exceções. Se em seu ambiente durante a descriptografia, a integridade dos dados puder ser violada, você terá muitas exceções que precisarão ser tratadas. E se o tamanho da sua chave não tiver exatamente 32 bytes? A biblioteca não nos diz nada sobre isso, esses são seus problemas que não lhe interessam. Outro tópico importante é a reutilização de matrizes de bytes, a fim de reduzir a carga no coletor de lixo em cenários intensivos. Por exemplo, no código, vimos uma matriz que o gerador de nonce retornou para nós. Gostaria de não criar um novo buffer a cada vez, mas reutilizar o existente. Isso não é possível nesta biblioteca, uma matriz de bytes será regenerada toda vez.
Usando o esquema que já vimos, tentaremos comparar vários algoritmos do
Libsodium.NET .

O primeiro algoritmo, AES-GCM, usa nonce 96 bits de comprimento (coluna amarela na imagem). Tem menos de 128 bits, o que cria algum desconforto, mas não é muito significativo. A próxima coluna é azul, este é o local ocupado pela etiqueta de autenticação; no AES-GCM, são 16 bytes ou 128 bits. O segundo dígito azul, entre parênteses, significa a quantidade de entropia, ou aleatoriedade, contida nessa tag - menos de 128 bits. Quanto menos - nesse algoritmo depende da quantidade de dados criptografados. Quanto mais criptografada, mais fraca a tag. Isso por si só deve gerar dúvidas sobre esse algoritmo, que só aumentará se olharmos para a coluna branca. Diz que repetições (colisões) de nonce levarão à falsificação de todos os textos cifrados criados pela mesma chave. Se, dentre, digamos, 100 de seus textos cifrados criados por uma chave comum em dois, houver uma colisão não-violenta, essa violência levará a um vazamento interno da chave de autenticação e permitirá que um invasor falsifique qualquer outro texto cifrado criado por essa chave. Essa é uma limitação muito significativa.
Vamos para o segundo método
Libsodium.NET . Como eu disse, aqui para nonce, pouco espaço é usado, apenas 64 bits. A tag ocupa 128 bits, mas contém apenas 106 bits de entropia ou menos, em outras palavras, significativamente inferior ao nível de segurança de 128 bits, que na maioria dos casos eles tentam alcançar. Quanto à falsificação, a situação aqui é um pouco melhor do que no caso da AES-GCM. A colisão de nonce leva à falsificação de textos cifrados, mas apenas para os blocos em que ocorreram colisões. No exemplo anterior, teríamos forjado 2 textos cifrados, não 100.
Finalmente, no caso do algoritmo xSalsa / Poly, temos um nonce muito grande de 192 bits, o que torna as colisões extremamente improváveis. O método de autenticação é o mesmo do método anterior; portanto, o tag novamente recebe 128 bits e possui 106 bits de entropia ou menos.
Compare todos esses números com os indicadores correspondentes da biblioteca
Inferno . Nele, o nonce ocupa um espaço colossal de 320 bits, o que torna as colisões quase impossíveis. Quanto ao tag, tudo é simples: ocupa exatamente 128 bits e possui exatamente 128 bits de entropia, nada menos. Este é um exemplo de uma abordagem confiável e segura.
Antes de conhecermos o
Libsodium.NET com mais detalhes, precisamos entender seu objetivo - infelizmente, nem todo mundo que usa essa biblioteca está ciente disso. Para fazer isso, consulte a documentação, que afirma que o
Libsodium.NET é um invólucro em C # para o
libsodium . Este é outro projeto de código aberto, cuja documentação diz que é um fork do
NaCl com uma API compatível. Bem, consulte a documentação do
NaCl , outro projeto de código aberto. Nele, como objetivo, postula-
se o NaCl para fornecer todas as operações necessárias para a criação de ferramentas criptográficas de alto nível. É aqui que o cão está enterrado: a tarefa do
NaCl e todas as suas conchas é fornecer elementos de baixo nível, a partir dos quais alguém já pode montar APIs criptográficas de alto nível. Esses shells como bibliotecas de alto nível não foram concebidos. Daí a moral: se você precisa de uma API criptográfica de alto nível, precisa encontrar uma biblioteca de alto nível e não usar um wrapper de baixo nível e fingir que está trabalhando com um de alto nível.
Vamos ver como a criptografia funciona no
Inferno .

Aqui está um código de exemplo no qual, como no caso do
Libsodium , cada criptografia e descriptografia leva apenas uma linha. Os argumentos são chave, texto e dados associados opcionais. Deve-se notar que não há nenhuma exceção, não há necessidade de tomar decisões; no caso de um erro de descriptografia, ele simplesmente retorna nulo, sem gerar exceções. Como a criação de exceções aumenta significativamente a carga no coletor de lixo, sua ausência é muito importante para scripts que processam grandes fluxos de dados. Espero ter conseguido convencê-lo de que essa abordagem é ótima.
Fora de interesse, vamos tentar criptografar alguma string. Esse deve ser o cenário mais simples que todos podem implementar. Suponha que tenhamos apenas dois valores de string diferentes possíveis: "ESQUERDA" e "DIREITA".

Na figura, você vê a criptografia dessas linhas usando o
Inferno (embora, neste exemplo, não importe qual biblioteca é usada). Criptografamos duas linhas com uma chave e obtemos dois textos cifrados,
c1
e
c2
. Tudo neste código está correto? Ele está pronto para a produção? Alguém pode dizer que o problema é possível em um curto espaço de tempo, mas está longe do principal, portanto, assumiremos que a chave é usada da mesma forma e que é de comprimento suficiente. Quero dizer outra coisa: com abordagens criptográficas convencionais,
c1
em nosso exemplo será menor que
c2
. Isso é chamado de vazamento de comprimento -
c2
em muitos casos terá um byte a mais que
c1
. Isso pode permitir que um invasor entenda qual sequência é representada por este texto cifrado, "ESQUERDA" ou "DIREITA". A maneira mais fácil de resolver esse problema é fazer com que as duas linhas tenham o mesmo comprimento - por exemplo, adicione um caractere ao final da linha "ESQUERDA".
À primeira vista, o vazamento de comprimento é percebido como um problema absurdo que não pode ser encontrado em aplicativos reais. Mas em janeiro de 2018, um artigo foi publicado na revista Wired com um estudo realizado pela empresa israelense Checkmarx, sob o título "A falta de criptografia no Tinder permite que pessoas de fora rastreiem quando você desliza a tela". Vou recontar brevemente o conteúdo, mas primeiro uma descrição aproximada da funcionalidade do Tinder. O Tinder é um aplicativo que recebe um fluxo com fotos e, em seguida, o usuário desliza a tela para a direita ou esquerda, dependendo se ele gosta ou não da foto. Os pesquisadores descobriram que, embora os próprios comandos tenham sido criptografados corretamente usando TLS e HTTPS, os dados para o comando da direita ocupam um número diferente de bytes que os da esquerda. Isso, é claro, é uma vulnerabilidade, mas por si só não é muito significativo. Mais significativo para o Tinder foi o fato de que eles enviaram os fluxos com fotos via HTTP normal, sem nenhuma criptografia. Assim, o invasor pode obter acesso não apenas às reações do usuário às fotos, mas também às próprias fotos. Então, como você pode ver, o vazamento de comprimento é um problema muito real.
Agora vamos tentar criptografar o arquivo. Imediatamente, devo dizer que na criptografia de arquivo
Libsodium.NET ou, mais amplamente, a criptografia de fluxo não é implementada por padrão; ela deve ser feita manualmente - o que, acredite, é muito difícil de executar corretamente. No
Inferno , as coisas são muito melhores com isso.

Acima, você vê um exemplo tirado praticamente sem alterações do MSDN. É muito simples, aqui vemos um fluxo para o arquivo de origem e outro para o arquivo de destino, além de um fluxo de criptografia que converte o primeiro no segundo. Nesse código, o
Inferno é usado apenas em uma linha - na onde a conversão ocorre. Portanto, diante de nós é uma solução simples e ao mesmo tempo totalmente funcional e testada para criptografia de fluxo.
Deve-se lembrar que ao criptografar com a mesma chave, temos um limite no número de mensagens. Eles existem no
Inferno e nesta biblioteca estão claramente escritos na tela. Mas, ao mesmo tempo, eles são tão grandes no
Inferno que, na prática, você nunca os alcançará. No
Libsodium.NET, as restrições são diferentes para algoritmos diferentes, mas em todos os casos elas são baixas o suficiente para serem excedidas. Portanto, você precisa verificar se eles serão alcançados em cada cenário individual.
Também devemos falar sobre autenticação de dados associados, pois esse é um tópico que muitas vezes não é abordado. Os anúncios podem ser "fracos": isso significa que eles são autenticados, mas não estão envolvidos no processo de criptografia e descriptografia. Por outro lado, os anúncios "fortes" alteram esse processo. A maioria das bibliotecas do AD que conheço são fracas, enquanto o
Inferno usa a segunda abordagem, na qual os ADs são usados no próprio processo de criptografia / descriptografia ...
Ele também deve se concentrar em qual nível de segurança deve se esforçar para a criptografia de alto nível. Em resumo, minha resposta é: criptografia de 256 bits com uma marca de autenticação de 128 bits. Por que uma chave é tão grande? Há muitas razões para isso, cada uma das quais é significativa por si só, mas agora gostaria que você se lembrasse de uma coisa: precisamos nos proteger de possíveis preconceitos ao gerar chaves criptográficas. Deixe-me explicar o que se entende por viés. Para um gerador de bits aleatório sem viés, para cada bit, as probabilidades de aceitar o valor 0 ou 1 são iguais. Mas suponha que em nosso gerador o bit aceite o valor 1 com uma probabilidade de 56%, não 50%. À primeira vista, esse viés é pequeno, mas na verdade é significativo: 25%. Agora vamos tentar calcular a quantidade de entropia que obtemos ao gerar um certo número de bits com nosso gerador.

Na figura, você vê a fórmula pela qual esse cálculo será feito. É importante que existam apenas duas variáveis: o viés sobre o qual já falamos (viés) e o número de bits criados pelo gerador. Assumimos que o viés é de 25% - este é um caso bastante extremo; na prática, você provavelmente não funcionará em sistemas com um gerador de números aleatórios distorcido. De qualquer forma, com viés de 25% e uma chave de 128 bits, obtemos apenas 53 bits de entropia. Primeiro, é significativamente menor que 128 bits, o que geralmente é esperado de um gerador de números aleatórios e, segundo, com as tecnologias modernas, essa chave pode ser simplesmente força bruta. Mas se em vez da chave de 128 bits usarmos 256 bits, obteremos 106 bits de entropia. Isso já é muito bom, embora seja menor que o esperado 256. Com as tecnologias modernas, é quase impossível decifrar essa chave.
No final da primeira parte do relatório, resumirei os resultados intermediários. Eu recomendo a todos que usem APIs criptográficas bem escritas. Encontre o que mais lhe convém ou envie uma petição à Microsoft para escrever para você. Além disso, ao escolher uma API, você deve prestar atenção à disponibilidade de suporte para trabalhar com threads. Pelas razões já explicadas, o comprimento mínimo da chave deve ser de 256 bits. Por fim, deve-se ter em mente que a criptografia de alto nível, como qualquer outra, não é ideal. Podem ocorrer vazamentos e, na maioria dos cenários, seus recursos devem ser mantidos em mente.
Vamos falar sobre criptografia assimétrica ou híbrida. Vou fazer uma pergunta complicada: você pode usar o RSA no .NET? Não se apresse em responder afirmativamente, como muitos fazem - vamos primeiro testar seu conhecimento nessa área. Os slides a seguir serão projetados especificamente para pessoas que já estão familiarizadas com este tópico. Mas primeiro, vejamos a Wikipedia e lembre-se do que é exatamente o RSA, caso alguém se esqueça ou não use esse algoritmo há muito tempo.

Suponha que haja Alice que, usando um gerador de números aleatórios, crie um par de chaves que inclua um privado e um público. Em seguida, há um Bob que deseja criptografar uma mensagem para Alice: "Olá Alice!" Usando sua chave pública, ele gera um texto cifrado, que ele envia para ela. Ela descriptografa esse texto cifrado usando a parte privada de sua chave.
Vamos tentar reproduzir esse cenário na prática.

Como você pode ver acima, criamos uma instância do RSA e criptografamos algum texto. Preste imediatamente atenção que o .NET nos obriga a escolher o modo de preenchimento. Existem cinco deles, todos com nomes obscuros. Se tentarmos todos por sua vez, descobriremos que os três últimos simplesmente lançam uma exceção e não funcionam. Usaremos um dos dois restantes -
OaepSHA1
. Aqui, a chave terá 1 kilobit de tamanho, que é muito pequena para o RSA, é praticamente uma chave invadida. Portanto, temos que definir o tamanho da chave manualmente. A partir da documentação, descobrimos que existe uma propriedade especial
.KeySize
, que recebe ou define o tamanho da chave.

À primeira vista, é exatamente isso que precisamos, então escrevemos:
rsa.KeySize = 3072
. Mas se, guiados por suspeitas vagas, depois verificarmos qual é o tamanho da chave agora, descobriremos que ele ainda precisa de 1 kilobit. Não importa, verificaremos esse parâmetro usando o método
WriteLine(rsa.KeySize)
ou
rsa.ExportParameters(false).Modulus.Length * 8
- neste último caso, o componente público da chave RSA é exportado, para isso precisamos do argumento "false". O módulo dessa chave é uma matriz, que multiplicamos por 8 e obtemos o tamanho em bits - que novamente será de 1 kilobit. Como você pode ver, esse algoritmo ainda é muito cedo para enviar para produção.
Não perderemos tempo tentando descobrir por que essa API não funciona; tente outra implementação RSA fornecida pela Microsoft no .NET 4.6, ou seja, completamente nova. É chamado
RSACng , e
Cng significa Cryptography next generation. Ótimo, quem não quer trabalhar com ferramentas da próxima geração? Certamente aqui vamos encontrar uma solução mágica para todos os nossos problemas.

Solicitamos uma instância do RSACng, definimos novamente o tamanho da chave como 3 kilobits, verificamos novamente o tamanho da chave via
WriteLine(rsa.KeySize)
- e descobrimos novamente que o tamanho da chave ainda é igual a um kilobit. Além disso, se solicitarmos o tipo de objeto que gerou a chave - como lembramos, solicitamos uma instância do RSACng - descobrimos que é RSACryptoServiceProvider. Eu só quero compartilhar meu sentimento pessoal de desespero aqui e gritar: "Por que Microsoft?!"
Após tormento e tormento prolongados, descobrimos que, de fato, você precisa usar o designer, não a fábrica.

Aqui, o valor padrão do tamanho da chave é 2048 bits, o que já é muito melhor. O que é ainda melhor - aqui finalmente conseguimos definir o tamanho da chave para 3 kilobits. Como se costuma dizer, conquista desbloqueada.
Deixe-me lembrá-lo de que todos os nossos esforços até agora foram reduzidos apenas à criação do RSA, ainda nem iniciamos a criptografia. Ainda há perguntas que precisamos primeiro responder. Para iniciantes, em que medida você pode confiar nos tamanhos de chave padrão? A implementação da fábrica RSA pode ser substituída machine.config
, portanto, pode mudar sem o seu conhecimento (por exemplo, um administrador do sistema pode alterá-la). E isso significa que o tamanho da chave padrão também pode mudar. Portanto, você nunca deve confiar nos valores fornecidos por padrão, o tamanho da chave sempre deve ser definido de forma independente. Em seguida, quão bons são os tamanhos de chave RSA padrão? Existem duas implementações de RSA no .NET, uma baseada RSACryptoServiceProvider
e outra baseadaRSACng
. No primeiro, o tamanho padrão é 1 kilobits, no segundo. Por diversão, vamos comparar esses valores com os da rede Bitcoin (BCN). Peço desculpas antecipadamente por levantar um tópico delicado, mas não discutiremos Bitcoin ou criptomoeda, apenas falaremos sobre a própria rede. Ela tem um hashrate publicado, que cresce todos os meses e hoje é igual a 2 64 hashes por segundo. Isso equivale a 2 90 hashes por ano. Para simplificar, suponha que um hash seja equivalente a uma operação básica - embora isso não seja totalmente verdade, é mais complexo. Se você ler livros sobre criptografia escritos por profissionais reais, e não pessoas como eu, saberá que 2 70 operações (ou seja, um minuto de BCN) são suficientes para quebrar uma chave RSA de 1 kilobit e 2 90(um ano BCN) - para quebrar uma chave de 2 kilobit. Ambos os valores devem nos causar ansiedade - é isso que pode ser alcançado com as tecnologias existentes. É por isso que eu recomendo fortemente que você sempre defina o tamanho da chave e aumente pelo menos 3 kilobits, e se o desempenho permitir, então 4.No .NET, não é tão fácil descobrir como exportar chaves públicas e privadas.
Na parte superior do slide, você vê duas instâncias da chave RSA, a primeira de RSACryptoServiceProvider
, a segunda deRSACng
cada 4 kilobits. O código abaixo é usado para extrair as chaves pública e privada de ambas as instâncias. Deve-se notar que as duas APIs são bem diferentes uma da outra - código diferente, métodos diferentes, parâmetros diferentes. Além disso, se compararmos o tamanho das chaves públicas da primeira e da segunda cópias, veremos que elas são comparáveis, aproximadamente meio kilobyte cada. Mas a chave privada para a nova implementação do RSA é muito menor que a antiga. É necessário ter isso em mente e observar a uniformidade, para não interferir nessas duas APIs.Tudo o que fizemos com a RSA até agora se resumiu a tentar obter uma cópia de trabalho; Agora tente criptografar alguma coisa.
Crie uma matriz de bytes, que será nosso texto sem formatação (data
) e criptografá-lo usando um desses modos de adição que não lançaram uma exceção. Mas desta vez temos uma exceção. Esta é uma exceção a um parâmetro inválido; mas de que parâmetro estamos falando? Não faço ideia - e provavelmente a Microsoft também. Se tentarmos executar o mesmo método com outros modos de suplemento, em cada caso, obteremos a mesma exceção. Portanto, o ponto não está no modo de suplemento. Portanto, o problema está no próprio código-fonte. É difícil dizer o que há de errado com ele, então vamos tentar dividir pela metade apenas por precaução. Desta vez, a criptografia é bem-sucedida. Estamos perplexos.Talvez o ponto principal seja que usamos o suplemento SHA-1? O SHA-1, como sabemos, não é mais uma função criptograficamente forte; portanto, nossos auditores e o departamento de conformidade insistem em nos livrar dele. Substitua OaepSHA1
por OaepSHA256
, pelo menos, tranquilizará os auditores.
Mas quando tentamos criptografar, obtemos novamente a exceção do parâmetro errado. Toda essa situação é causada pelo fato de que a restrição no tamanho do texto que pode ser transferido para a função criptográfica depende não apenas do modo de suplemento, mas também do tamanho da chave.Vamos tentar descobrir exatamente como é essa fórmula mágica, que determina a quantidade máxima de dados criptografados. Deve estar no métodoint GetMaxDataSizeForEnc(RSAEncryptionPadding pad)
, que calcula este volume, tendo recebido o modo de suplemento na entrada. A principal desvantagem desse método é que ele não existe, eu o inventei. Estou tentando transmitir a ideia de que mesmo as informações mais básicas que um desenvolvedor precisa para usar o RSA corretamente não estão disponíveis para nós. Obrigado Microsoft.Aqui estão as razões pelas quais o RSA deve ser evitado, mesmo para assinatura. Como espero ter conseguido mostrar, as APIs para RSA no .NET são extremamente insatisfatórias. Você é forçado a tomar muitas decisões em relação ao modo de suplemento, tamanho dos dados e similares, o que é indesejável. Além disso, para um nível de segurança de 128 bits, você precisará de pelo menos uma chave de 4 kilobytes muito volumosa. Ele fornecerá uma chave privada de kilobyte, uma chave pública de meio kilobyte e uma assinatura de meio kilobyte. Para muitos cenários, esses valores podem não ser desejáveis. E se você tentar alcançar um nível de segurança de 256 bits, precisará de uma chave enorme - 15360 bits. No RSA, usar essa chave é quase impossível. No meu laptop, uma dessas chaves é gerada em um minuto e meio.Além disso, o RSA em um nível fundamental, como algoritmo, implementa muito lentamente uma assinatura, independentemente da implementação. Por que a velocidade da assinatura é importante para nós? Se você usar TLS com certificados RSA, a assinatura será feita no servidor. E nós, como desenvolvedores, somos mais afetados exatamente pelo que acontece no servidor, somos responsáveis por isso, sua taxa de transferência é importante para nós. Em resumo, quero recomendar mais uma vez não usar o RSA.Quero recomendar novamente para não usar o RSA.Quero recomendar novamente para não usar o RSA.Nesse caso, o que pode substituir o RSA? Gostaria de apresentar a você as primitivas criptográficas elípticas modernas. Primeiro, lembre-se do ECDSA (Digital Signature Algorithm), que pode ser usado em vez do RSA para assinaturas. Nesta e nas seguintes abreviações, EC é um prefixo genérico que significa Elliptic-Curve ("elíptico"). Em securitydriven.net/inferno/#DSA Assinaturas, você pode encontrar um exemplo de código ECDSA, que, aliás, é nativo do .NET. Outro algoritmo importante é o ECIES (Esquema de Criptografia Integrado, “esquema de criptografia integrado elíptico”). Esse algoritmo pode executar criptografia híbrida em vez do RSA, ou seja, onde você gera uma chave simétrica, criptografa os dados com ela e, em seguida, criptografa a própria chave.O código de amostra está disponível no exemplo securitydriven.net/inferno/#ECIES. Finalmente, outro algoritmo muito importante é o ECDH (troca de chaves Diffie-Hellman, "troca de chaves Diffie-Hellman"). Permite criar chaves para criptografia simétrica entre duas partes com chaves públicas conhecidas. Em algumas situações e métodos de uso, ele permite sigilo direto (sigilo para a frente ). O link securitydriven.net/inferno/#DHM chave do código disponível amostra Exchange.Para resumir a conversa sobre criptografia assimétrica. Você sempre deve usar APIs de alto nível que não o forçam a tomar decisões para as quais você não está pronto. Eu também recomendaria parar de usar o RSA. Obviamente, é mais fácil falar do que fazer, pois todos trabalhamos com aplicativos grandes e já criados, que podem não ser totalmente reformulados. Nesse caso, pelo menos você precisa aprender a usar o RSA corretamente. Além disso, aconselho que você se familiarize com os modernos algoritmos criptográficos elípticos (ECDSA, ECDH, ECIES). Por fim, é importante que a criptografia de alto nível não resolva magicamente todos os problemas; portanto, é preciso lembrar os objetivos que você busca. Vou citar StackOverflow, com o qual concordo completamente: “A criptografia sozinha não resolve problemas.A criptografia simétrica apenas torna a privacidade de dados um problema de gerenciamento de chaves. ”Vou dizer algumas palavras sobre recursos que podem ser úteis para você. Existe uma biblioteca SecurityDriven.Inferno de nível superior relativamente aceitável, com boa documentação. Há um livro maravilhoso, Serious Cryptography, de Jean-Philippe Aumasson, Serious Cryptography. Ele fornece uma visão geral do estado atual da criptografia, levando em consideração as últimas inovações. Além disso, escrevi o livro já mencionado Application Security in .NET, Succinctly, que é de domínio público. Possui ainda mais informações sobre as armadilhas de segurança do .NET. Finalmente, o Slideshare tem uma excelente apresentação de Vladimir Kochetkov, que descreve os conceitos básicos da teoria de segurança de aplicativos de uma maneira um tanto simplista, mas muito sólida, e explica várias fontes de perigos.Como conclusão, vamos ver alguns exemplos adicionais que eu preparei. No começo, falei sobre o quarto estágio da iluminação criptográfica, no qual chega à conclusão de que a melhor solução pode não precisar de criptografia. Vejamos um exemplo dessa solução. Vamos dar uma olhada no mecanismo clássico do .NET - CSRF (falsificação de solicitação entre sites, "falsificação de solicitação entre sites"), projetado para proteger contra uma classe de ataques, incluindo falsificação de solicitação entre sites. Nesse modelo, temos um agente de usuário - geralmente um navegador. Ele tenta estabelecer uma conexão com o servidor enviando uma solicitação GET. Em resposta, o servidor envia um token CSRF, oculto no campo "oculto" do HTML. Além disso, o mesmo token é anexado à resposta como um cookie, como um cabeçalho.O usuário processa algum formulário e executa um POST, que retorna ao servidor com os dois tokens. O servidor verifica, em primeiro lugar, se os dois tokens foram enviados e, em segundo lugar, se eles correspondem. É essa comparação de identidade que permite que o servidor se proteja de um invasor. Esse é um mecanismo clássico incorporado ao ASP.NET e ao ASP.NET Core. Mikhail Shcherbakov fez um excelente relatório no qual o trabalho da CSRF foi investigado em detalhes.O problema com essa abordagem é que a geração de token CSRF usa criptografia. A dificuldade é que a criptografia em si é uma operação complexa e que consome recursos, carrega o processador, requer memória e aumenta o atraso. Tudo isso é indesejável. Além disso, a injeção de um token é um processo complicado, confuso e inconveniente. Em muitos casos - por exemplo, ao usar chamadas assíncronas AJAX -, sua implementação é sua como desenvolvedor. Quem fez isso sabe que essa atividade é extremamente desagradável.
A mesma proteção criptográfica ou comparável pode ser criada sem o uso de criptografia, como é mostrado no slide. Entendo que o texto aqui é bastante complicado, por isso estou pronto para discuti-lo com mais detalhes na área de discussão. Isso é tudo para mim, muito obrigado.. DotNext. DotNext 2018 Moscow — 22-23 2018 - « ».
. , , . ! .