Os programadores saudáveis comem cereais criptográficos todas as manhãs

ElevenPaths    13 diciembre, 2019
Os programadores saudáveis comem cereais criptográficos todas as manhãs

Qual é o pior pesadelo de um programador? A criptografia, segundo os autores do estudo Why does cryptographic software fail:

“Nosso estudo cobriu 269 vulnerabilidades criptográficas reportadas na base de dados CVE desde janeiro de 2011 até maio de 2014. Os resultados mostram que somente 17% dos erros se encontram nas bibliotecas criptográficas (o que podem ter consequências devastadoras) e o 83% restante são erros causados por uso indevido de bibliotecas criptográficas por parte de aplicações individuais. Observamos que a prevenção de erros em diferentes partes de um sistema requer técnicas diferentes e que não existem correções efetivas para tratar certos tipos de erros, como a geração de chaves criptográficas frágeis”.

E se olharmos para o mundo Android? Na pesquisa An Empirical Study of Cryptographic in Android Applications os autores contam que:

“Desenvolvemos técnicas de análise para avaliar automaticamente os apps da loja Google Play. Das 11.748 aplicações que utilizam APIs criptográficas, 88% (10.327) cometem ao menos um erro.”

O que esses estudos confirmam é que a criptografia raramente é o elo mais frágil na segurança, ela não existe somente nos livros de matemática mas para que funcione é necessários estar escrita em softwares, integrada a um sistema maior e mais complexos, integrada a um sistema operacional, executada em hardware, conectada a uma rede e operada por usuários. Cada um desses passos traz consigo dificuldades e vulnerabilidades:

Os programadores saudáveis comem cereais criptográficos todas as manhãs

São essas as falhas cometidas por desenvolvedores ao implementar a criptografia e que abrem caminho para o desastre. Esse artigo reúne os erros mais típicos que todo desenvolvedor deve evitar para criar aplicações que utilizam criptografia de maneira sólida.

Se reinventar a roda é uma má ideia, em criptografia causa o caos

O artigo Great Crypto Failures, assinado pelos investigadores da Checkpoint Software, revisam falhas épicas de implementação criptográficas em ransomware. Ao focar na criação de um código leve e único, os cibercriminosos criadores dessa ameaça de deparam com a necessidades de escrever suas próprias implementações de algoritmos criptográficos como AES ou RSA ao invés de recorrer a biblioteca padrão. Essa não é uma tarefa fácil, mais eficaz seria recorrer às bibliotecas criptográficas criadas por especialistas que sabem muito bem o que fazer.

Muitas linguagens de programação contam com uma ou várias APIs de criptografia. Por exemplo, o Java oferece a Java Cryptographic Extensions (JCE), enquanto o .NET tem as chaves de espaço de nomes System.Security.Cryptography. Assim como bibliotecas mais primitivas oferecidas por linguagens de programação, existem conjuntos abertos independentes como o OpenSSL, Bouncy Castle e o NaCl, para citar as mais populares e robustas.

Os programadores saudáveis comem cereais criptográficos todas as manhãs

Todas essas APIs proporcionam serviços como a criptografia propriamente dita, geração de chaves secretas, códigos de autenticação de mensagens, handshake de chaves, gestão de certificados, assinatura digital etc. Bingo! Use uma biblioteca e o problema estará resolvido! Certo?

Bem, não tão rápido. Na realidade, os problemas apenas começaram. É correto afirmar que estas APIs foram criadas para permitir que desenvolvedores de aplicações aplique facilmente a criptografia, mas elas fazem com que os desenvolvedores não se envolvam com os detalhes técnicos necessários para a correta implementação correta dos recursos. A codificação dos algoritmos é terceirizada aos provedores das APIs, que são os reais especialistas. O que pode dar errado?

A criptografia é mais difícil do que parece. Para o desespero dos programadores, as APIs oferecem uma ampla variedade de algoritmos diferentes que, por sua vez, admitem múltiplos modos e opções de configuração. Além disso, cada fornecedor pode utilizar algoritmos adicionais ou, pior ainda, proporcionar diferentes valores pré-determinados para a mesma chamada da API. Como resultado, os desenvolvedores enfrentam o desafio de utilizar e orquestrar corretamente todos os componentes de uma API, sem entender a fundo o seu funcionamento.

Se atrevam a entrar no túnel do terror criptográfico

Imagine que você é um programador Android, o rei da programação, que não recebeu qualquer conteúdo sobre criptografia na escola, um dia você precisa aplicar um recurso de segurança tão trivial como criptografar uma variável de texto. Como você resolveria o problema?

O primeiro passo é consultar o oráculo Google para saber se algo ali pode te ajudar, no entanto você vai precisar muito mais do que simplesmente criptografar a informação. Em uma apresentação recente, Maximilian Blochberger, criador da biblioteca criptográfica Tafelsatz, buscou no Google:

“Android encryption example”

E o resultado apresentado pelo sistema de busca em primeiro lugar foi:

SecretKeySpec skeySpec = new SecretKeySpec(raw, ”AES”);
Cipher cipher = Cipher.getInstance(”AES”);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
[…]
byte[] keyStart = ”this is a key”.getBytes();
KeyGenerator kgen = KeyGenerator.getInstance(”AES”);
SecureRandom sr = SecureRandom.getInstance(”SHA1PRNG”);
sr.setSeed(keyStart);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
byte[] encryptedData = encrypt(key,b);
byte[] decryptedData = decrypt(key,encryptedData);

Vamos analisar o código acima linha por linha:

 encrypt(byte[] raw, byte[] clear) 

Não se valida a extensão dos valores de entrada ou de saída. Todos sabemos quão perigoso é trabalhar com buffers que não tenham nenhum tipo de validação. Entre outros problemas, pode-se produzir transbordos, leitura para além do array e outros pequenos problemas.

 SecretKeySpec skeySpec = new SecretKeySpec(raw, ”AES”); 

Um desenvolvedor não tem por que saber criptografia. Ele só quer proteger uma cadeia de texto. Com qual algoritmo? Não se faz nenhuma ideia, desde que seja seguro. Todas as bibliotecas oferecem uma grande variedade de algoritmos. São todos eles seguros? Infelizmente não. Em um artigo anterior, listamos os algoritmos que não devem ser usados. Se você consultar o artigo irá encontrar o AES entre os seguros, então a linha acima está correta. Ou não?

Bem, não, não está porque no JCE “AES” é a abreviação de “AES/ECB/PKCS5PADDING”, que significa que a criptografia AES será usada no modo ECB de encadeamento de blocos e com a descrição do algoritmo de preenchimento escrita em PKCS#5. Se você leu o artigo sobre criptografia insegura, vai lembrar que o modo ECB é inseguro. Infelizmente, as próprias bibliotecas criptográficas podem não proporcionar opções seguras por padrão e sem as oferecem, podem usar algoritmos obsoletos por questões de compatibilidade com aplicações legadas.

SecureRandom sr = SecureRandom.getInstance(”SHA1PRNG”);

Se utiliza aqui o algoritmo SHA1, hoje considerado inseguro.

byte[] keyStart = ”this is a key”.getBytes();

Se inicializa a chave de criptografia com uma lenha lida a partir de um parâmetro estático. Isso é uma péssima ideia. Tão ruim que ninguém poderia pensar em usar o código como descrito acima, certo?

Na verdade, a derivação da chave de criptografia AES a partir de uma senha é um processo completamente falho. Existem protocolos muito mais seguros, como o PBKDF2.

byte[] encryptedData = encrypt(key,b);

Os dados de entrada são criptografados com a chave recém gerada. Se usamos a mesma chave para criptografar os mesmos dados, obteremos o mesmo resultado, falta diversificação! Em um bom protocolo de criptografia, mesmo que você use a mesma chave para cifrar a mesma mensagem, obterá sempre um resultado diferente. Isso é possível graças ao uso de aleatoriedade, como um vetor de inicialização aleatório, ou um contador como no modo CTR. Tecnicamente é necessário buscar que a criptográfica tenha a propriedade IND-CPA.

byte[] decryptedData = decrypt(key,encryptedData);

E, para terminar, que rufem os tambores, o cifrado não está autenticado o que garante a confidencialidade, mas não a autenticidade. Por exemplo, o AES-GCM proporciona ambos os recursos.

Para um pequeno código exemplo, que aparece como primeiro resultado na busca do Google quando se pesquisa por “Android encryption example”, o resultado é assustador. Não se pode estranhar que 88% dos apps tenham algum erro de criptografia.

Mas os problemas não acabam aqui. Inclusive, se você resolver todos os problemas demonstrados aqui, começarão aqueles realmente complicados. De onde o código vai acessar a senha do usuário? Onde vai armazenar a chave de criptografia gerada? Como vai gerenciar essa informação na memória durante a execução? De me maneira vai criptografar a própria execução? Quem tem acesso à essas chaves? Quantas vezes se reutiliza o mesmo usuário ou senha? Por quanto tempo essas informações são armazenadas? Se são exportadas para outros sistemas, como estarão protegidas? É uma lista enorme de perguntas, cujas respostas estão contidas todas no tema de gestão de chaves, uma disciplina complexa da criptografia.

O que você pode fazer?

Algumas dicas para quando você precisar de criptografia em seus programas

Se você seguir as seguintes dicas, não podemos garantir a segurança total, mas asseguramos que seu app não estará a caminho do desastre total.

1) Nunca invente algoritmos criptográficos, use os que já existem e que sejam reconhecidamente seguros.

No artigo “A criptografia insegura que você deveria deixar de usar” há uma lista de algoritmos considerados inseguros aqueles que devem ser utilizados em seu lugar. Por mais fácil que pareça inventar um algoritmo criptográfico, o processo não passa disso, aparência! Das raras vezem em que se explorou um sistema por causa da fragilidade de um algoritmo, as causas foram:

  • Utilização de um algoritmo obsoleto
  • Utilização de um algoritmo criado pelo próprio desenvolvedor

Não cometa esse erro!

2) Nunca codifique algoritmos criptográficos, use bibliotecas de confiança

Há tantos detalhes sutis que, se você não entende perfeitamente o funcionamento do algoritmo ou a biblioteca escolhida, o mais provável é que algo saia mal. Profissionais com conhecimento mais específico e avançado já trabalharam arduamente nesses componentes. Aproveite! Use uma API de criptografia confiável.

3) Entenda como rodam as funções e parâmetros da biblioteca criptográfica que você escolheu

A melhor biblioteca não servirá para nada se não for bem utilizada. Você terá que tomar decisões como:

  • O tipo de encadeamento de blocos, nunca use ECB. Use outros modos com vetores de inicialização aleatórios e diferentes a cada execução
  • O tamanho das chaves deve ser de 128 bits para criptografia simétrica, 3.072 bits ou mais para RSA e 256 bits ou mais para ECC
  • Utilizar ou não salts: use sempre, que eles sejam aleatórios e diferentes a cada chamada da aplicação
  • Qual o tipo de padding utilizar
  • Etc.

Existe um universo de bibliotecas disponíveis. Algumas delas, como a OpenSSL, são muito complexas e difíceis de usar mesmo para aqueles que entendem de criptografia, elas dão acesso à funções de baixo nível e isso significa que, para fazer tarefas complexas, como autenticar e cifrar uma mensagem, seja necessário invocar uma dúzia de funções diferentes. Quantas oportunidades para cometer um erro!

Por esse motivo, alguns profissionais de criptografia benevolentes, preocupados com o bem da humanidade, criaram bibliotecas intuitivas e fáceis de usar, com funções com o mais alto nível possível. No lugar de expor funções básicas, essas bibliotecas dão acesso às tarefas, como por exemplo “autenticar e criptografar uma mensagem” ou “assinar um documento”. É a própria API que se encarrega dos detalhes mais operacionais de cada ação.

Algumas bibliotecas que você pode avaliar e que usam essa filosofia são NaCl, Tafelsalz ou Libsodium, todas elas com o objetivo se tornar quase impossível cometer um erro de segurança na implementação.

4) Gerencia suas chaves

Utiliza chaves geradas aleatoriamente. Não use sementes estáticas ou que possam ser facilmente adivinhadas, como a hora. Você vai precisar de funções criptográficas seguras e sua API as terá, lembre que a função random() não serve!

Não use senhas como chaves. Use funções de derivação de chaves a partir de senhas. Não sofra, a sua API as terá.

Nunca use chaves constantes.

Se você usa criptografia assimétrica:

  • Não copie chaves privadas, nem as armazene e texto plano, você também não deve escrevê-las nos códigos dos seus apps (hard-code)
  • Evite usa a mesma chave pública para operadores diferentes: criptografia, autenticação, assinatura etc.

Uma programação ruim torna qualquer criptografia insegura

Por mais que uma biblioteca criptográfica seja a prova de erros, o programador tem que entender o que está fazendo, mesmo que superficialmente. A realidade é que muitos desenvolvedores não têm compreensão formal da aplicação da criptografia em suas aplicações, mesmo que sejam especialistas no desenvolvimento de software. Veja, não é necessário se converter no Cavaleiro Criptográfica da Ordem das Cifras, mas também não causará mal nenhum ler um bom tutorial sobre criptografia ou realizar algum curso online de qualidade. E se você quiser provar seus conhecimentos sobre o tema, enquanto escreve códigos, há bons desafios no Cryptopals.

Consuma seus cereais criptográficos todas as manhãs!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *