Skip to main content

Implementar TLS en Java

Escrito por

Himanish Munjal

blog feature toolkit

9 de noviembre de 2022

0 minutos de lectura

TLS, o seguridad de la capa de transporte, es un protocolo que se usa en todo el mundo para cifrar y proteger las comunicaciones por internet.

En este artículo, hablaremos sobre qué es TLS, qué beneficios ofrece y por qué lo necesitas. Luego veremos cómo implementar TLS en Java.

¿Qué es TLS?

TLS y su predecesor SSL (capa de sockets seguros) son los protocolos criptográficos más utilizados para proporcionar cifrado, autenticidad e integridad, lo que permite proteger de extremo a extremo los datos que se envían entre aplicaciones por internet.

Los protocolos TLS y SSL se ubican entre la capa de aplicación y la capa TCP/IP, donde pueden proteger los datos de las aplicaciones y enviarlos a la capa de transporte. Gracias a su ubicación, TLS y SSL son compatibles con varios protocolos de la capa de aplicación. Consulta este artículo para obtener más información sobre el modelo OSI.

SSL, desarrollado originalmente por Netscape, tenía varias fallas de seguridad en su versión 1.0, que nunca se publicó. En 1995 se presentó SSL 2.0, seguido de SSL 3.0 en 1996. Luego, SSL cambió de nombre y en 1999 se lanzó TLS 1.0. La versión actual, TLS 1.3, se presentó en 2018. Para protegerte de las vulnerabilidades conocidas, siempre se recomienda usar la versión más reciente de TLS. Puedes obtener más información sobre TLS en este artículo.

La importancia de TLS

Históricamente, la comunicación por internet se realizaba en texto sin formato y con una seguridad mínima. Esto significa que los datos que enviabas a un servidor eran visibles mientras viajaban por la red, por lo que quedaban expuestos a los ataques de intermediario, en los que un actor malicioso se inserta en la comunicación entre un usuario y una aplicación. Esto le permite ver y manipular los datos que se envían, lo que podría exponer información personal, como contraseñas o datos bancarios.

Para resolver este tipo de problema, se introdujeron SSL y TLS. Estos protocolos ofrecen tres beneficios principales:

  • Protección de la integridad: TLS protege los mensajes para que un intruso que espía la red no pueda modificarlos.

  • Autenticación: En la mayoría de los modos, TLS autentica a los pares mediante certificados firmados. Por lo general, se autentican los servidores y estos pueden solicitar la autenticación de los clientes. Esta autenticación se realiza durante el protocolo de enlace de TLS.

  • Confidencialidad: TLS cifra los datos que se envían entre el cliente y el servidor, lo que protege su confidencialidad y privacidad. Así, quienes espíen la red de forma pasiva no podrán ver los datos confidenciales que se comparten entre las máquinas.

Cuando se implementa SSL o TLS, los usuarios pueden tener la certeza de que sus datos no quedan expuestos y de que la información que envían o reciben no se altera. Por eso, implementar TLS es indispensable para las aplicaciones conectadas a internet, ya que te permite proteger los datos de tus clientes.

Conceptos básicos

Antes de entrar en los detalles sobre cómo implementar TLS en Java, repasemos algunos conceptos que te ayudarán a entender los aspectos técnicos de TLS.

Sockets

Un socket es uno de los extremos de una interfaz de comunicación bidireccional entre programas conectados a una red. La capa de transporte subyacente (TCP o UDP) identifica la aplicación de destino a la que se transmitirán los datos al vincular un socket a un número de puerto.

Las clases de socket representan la conexión entre las aplicaciones cliente y servidor. El paquete java.net proporciona dos clases, Socket y ServerSocket, que implementan, respectivamente, el lado cliente y el lado servidor de la conexión.

Si quieres obtener más información sobre los sockets, consulta este artículo.

La comunicación TLS se realiza mediante sockets seguros: SSLSocket y SSLServerSocket. Estas clases extienden, respectivamente, las clases Socket y ServerSocket.

Protocolo de enlace de TLS

El protocolo de enlace SSL o TLS es una serie de acciones que realizan el cliente y el servidor para autenticarse mutuamente y establecer las claves secretas que usarán para comunicarse.

El protocolo de enlace de TLS es un tema muy amplio que va más allá del alcance de este artículo. Si necesitas un buen punto de partida para entenderlo, puedes consultar este artículo sobre qué sucede durante un protocolo de enlace de TLS y este resumen de los protocolos de enlace SSL o TLS.

Java LTS

Las versiones de Java LTS (soporte a largo plazo) reciben actualizaciones trimestrales de seguridad, estabilidad y rendimiento durante un período prolongado. Las versiones estándar (que no son LTS) solo reciben actualizaciones y correcciones hasta que se lanza la siguiente versión.

Java 7, 8, 11 y la versión lanzada recientemente, Java 17, son versiones LTS. Debido a sus períodos de soporte extendidos, se recomienda usar exclusivamente versiones LTS en los entornos de producción.

Como ocurre con cualquier software, cada versión incluye nuevas funciones y mejoras. Puedes consultar las funciones incluidas en Java 11 aquí y en Java 17 aquí. Como las funciones varían según la versión de Java, consulta la documentación correspondiente si encuentras algún problema inesperado al implementar TLS.

Implementar TLS en Java

Después de toda la teoría, por fin llegamos a la parte más interesante: la implementación. En las siguientes secciones, conocerás el framework JSSE y cómo usar sus API para implementar TLS.

API de JSSE

Java Secure Socket Extension, o JSSE, es un framework que abstrae la implementación subyacente de la seguridad de la capa de transporte (TLS). Al abstraer la complejidad de los protocolos subyacentes, JSSE permite que los programadores implementen fácilmente comunicaciones cifradas y seguras. JSSE usa el protocolo TLS para ofrecer comunicaciones cifradas y seguras entre tus clientes y servidores.

JSSE proporciona cuatro clases principales como implementación estándar:

  • SSLSocketFactory: esta clase funciona como una fábrica para crear sockets seguros.

  • SSLServerSocketFactory: esta clase es similar a SSLSocketFactory, pero se usa específicamente para crear sockets de servidor.

  • SSLSocket: SSLSocket es una extensión de Socket que agrega una capa de protección sobre el protocolo de transporte de red subyacente, como TCP y UDP, y ofrece los beneficios de SSL y TLS.

  • SSLServerSocket: esta clase es similar a SSLSocket. La principal diferencia es que SSLServerSocket se usa para crear sockets en el lado del servidor, mientras que SSLSocket se usa para crearlos en el lado del cliente.

Usarás estas clases para implementar TLS en el lado del cliente y del servidor.

Implementar TLS en el lado del cliente

En esta sección, implementarás TLS en el lado del cliente con SSLSocketFactory y SSLSocket.

Crea un archivo de texto llamado JavaTLSClientExample.class, copia y pega el siguiente código en él y guarda el archivo:

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 ejecutar el código, abre la terminal y usa el comando cd para ir a la carpeta donde guardaste el archivo anterior: cd <PathToFile>.

Compila el archivo con el comando javac JavaTLSClientExample.class. Luego, ejecuta el archivo con el comando java JavaTLSClientExample.class.

A continuación, se explica lo que ocurre en cada paso del código de ejemplo.

El primer paso consiste en obtener un SSLSocketFactory, que luego se puede usar para crear el socket.

Hay varias formas de obtener un SSLSocketFactory. En este ejemplo, se obtiene la fábrica predeterminada mediante el método estático SSLSocketFactory.getDefault().

El segundo paso consiste en crear un socket SSL. Llama al método createSocket y proporciona información como el host remoto y el puerto remoto. Esto crea un socket y lo conecta al host remoto especificado en el puerto indicado.

En el tercer paso, se configuran los parámetros. Primero, se indica qué versión de TLS debe admitir el socket. Al momento de escribir este artículo, TLS v1.3 es la versión más reciente, por lo que se usa en el ejemplo anterior. Además, se usa CipherSuite TLS_AES_128_GCM_SHA256 para especificar los distintos conjuntos de parámetros que se usarán en cada paso del protocolo de enlace de TLS.

En el cuarto paso, se inicia el protocolo de enlace. Aquí es donde se inicia explícitamente el protocolo de enlace de TLS.

Durante el protocolo de enlace, el socket usa el protocolo y el CipherSuite que se configuraron en el paso anterior.

En el quinto paso, la configuración está lista y se envía una solicitud al servidor. En este paso, se envía información de texto al flujo de salida del socket. OutputStreamWriter convierte el flujo de caracteres en un flujo de bytes. BufferedWriter evita invocar el convertidor con demasiada frecuencia, y PrintWriter imprime la representación formateada de los objetos en el flujo de salida de texto. Como PrintWriter no genera excepciones, debes comprobarlas explícitamente con el método checkError.

Por último, en el sexto paso, se lee la respuesta. Una vez enviada la solicitud, la respuesta se lee desde inputStream del socket y luego se imprime.

Implementar TLS en el lado del servidor

Ahora veremos cómo implementar TLS en el lado del servidor con SSLServerSocketFactory y SSLServerSocket.

Crea un archivo llamado JavaTLSServerExample.class y pega el siguiente código:

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 ejecutar el código, vuelve a abrir una terminal en la carpeta donde guardaste el archivo anterior.

Compila el archivo con el comando javac JavaTLSServerExample.class.

Sigue esta guía para configurar y agregar un certificado de CA a tu almacén de claves.

Ejecuta java -Djavax.net.ssl.keyStore=<KeystoreFilePath> -Djavax.net.ssl.keyStorePassword=<KeystorePassword> JavaTLSServerExample para ejecutar el archivo. Djavax.net.ssl.keyStore y -Djavax.net.ssl.keyStorePassword son necesarios para proporcionar al entorno de ejecución de Java el certificado SSL del servidor.

Repasemos una vez más cada paso del código. Los pasos del uno al tres son similares a los pasos del uno al tres del ejemplo de TLS en el lado del cliente.

En el primer paso, obtienes un SSLSocketFactory predeterminado.

En el segundo paso, creas un SSLServerSocket y lo vinculas al puerto 8990.

En el tercer paso, defines los protocolos y CipherSuites que admite tu SSLServerSocket.

En el cuarto paso, aceptas la solicitud entrante. SSLServerSocket.accept() espera a que se establezca una conexión con este socket de servidor, la acepta y crea un socket nuevo. El método se bloquea hasta que se establece una conexión.

En el quinto paso, se leen los datos del flujo de entrada del socket y se imprimen. InputStreamReader convierte el flujo de bytes en un flujo de caracteres, y BufferedReader evita invocar el convertidor con demasiada frecuencia.

Relación entre la API de JSSE y SSLSocketFactory

Diagrama que muestra las API estándar de JSSE, incluidas SSLSocketFactory, SSLServerSocketFactory, SSLSocket y SSLServerSocket.

Como mencionamos anteriormente, JSSE es un framework. JSSE proporciona varias clases e implementaciones que abstraen la complejidad de proteger la conexión entre el cliente y el servidor. También proporciona un conjunto de clases de implementación que pueden usarse como implementaciones predeterminadas para configurar sockets seguros en el cliente y el servidor.

En el lado del cliente, se usa SSLSocketFactory para obtener una fábrica predeterminada, que luego se usa para obtener un SSLSocket del lado del cliente. En el lado del servidor, se usa SSLServerSocketFactory para iniciar SSLServerSocket.

Conclusión

Si creaste una aplicación Java que se comunica por internet, es necesario implementar TLS tanto en el lado del cliente como en el del servidor. TLS garantiza que los datos se transfieran de forma segura mediante el cifrado y la protección de su integridad, y también ayuda a autenticar a ambas partes.

JSSE es un framework proporcionado por Java que puede ayudarte a implementar TLS en tu propia aplicación. Puedes usar sus clases, como SSLSocket y SSLServerSocket, para crear sockets seguros.

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

illustration hero ai
Blog

El huracán de la IA ha llegado

La IA está acelerando por igual la creación de software y los ciberataques. Los líderes deben proteger los agentes y el código desde el inicio, aplicar controles en tiempo de ejecución y validar las defensas de forma independiente.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.