Skip to main content

TLS in Java implementieren

Artikel von

Himanish Munjal

blog feature toolkit

9. November 2022

0 Min. Lesezeit

TLS (Transport Layer Security) ist ein weltweit eingesetztes Protokoll, das die Kommunikation im Internet verschlüsselt und absichert.

In diesem Artikel erfahren Sie, was TLS ist, welche Vorteile es bietet und warum Sie es benötigen. Anschließend zeigen wir Ihnen, wie Sie TLS in Java implementieren.

Was ist TLS?

TLS und sein Vorgänger SSL (Secure Socket Layer) sind die am häufigsten verwendeten kryptografischen Protokolle. Sie sorgen für Verschlüsselung, Authentizität und Integrität und ermöglichen so die Ende-zu-Ende-Sicherheit von Daten, die zwischen Anwendungen über das Internet übertragen werden.

Die Protokolle TLS und SSL befinden sich zwischen der Anwendungsschicht und der TCP/IP-Schicht. Dort können sie Anwendungsdaten absichern und an die Transportschicht senden. Aufgrund ihrer Position unterstützen TLS und SSL mehrere Anwendungsprotokolle. In diesem Artikel erfahren Sie mehr über das OSI-Modell.

SSL wurde ursprünglich von Netscape entwickelt. SSL 1.0 wies zahlreiche Sicherheitslücken auf und wurde nie öffentlich veröffentlicht. 1995 wurde SSL 2.0 eingeführt, 1996 folgte SSL 3.0. Später wurde SSL umbenannt und 1999 TLS 1.0 veröffentlicht. Die aktuelle Version TLS 1.3 wurde 2018 eingeführt. Um sich vor bekannten Sicherheitslücken zu schützen, sollten Sie immer die neueste TLS-Version verwenden. In diesem Artikel erfahren Sie mehr über TLS.

Die Bedeutung von TLS

Historisch wurde die Internetkommunikation unverschlüsselt und mit minimalen Sicherheitsvorkehrungen abgewickelt. Die Daten, die Sie an einen Server sendeten, waren dadurch während der Übertragung sichtbar und anfällig für Man-in-the-Middle-Angriffe. Dabei schaltet sich ein Angreifer in die Kommunikation zwischen einem Benutzer und einer Anwendung ein. So kann er die übertragenen Daten einsehen und manipulieren und möglicherweise persönliche Informationen wie Passwörter oder Bankdaten offenlegen.

Um dieses Problem zu lösen, wurden SSL und TLS eingeführt. Sie bieten drei wesentliche Vorteile:

  • Integritätsschutz: TLS schützt vor der Manipulation von Nachrichten durch einen aktiven Angreifer, der den Datenverkehr abhört.

  • Authentifizierung: In den meisten Betriebsmodi ermöglicht TLS die gegenseitige Authentifizierung mithilfe signierter Zertifikate. Server werden üblicherweise authentifiziert; Clients können auf Anfrage des Servers ebenfalls authentifiziert werden. Diese Authentifizierung erfolgt während des TLS-Handshakes.

  • Vertraulichkeit: TLS verschlüsselt die zwischen Client und Server übertragenen Daten und schützt so deren Vertraulichkeit und Privatsphäre. Dadurch können passive Angreifer, die den Datenverkehr abhören, keine sensiblen Daten sehen, die zwischen den Geräten ausgetauscht werden.

Mit SSL oder TLS können Benutzer sicher sein, dass ihre Daten nicht offengelegt oder bei der Übertragung manipuliert werden. Deshalb ist die TLS-Implementierung für Anwendungen, die über das Internet erreichbar sind, unverzichtbar: Sie ermöglicht es Ihnen, die Daten Ihrer Kunden zu schützen.

Grundlegende Konzepte

Bevor wir uns genauer ansehen, wie Sie TLS in Java implementieren, frischen wir einige Konzepte auf, die Ihnen helfen, die technischen Details von TLS zu verstehen.

Sockets

Ein Socket ist ein Ende einer bidirektionalen Kommunikationsschnittstelle zwischen vernetzten Programmen. Die zugrunde liegende Transportschicht (TCP oder UDP) identifiziert die Zielanwendung, an die die Daten übertragen werden, indem sie ein Socket an eine Portnummer bindet.

Socket-Klassen bilden die Verbindung zwischen Client- und Serveranwendungen ab. Das Paket java.net stellt zwei Klassen bereit: Socket und ServerSocket. Sie implementieren jeweils die Client- und die Serverseite der Verbindung.

Weitere Informationen zu Sockets finden Sie in diesem Artikel.

Die TLS-Kommunikation erfolgt über sichere Sockets: SSLSocket und SSLServerSocket. Diese Klassen erweitern jeweils die Klassen Socket und ServerSocket.

TLS-Handshake

Ein SSL- oder TLS-Handshake besteht aus einer Reihe von Aktionen, mit denen sich Client und Server gegenseitig authentifizieren und die geheimen Schlüssel für ihre Kommunikation festlegen.

Der TLS-Handshake ist ein sehr umfassendes Thema, dessen Details den Rahmen dieses Artikels sprengen würden. Wenn Sie einen guten Einstieg in das Thema suchen, sind dieser Artikel dazu, was während eines TLS-Handshakes geschieht, und diese Übersicht über SSL- und TLS-Handshakes gute Anlaufstellen.

Java-LTS-Versionen

Java-LTS-Versionen (Long-Term Support) erhalten über einen längeren Zeitraum vierteljährliche Sicherheits-, Stabilitäts- und Leistungsupdates. Standardversionen ohne LTS erhalten Updates und Fehlerbehebungen nur bis zur Veröffentlichung der nächsten Version.

Java 7, 8, 11 und das kürzlich veröffentlichte Java 17 sind LTS-Versionen. Aufgrund des längeren Supportzeitraums wird empfohlen, in Produktionsumgebungen ausschließlich LTS-Versionen zu verwenden.

Wie bei jeder Software bietet auch jede Java-Version neue Funktionen und Verbesserungen. Die Neuerungen in Java 11 finden Sie hier, die in Java 17 hier. Da sich die Funktionen je nach Java-Version unterscheiden, sollten Sie die Dokumentation für Ihre Version konsultieren, wenn bei der TLS-Implementierung unerwartete Probleme auftreten.

TLS in Java implementieren

Nach all der Theorie kommen wir endlich zum spannenden Teil: der Implementierung. In den folgenden Abschnitten erfahren Sie mehr über das JSSE-Framework und wie Sie mit JSSE-APIs TLS implementieren.

JSSE-API

Java Secure Socket Extension (JSSE) ist ein Framework, das die zugrunde liegende Implementierung von Transport Layer Security (TLS) abstrahiert. Indem JSSE die Komplexität der zugrunde liegenden Protokolle abstrahiert, können Entwickler ganz einfach sichere, verschlüsselte Kommunikation implementieren. JSSE nutzt das TLS-Protokoll, um eine sichere, verschlüsselte Kommunikation zwischen Ihren Clients und Servern zu ermöglichen.

JSSE stellt standardmäßig vier zentrale Klassen bereit:

  • SSLSocketFactory: Diese Klasse dient als Factory zum Erstellen sicherer Sockets.

  • SSLServerSocketFactory: Diese Klasse entspricht der Klasse SSLSocketFactory, wird jedoch speziell zum Erstellen von Server-Sockets verwendet.

  • SSLSocket: SSLSocket erweitert Socket um eine zusätzliche Sicherheitsebene für das zugrunde liegende Netzwerktransportprotokoll, etwa TCP oder UDP, und bietet die Vorteile von SSL und TLS.

  • SSLServerSocket: Diese Klasse ähnelt der Klasse SSLSocket. Der wesentliche Unterschied besteht darin, dass SSLServerSocket serverseitige Sockets und SSLSocket clientseitige Sockets erstellt.

Mit diesen Klassen implementieren Sie TLS auf der Client- und Serverseite.

TLS auf der Clientseite implementieren

In diesem Abschnitt implementieren Sie clientseitiges TLS mithilfe von SSLSocketFactory und SSLSocket.

Erstellen Sie eine Textdatei mit dem Namen JavaTLSClientExample.class, fügen Sie den folgenden Code ein und speichern Sie die Datei:

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();
            }
        }
    }
}

Öffnen Sie zum Ausführen des Codes ein Terminal und wechseln Sie mit dem Befehl cd in den Ordner, in dem Sie die obige Datei gespeichert haben: cd <PathToFile>.

Kompilieren Sie die Datei mit dem Befehl javac JavaTLSClientExample.class. Führen Sie sie anschließend mit dem Befehl java JavaTLSClientExample.class aus.

Im Folgenden erklären wir, was in den einzelnen Schritten des Beispielcodes geschieht.

Im ersten Schritt rufen Sie eine SSLSocketFactory ab, mit der Sie anschließend das Socket erstellen.

Es gibt mehrere Möglichkeiten, eine SSLSocketFactory abzurufen. In diesem Beispiel rufen Sie die standardmäßige Factory ab, indem Sie die statische Methode SSLSocketFactory.getDefault() aufrufen.

Im zweiten Schritt erstellen Sie ein SSL-Socket. Dazu rufen Sie die Methode createSocket auf und übergeben Angaben wie den Remote-Host und den Remote-Port. Dadurch wird ein Socket erstellt und mit dem angegebenen Remote-Host am angegebenen Remote-Port verbunden.

Im dritten Schritt richten Sie die Parameter ein. Zunächst legen Sie fest, welche TLS-Version das Socket unterstützen soll. Zum Zeitpunkt der Erstellung dieses Artikels ist TLS v1.3 die neueste Version und wird daher im obigen Beispiel verwendet. Außerdem wird die CipherSuite TLS_AES_128_GCM_SHA256 verwendet, um die verschiedenen Parametersätze für die einzelnen Schritte des TLS-Handshakes festzulegen.

Im vierten Schritt starten Sie den Handshake. Dabei wird der TLS-Handshake explizit eingeleitet.

Während des Handshakes verwendet das Socket das Protokoll und die CipherSuite, die Sie im vorherigen Schritt festgelegt haben.

Im fünften Schritt ist die Einrichtung abgeschlossen und Sie senden eine Anfrage an den Server. Dabei werden Textdaten in den Ausgabestream des Sockets geschrieben. OutputStreamWriter wandelt den Zeichenstream in einen Byte-Stream um. BufferedWriter vermeidet häufige Aufrufe des Konverters, während PrintWriter die formatierte Darstellung von Objekten in den Textausgabestream schreibt. Da PrintWriter keine Ausnahmen auslöst, müssen Sie mit der Methode checkError explizit auf Fehler prüfen.

Im sechsten und letzten Schritt lesen Sie die Antwort. Nachdem die Anfrage gesendet wurde, lesen Sie die Antwort aus dem inputStream des Sockets und geben sie anschließend aus.

TLS auf der Serverseite implementieren

Sehen wir uns nun an, wie Sie TLS auf der Serverseite mit SSLServerSocketFactory und SSLServerSocket implementieren.

Erstellen Sie eine Datei mit dem Namen JavaTLSServerExample.class und fügen Sie den folgenden Code ein:

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();
            }
        }
    }
}

Öffnen Sie zum Ausführen des Codes erneut ein Terminal in dem Ordner, in dem Sie die obige Datei gespeichert haben.

Kompilieren Sie die Datei mit dem Befehl javac JavaTLSServerExample.class.

Folgen Sie dieser Anleitung, um ein CA-Zertifikat einzurichten und Ihrem Keystore hinzuzufügen.

Führen Sie die Datei mit dem Befehl java -Djavax.net.ssl.keyStore=<KeystoreFilePath> -Djavax.net.ssl.keyStorePassword=<KeystorePassword> JavaTLSServerExample aus. Mit Djavax.net.ssl.keyStore und -Djavax.net.ssl.keyStorePassword stellen Sie der Java-Laufzeitumgebung das SSL-Zertifikat für den Server bereit.

Gehen wir die einzelnen Schritte des Codes noch einmal durch. Die Schritte eins bis drei ähneln den ersten drei Schritten im Beispiel zur clientseitigen TLS-Implementierung.

Im ersten Schritt rufen Sie die standardmäßige SSLSocketFactory ab.

Im zweiten Schritt erstellen Sie ein SSLServerSocket und binden es an Port 8990.

Im dritten Schritt legen Sie die von Ihrem SSLServerSocket unterstützten Protokolle und CipherSuites fest.

Im vierten Schritt nehmen Sie die eingehende Anfrage an. SSLServerSocket.accept() wartet auf eine Verbindung zu diesem Server-Socket, nimmt sie an und erstellt ein neues Socket. Die Methode wartet, bis eine Verbindung hergestellt wurde.

Im fünften Schritt werden die Daten aus dem Eingabestream des Sockets gelesen und ausgegeben. InputStreamReader wandelt den Byte-Stream in einen Zeichenstream um. BufferedReader vermeidet häufige Aufrufe des Konverters.

Zusammenhang zwischen JSSE-API und SSLSocketFactory

Diagramm der JSSE-Standard-APIs mit SSLSocketFactory, SSLServerSocketFactory, SSLSocket und SSLServerSocket.

Wie oben erwähnt, ist JSSE ein Framework. JSSE stellt verschiedene Klassen und Implementierungen bereit, die die Komplexität der Absicherung der Verbindung zwischen Client und Server abstrahieren. Außerdem bietet JSSE Implementierungsklassen, die standardmäßig zum Einrichten sicherer Sockets für Clients und Server verwendet werden können.

Auf der Clientseite rufen Sie mit SSLSocketFactory eine standardmäßige Factory ab, mit der Sie dann ein clientseitiges SSLSocket erstellen. Auf der Serverseite verwenden Sie SSLServerSocketFactory, um das SSLServerSocket zu initialisieren.

Fazit

Wenn Ihre Java-Anwendung über das Internet kommuniziert, müssen Sie TLS sowohl auf der Client- als auch auf der Serverseite implementieren. TLS schützt die sichere Übertragung von Daten durch Verschlüsselung und Integritätsschutz und unterstützt außerdem die Authentifizierung beider Kommunikationspartner.

JSSE ist ein von Java bereitgestelltes Framework, das Sie bei der Implementierung von TLS für Ihre Anwendung unterstützt. Mit Klassen wie SSLSocket und SSLServerSocket können Sie sichere Sockets erstellen.

Weiterlesen

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.

feature insights context
Blog

Ist Prävention im Grunde ein gelöstes Problem?

Die Prävention in von Agenten generiertem Code ist architektonisch gelöst – die Herausforderung besteht jedoch weiterhin darin, Kontrollen zu wählen, die die Sicherheit schützen, ohne die Entwicklung zu verlangsamen.