Skip to main content

Implementando TLS em Java

Escrito por

Himanish Munjal

blog feature toolkit

9 de novembro de 2022

0 minutos de leitura

TLS, ou segurança da camada de transporte, é um protocolo usado no mundo todo para criptografar e proteger a comunicação pela internet.

Neste artigo, vamos explicar o que é TLS, quais benefícios ele oferece e por que você precisa dele. Em seguida, vamos mostrar como implementar TLS em Java.

O que é TLS?

TLS e seu antecessor SSL (secure socket layer) são os protocolos criptográficos mais usados para oferecer criptografia, autenticidade e integridade, permitindo a segurança de ponta a ponta dos dados enviados entre aplicações pela internet.

Os protocolos TLS e SSL ficam entre a camada de aplicação e a camada TCP/IP, onde podem proteger e enviar dados da aplicação para a camada de transporte. Por estarem nessa posição, TLS e SSL são compatíveis com vários protocolos da camada de aplicação. Confira este artigo para saber mais sobre o modelo OSI.

Desenvolvido originalmente pela Netscape, o SSL 1.0 tinha várias falhas de segurança e nunca foi lançado publicamente. Em 1995, foi introduzido o SSL 2.0, seguido pelo SSL 3.0 em 1996. Depois, o SSL foi renomeado, e o TLS 1.0 foi lançado em 1999. A versão atual, TLS 1.3, foi introduzida em 2018. Para se proteger contra vulnerabilidades conhecidas, é sempre recomendável usar a versão mais recente do TLS. Você pode saber mais sobre TLS neste artigo.

A importância do TLS

Historicamente, a comunicação pela internet era feita em texto simples, com pouca segurança. Isso significa que os dados enviados a um servidor ficavam visíveis durante a transmissão, deixando-os vulneráveis a ataques man-in-the-middle — quando uma pessoa mal-intencionada se insere na comunicação entre um usuário e uma aplicação. Assim, ela pode ver e manipular os dados enviados, expondo potencialmente informações pessoais, como senhas ou dados bancários.

Para resolver esse tipo de problema, foram introduzidos o SSL e o TLS. Eles oferecem três benefícios principais:

  • Proteção da integridade: o TLS protege as mensagens contra alterações feitas por alguém que esteja monitorando ativamente a transmissão.

  • Autenticação: na maioria dos modos, o TLS autentica as partes usando certificados assinados. Os servidores geralmente são autenticados, e os clientes podem ser autenticados quando os servidores solicitam. Essa autenticação ocorre durante o handshake do TLS.

  • Confidencialidade: o TLS criptografa os dados enviados entre cliente e servidor, protegendo sua confidencialidade e privacidade. Isso impede que pessoas que monitorem passivamente a transmissão vejam dados confidenciais compartilhados entre as máquinas.

Com o SSL ou o TLS implementado, os usuários podem ter a certeza de que seus dados não serão expostos nem adulterados durante o envio ou o recebimento. Por isso, implementar TLS é essencial para aplicações acessíveis pela internet: ele ajuda você a proteger os dados dos seus clientes.

Conceitos fundamentais

Antes de explicar em detalhes como implementar TLS em Java, vamos revisar alguns conceitos que ajudarão você a entender os aspectos mais técnicos do TLS.

Sockets

Um socket é uma das extremidades de uma interface de comunicação bidirecional entre programas conectados em rede. A camada de transporte subjacente (TCP ou UDP) identifica a aplicação de destino para a qual os dados serão transmitidos, associando um socket a um número de porta.

As classes de socket representam a conexão entre aplicações cliente e servidor. O pacote java.net oferece duas classes — Socket e ServerSocket — que implementam, respectivamente, o lado cliente e o lado servidor da conexão.

Para saber mais sobre sockets, confira este artigo.

A comunicação TLS ocorre por meio de sockets seguros: SSLSocket e SSLServerSocket. Essas classes estendem, respectivamente, as classes Socket e ServerSocket.

Handshake TLS

O handshake SSL ou TLS é uma sequência de ações executadas pelo cliente e pelo servidor para que um autentique o outro e estabeleça as chaves secretas que serão usadas na comunicação.

O handshake TLS é um assunto amplo, que vai além do escopo deste artigo. Se você quer começar a entender os handshakes TLS, este artigo sobre o que acontece durante um handshake TLS e esta visão geral dos handshakes SSL ou TLS são ótimos pontos de partida.

Java LTS

As versões LTS (suporte de longo prazo) do Java recebem atualizações trimestrais de segurança, estabilidade e desempenho por um período prolongado. Já as versões padrão (não LTS) recebem atualizações e correções apenas até o lançamento da próxima versão.

Java 7, 8, 11 e o recém-lançado Java 17 são versões LTS. Devido ao período de suporte prolongado, é recomendável usar exclusivamente versões LTS em ambientes de produção.

Como acontece com qualquer software, cada versão traz novos recursos e melhorias. Você encontra os recursos lançados no Java 11 aqui e no Java 17 aqui. Como os recursos variam entre as versões do Java, consulte a documentação da sua versão se encontrar algum problema inesperado ao implementar TLS.

Implementando TLS em Java

Depois de toda a teoria, chegamos à parte mais interessante: a implementação. Nas próximas seções, você vai conhecer o framework JSSE e aprender a usar as APIs do JSSE para implementar TLS.

API JSSE

Java Secure Socket Extension, ou JSSE, é um framework que abstrai a implementação subjacente da segurança da camada de transporte (TLS). Ao abstrair a complexidade dos protocolos subjacentes, o JSSE permite que programadores implementem facilmente comunicações seguras e criptografadas. O JSSE usa o protocolo TLS para oferecer comunicações seguras e criptografadas entre clientes e servidores.

O JSSE oferece quatro classes principais como implementação padrão:

  • SSLSocketFactory: essa classe funciona como uma fábrica para criar sockets seguros.

  • SSLServerSocketFactory: essa classe é semelhante à classe SSLSocketFactory, mas é usada especificamente para criar sockets de servidor.

  • SSLSocket: SSLSocket estende Socket adicionando proteções de segurança ao protocolo de transporte de rede subjacente, como TCP e UDP, e oferece os benefícios do SSL e do TLS.

  • SSLServerSocket: essa classe é semelhante à classe SSLSocket. A principal diferença é que SSLServerSocket é usada para criar sockets no lado do servidor, enquanto SSLSocket é usada para criar sockets no lado do cliente.

Você usará essas classes para implementar TLS no lado do cliente e do servidor.

Implementando TLS no lado do cliente

Nesta seção, você vai implementar TLS no lado do cliente usando SSLSocketFactory e SSLSocket.

Crie um arquivo de texto chamado JavaTLSClientExample.class, copie e cole o código a seguir nele e salve o arquivo:

package io.snyk.programming.tls;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

public class JavaTLSClientExample {

    private static final String[] protocols = new String[]{"TLSv1.3"};
    private static final String[] cipher_suites = new String[]{"TLS_AES_128_GCM_SHA256"};

    public static void main(String[] args) throws Exception {

        SSLSocket socket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {

            // Step : 1
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();

            // Step : 2
            socket = (SSLSocket) factory.createSocket("google.com", 443);

            // Step : 3
            socket.setEnabledProtocols(protocols);
            socket.setEnabledCipherSuites(cipher_suites); 

            // Step : 4 {optional}
            socket.startHandshake(); 

            // Step : 5
            out = new PrintWriter(
                    new BufferedWriter(
                            new OutputStreamWriter(
                                    socket.getOutputStream())));

            out.println("GET / HTTP/1.0");
            out.println();
            out.flush();

            if (out.checkError()) {
                System.out.println("SSLSocketClient:  java.io.PrintWriter error");
            }

            // Step : 6
            in = new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()));

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                socket.close();
            }
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
        }
    }
}

Para executar o código, abra o terminal e use o comando cd para acessar a pasta onde você salvou o arquivo acima — cd <PathToFile>.

Compile o arquivo executando javac JavaTLSClientExample.class. Em seguida, execute-o com o comando java JavaTLSClientExample.class.

Veja o que acontece em cada etapa do código de exemplo.

A primeira etapa é obter um SSLSocketFactory, que poderá ser usado para criar o socket.

Há várias maneiras de obter um SSLSocketFactory. Neste exemplo, você obtém a fábrica padrão chamando o método estático SSLSocketFactory.getDefault().

Na segunda etapa, você cria um socket SSL. Para isso, chama o método createSocket e informa dados como o host e a porta remotos. Isso cria um socket e o conecta ao host remoto especificado, na porta indicada.

Na terceira etapa, você configura os parâmetros. Primeiro, informa ao socket qual versão do TLS deve ser compatível. No momento da redação deste artigo, o TLS v1.3 é a versão mais recente, por isso é usado no exemplo acima. Além disso, a CipherSuite TLS_AES_128_GCM_SHA256 especifica os diferentes conjuntos de parâmetros usados em cada etapa do handshake TLS.

Na quarta etapa, você inicia o handshake TLS explicitamente.

Durante o handshake, o socket usa o protocolo e a CipherSuite definidos na etapa anterior.

Na quinta etapa, a configuração está concluída e você faz uma solicitação ao servidor. Nessa etapa, informações de texto são enviadas ao fluxo de saída do socket. OutputStreamWriter converte o fluxo de caracteres em um fluxo de bytes. BufferedWriter evita chamadas frequentes ao conversor, e PrintWriter imprime a representação formatada dos objetos no fluxo de saída de texto. Como PrintWriter não gera exceções, você precisa verificar explicitamente se houve erros usando o método checkError.

Por fim, na sexta etapa, você lê a resposta. Depois de fazer a solicitação, a resposta é lida do inputStream do socket e, em seguida, impressa.

Implementando TLS no lado do servidor

Agora, você vai ver como implementar TLS no lado do servidor usando SSLServerSocketFactory e SSLServerSocket.

Crie um arquivo chamado JavaTLSServerExample.class e cole nele o código a seguir:

package io.snyk.programming.tls;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.*;

public class JavaTLSServerExample {

    private static final String[] protocols = new String[]{"TLSv1.3"};
    private static final String[] cipher_suites = new String[]{"TLS_AES_128_GCM_SHA256"};

    public static void main(String[] args) throws Exception {

        SSLServerSocket serverSocket = null;

        try {

            // Step : 1
            SSLServerSocketFactory factory =
                    (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

            // Step : 2
            serverSocket = (SSLServerSocket) factory.createServerSocket(8980);

            // Step : 3
            serverSocket.setEnabledProtocols(protocols);
            serverSocket.setEnabledCipherSuites(cipher_suites);

            // Step : 4
            SSLSocket sslSocket = (SSLSocket) serverSocket.accept();

            // Step : 5
            InputStream inputStream = sslSocket.getInputStream();
            InputStreamReader inputStreamReader = new
                    InputStreamReader(inputStream);

            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String request = null;
            while((request = bufferedReader.readLine()) != null) {
                 System.out.println(request);
                 System.out.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}

Para executar o código, abra novamente um terminal na pasta onde o arquivo acima foi salvo.

Compile o arquivo executando javac JavaTLSServerExample.class.

Siga este guia para configurar e adicionar um certificado de CA ao seu keystore.

Execute java -Djavax.net.ssl.keyStore=<KeystoreFilePath> -Djavax.net.ssl.keyStorePassword=<KeystorePassword> JavaTLSServerExample para executar o arquivo. Djavax.net.ssl.keyStore e -Djavax.net.ssl.keyStorePassword são necessários para fornecer ao runtime Java o certificado SSL do servidor.

Vamos revisar cada etapa do código mais uma vez. As etapas de um a três são semelhantes às etapas de um a três do exemplo de TLS no lado do cliente.

Na primeira etapa, você obtém um SSLSocketFactory padrão.

Na segunda etapa, você cria um SSLServerSocket e o associa à porta 8990.

Na terceira etapa, você define os protocolos e as CipherSuites compatíveis com seu SSLServerSocket.

Na quarta etapa, você aceita a solicitação recebida. SSLServerSocket.accept() aguarda uma conexão com esse socket de servidor, aceita a conexão e cria um novo socket. O método fica bloqueado até que uma conexão seja estabelecida.

Na quinta etapa, os dados são lidos do fluxo de entrada do socket e impressos. InputStreamReader converte o fluxo de bytes em um fluxo de caracteres, e BufferedReader evita chamadas frequentes ao conversor.

Relação entre a API JSSE e SSLSocketFactory

Diagrama que mostra as APIs padrão do JSSE, incluindo SSLSocketFactory, SSLServerSocketFactory, SSLSocket e SSLServerSocket.

Como mencionado acima, o JSSE é um framework. Ele oferece várias classes e implementações que abstraem a complexidade de proteger a conexão entre cliente e servidor. O JSSE também fornece um conjunto de classes de implementação que podem ser usadas como padrão para configurar sockets seguros no cliente e no servidor.

No lado do cliente, SSLSocketFactory é usada para obter uma fábrica padrão, que por sua vez é usada para obter um SSLSocket do cliente. No lado do servidor, SSLServerSocketFactory é usada para iniciar o SSLServerSocket.

Conclusão

Se você criou uma aplicação Java que se comunica pela internet, é necessário implementar TLS tanto no lado do cliente quanto no do servidor. O TLS garante a transferência segura dos dados por meio de criptografia e integridade, além de ajudar a autenticar as duas partes.

O JSSE é um framework fornecido pelo Java que pode ajudar você a implementar TLS na sua aplicação. Você pode usar classes como SSLSocket e SSLServerSocket para criar sockets seguros.

Leia mais

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.

feature insights context
Blog

A prevenção é essencialmente um problema resolvido?

A prevenção em código gerado por agentes está resolvida do ponto de vista arquitetural — mas escolher controles que protejam a segurança sem desacelerar o desenvolvimento continua sendo um desafio.