Skip to main content

Como proteger algoritmos de criptografia simétrica em Java

Escrito por
blog feature pypi spoof

18 de outubro de 2023

0 minutos de leitura

No mundo conectado em que vivemos, proteger os dados digitais se tornou uma prioridade. Com a ampla adoção de aplicações Java em diversos setores, do bancário ao de saúde, precisamos destacar a importância da criptografia. Criptografar é transformar dados legíveis, ou texto simples, em dados ilegíveis, ou texto cifrado. Assim, mesmo que os dados criptografados sejam interceptados, pessoas não autorizadas não poderão acessá-los.

A primeira decisão que você precisa tomar como desenvolvedor é se precisa de criptografia. Embora pareça uma pergunta estranha, o princípio fundamental da criptografia é que o texto cifrado pode ser revertido para o texto original. No caso de senhas, por exemplo, não queremos usar criptografia, pois não queremos conseguir “descriptografar” o texto original. Por isso, usamos hashing para senhas, e não criptografia.

Criptografia em Java

As aplicações Java costumam lidar com dados confidenciais, como informações de clientes, dados financeiros e registros de transações. Como desenvolvedor Java, usar algoritmos de criptografia robustos é essencial para manter a integridade e a confidencialidade desses dados. Isso protege seus usuários e todos os dados essenciais armazenados no seu sistema.

Criptografia simétrica e assimétrica

De modo geral, podemos distinguir dois tipos de criptografia: simétrica e assimétrica. Cada tipo funciona de maneira fundamentalmente diferente e atende a casos de uso distintos.

A criptografia simétrica usa a mesma chave para criptografar e descriptografar. Por isso, é mais rápida, mas menos segura quando é necessário compartilhar a chave. Já a criptografia assimétrica usa um par de chaves (pública e privada). A chave pública é usada para criptografar, e a privada, para descriptografar. A criptografia simétrica é indicada para situações em que grandes volumes de dados precisam ser criptografados, enquanto a assimétrica é ideal para compartilhar chaves com segurança pela rede. Embora existam esses dois tipos de criptografia, neste artigo vamos nos concentrar apenas na criptografia simétrica em aplicações Java.

Ao usar criptografia simétrica, o ideal é recorrer a serviços que utilizem módulos de segurança de hardware (HSMs) para gerenciar chaves e padrões de criptografia envelope. Isso simplifica o gerenciamento de chaves, que é a parte mais difícil da solução de criptografia. No entanto, se você não puder usar um serviço como o Amazon Key Management Service (KMS), mas ainda quiser implementar essa abordagem, as informações a seguir mostram como fazer isso com segurança. No restante deste artigo, vamos nos concentrar na implementação de criptografia simétrica na sua aplicação Java usando os pacotes javax.crypto.

Entenda os riscos de algoritmos de criptografia obsoletos

Apesar da importância da criptografia, nem todos os algoritmos são iguais. Alguns algoritmos mais antigos ou “obsoletos”, como DES (Data Encryption Standard) ou 3DES, apresentam vulnerabilidades que podem ser exploradas por invasores. Esses algoritmos já foram considerados seguros, mas os avanços no poder computacional e nas técnicas de criptoanálise os tornaram inseguros.

O uso de algoritmos de criptografia inseguros nas suas aplicações Java pode trazer riscos. Um invasor que consiga acessar seus dados criptografados talvez consiga descriptografá-los se o algoritmo estiver obsoleto e tiver vulnerabilidades conhecidas. Isso pode resultar em uma violação de dados, com consequências potencialmente devastadoras para seus usuários e sua organização.

Recomendações de algoritmos e modos de cifra

Para algoritmos de criptografia simétrica, a fundação OWASP recomenda atualmente o AES (Advanced Encryption Standard) com uma chave de pelo menos 128 bits, de preferência 256 bits. Além disso, você deve usar o algoritmo com um modo seguro. Os padrões e diretrizes de criptografia do NIST também aprovam o AES como uma técnica de cifra de bloco.

Uma cifra como o AES oferece vários modos, cada um com características diferentes de segurança e desempenho. Quanto aos modos, evite o ECB (Electronic Codebook), que não oferece confidencialidade adequada às mensagens. Em vez disso, use modos como CCM (Counter with CBC-MAC) ou GCM (Galois/Counter Mode). Ambos são modos autenticados e garantem a integridade, a confidencialidade e a autenticidade dos dados.

Como identificar algoritmos de criptografia fracos em Java

É relativamente fácil usar as APIs javax.crypto para implementar um pequeno serviço de criptografia em Java. A seguir, veja um exemplo de EncryptionService em Java que usa o algoritmo obsoleto DES com o modo ECB.

public class EncryptionServiceDes {

    private SecretKey secretKey;
    private Cipher cipher;

    public EncryptionServiceDes(String key) throws GeneralSecurityException {
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        DESKeySpec desKeySpec = new DESKeySpec(keyBytes);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        secretKey = keyFactory.generateSecret(desKeySpec);
        cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
    }

    public String encrypt(String original) throws GeneralSecurityException {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        // Encrypt the original data
        byte[] encryptedData = cipher.doFinal(original.getBytes(StandardCharsets.UTF_8));
        // Encode the encrypted data in base64 for better handling
        return Base64.getEncoder().encodeToString(encryptedData);
    }
    public String decrypt(String cypher) throws GeneralSecurityException{
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        // Decode the base64-encoded ciphertext
        byte[] encryptedData = Base64.getDecoder().decode(cypher);
        // Decrypt the data
        byte[] decryptedData = cipher.doFinal(encryptedData);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }
}

Agora que adicionamos a um projeto um algoritmo de criptografia conhecido por ser inseguro, vamos usar o serviço gratuito da Snyk para detectar esse uso inseguro e encontrar uma solução melhor.

Primeiro, crie uma conta gratuita na Snyk. Você pode começar facilmente usando sua conta do GitHub, Bitbucket, Azure AD ou Docker.

Em seguida, conectei meu repositório do GitHub à Snyk. A análise estática do Snyk Code me alerta imediatamente que minha aplicação usa um algoritmo criptográfico vulnerável ou arriscado. O alerta está correto, pois há várias referências a DES no serviço acima. O mecanismo do Snyk Code também recomenda considerar o uso do AES, de acordo com as orientações da OWASP.

A análise de segurança do código sinaliza DES/ECB/PKCS5Padding como um algoritmo criptográfico vulnerável, com CWE-327 e pontuação 828.

Embora use AES no breve trecho de código abaixo, estou usando o modo CBC, que não é considerado seguro.

…
    public EncryptionServiceAES(String key) throws GeneralSecurityException {
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        secretKey = new SecretKeySpec(keyBytes, "AES");
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    }
…

A Snyk também identifica o uso de modos inseguros, entre outros problemas. A captura de tela abaixo foi feita no plugin da Snyk, que analisa meu código no IntelliJ IDEA e fornece informações úteis, como exemplos de correções externas.

Relatório de vulnerabilidade da Snyk mostrando um alerta sobre um algoritmo criptográfico inseguro para AES/CBC e uma correção de código usando AES/GCM.

Alterar o EncryptionService para uma versão que use AES/GCM/NoPadding, como a seguir, é uma solução muito melhor do que a anterior e está de acordo com as recomendações atuais da OWASP.

public class EncryptionServiceAESGCM {
    private SecretKey secretKey;
    private Cipher cipher;

    public EncryptionServiceAESGCM(String key) throws GeneralSecurityException {
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        secretKey = new SecretKeySpec(keyBytes, "AES");
        cipher = Cipher.getInstance("AES/GCM/NoPadding");
    }

    public String encrypt(String original) throws GeneralSecurityException{
        byte[] iv = new byte[16]; // Initialization Vector
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv); // Generate a random IV
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
        byte[] encryptedData = cipher.doFinal(original.getBytes());
        var encoder = Base64.getEncoder();
        var encrypt64 = encoder.encode(encryptedData);
        var iv64 = encoder.encode(iv);
        return new String(encrypt64) + "#" + new String(iv64);
    }

    public String decrypt(String cypher) throws GeneralSecurityException{
        var split = cypher.split("#");
        var decoder = Base64.getDecoder();
        var cypherText = decoder.decode(split[0]);
        var iv = decoder.decode(split[1]);
        var paraSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, paraSpec);
        byte[] decryptedData = cipher.doFinal(cypherText);
        return new String(decryptedData);
    }
}

Revise continuamente os algoritmos de criptografia nas suas aplicações Java

Atualizar algoritmos de criptografia obsoletos é essencial, mas também é importante monitorar continuamente sua base de código em busca de novos problemas de segurança. Os algoritmos considerados seguros no momento em que este artigo foi escrito certamente estarão obsoletos em alguns anos.

Por isso, a análise proativa para identificar esses problemas deve ser contínua. Isso vale não apenas para algoritmos de criptografia, mas também para outros problemas no código que você mantém. Adotar e usar uma ferramenta de teste de segurança por análise estática (SAST), como o Snyk Code, ajuda você a reconhecer, identificar e corrigir esses problemas com facilidade, antes que causem danos.

Use o Snyk Code para Java gratuitamente

O Snyk Code é uma ferramenta de ponta para testes de segurança de aplicações estáticas (SAST) que identifica vulnerabilidades de segurança na sua base de código. Ele oferece suporte a várias linguagens de programação, incluindo Java, analisando o código e fornecendo feedback em tempo real aos desenvolvedores. Você pode integrar o Snyk Code ao seu pipeline de CI/CD para analisar o código automaticamente a cada commit e identificar e corrigir problemas de segurança logo no início do ciclo de desenvolvimento. Além disso, você pode conectá-lo ao seu repositório Git, usá-lo pela linha de comando com nossa CLI e integrá-lo a todas as principais IDEs para Java.

Assim, você identifica problemas no código desde cedo e em todas as etapas do ciclo de desenvolvimento. E o melhor: você pode usar a ferramenta gratuitamente. Basta se inscrever na Snyk abaixo.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Os exemplos de código deste post estão publicados no GitHub para você testar por conta própria. Se quiser conferir mais recursos sobre segurança para desenvolvedores Java, veja:

Leia mais

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está ultrapassando os fluxos de trabalho tradicionais de segurança. O Agentic AppSec da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

illustration hero ai
Blog

O furacão da IA chegou

A IA está acelerando tanto a criação de software quanto os ataques cibernéticos. As lideranças devem proteger agentes e código desde o início, aplicar controles em tempo de execução e validar as defesas de forma independente.