Zur Homepage www.HI-Tier.de Verschlüsselung: Java
Zurück Home Nach oben
Öffentlicher Bereich für Entwickler

 

Verschlüsselung im HIT-Protokoll für Java

Die Verschlüsselung in der Kommunikation mit HIT läuft über zwei Ebenen:

bulletAnmeldung an HIT mit asymmetrischer Verschlüsselung
bulletDatenaustausch mit HIT (und Abmeldung von HIT) mit symmetrischer Ver- und Entschlüsselung

Mehr Details dazu hier.

Ein verschlüsselter Datenaustausch erfordert eine erfolgreiche verschlüsselte Anmeldung an HIT. Umgekehrt jedoch erfordert eine erfolgreiche verschlüsselte Anmeldung nicht zwingend einen verschlüsselten Datenaustausch, ist aber zu empfehlen! Es ist (Stand 2023) geplant, den Datenaustausch mittelfristig verpflichtend zu machen, um der IT-Sicherheit zu genügen.

Nur mit verschlüsseltem Datenaustausch:
war eine verschlüsselte Anmeldung erfolgreich, dann bleibt die Verschlüsselung bis zur nächsten neuen verschlüsselten Anmeldung oder zum expliziten Abmelden aktiv - sowohl für HITP-Befehle als auch für HITP-Antworten. Eine nicht erfolgreiche Anmeldung aktiviert keine Verschlüsselung, d.h. es können dann (ausser einer neuen Anmeldung) nur unverschlüsselte Befehle gesendet und unverschlüsselte Antworten erhalten werden.

Achtung: im Feb. 2024 wurde die Verschlüsselung um weitere Verfahren erweitert und dafür wird eine externe Bibliothek (Bouncy Castle ab v1.76) eingebunden. Damit die bisherige integrierte und die neue Bibliothek unabhängig voneinander arbeiten können, musste die Struktur im Java-Package "de.hi_tier.hitupros" umgestellt werden. Ein bestehendes Javaprogramm kann daher nicht durch bloßes Ersetzen der entsprechenden JAR upgradet werden!musste

In den folgenden Beispielen werden Java-Codestücke, HIT-Befehle und -Antworten aus optischen Gründen teilweise mehrzeilig angezeigt.
Technisch sind sie ohne Zeilenumbruch als eine einzelne Zeile aufzufassen!

Es gibt nun auch eine Demo, die unten beschriebenen Codestücke als Ganzes vereinigt: die Demo verbindet sich mit einem HitServer und führt einen asymmetrisch verschlüsselten LOGON und einen symmetrisch verschlüsselten Datenaustausch (Abfrage und Antworten) durch. Der Javasource ist unter de.hi_tier.hitupros.crypto.DemoHITP zu finden (inkl. Startmethode main()). Darin sind Konstanten für Adressen, Logins und ENC_SYM hinterlegt, die man manuell modifizieren und z.B. im Debugger der IDE laufen lassen kann, um das Verhalten zu beobachten.
Siehe nächster Abschnitt für die Quelle des dazugehörigen Source.

Verwendung

Einfach die Sourcen oder das fertig compilierte JAR des Package de.hi_tier.hitupros unseres HitBatch-Clients verwenden und ins das eigene Programm integrieren.

Die zentrale Klasse de.hi_tier.hitupros.crypto.HitCrypto ist so ausgelegt, dass sie bereits zum Versand fertig vorliegende Befehle, die an HIT-Server gesendet werden sollen, verschlüsseln und umgekehrt die vom HIT-Server erhaltenen verschlüsselten Antworten entschlüsseln kann. Das Erzeugen der HIT-Befehle und Auswerten der HIT-Antworten ist somit der eigentlichen Anwendung vorbehalten, die das Package nutzt.

ACHTUNG: Bis Feb. 2024 war das die Klasse de.hi_tier.hitupros.HitSecurity. Diese gibt es wegen der Einbindung einer weiteren Bibliothek nicht mehr, da HitCrypto mehr und andere Aufgaben übernimmt!

Hexstrings

Da mit Bytes in einem text-basieren Datenaustauschprotokoll nicht gearbeitet werden kann, werden binäre Daten im HIT-Protokoll als Hexstrings aufgefasst. Ein Byte (8 bit) wird in sein entsprechendes hexadezimales Äquivalent in Form einer 2-stelligen Zeichenkette mit dem Zeichensatz 0 bis 9 und A bis F und Bytearrays (= byte[]) dementsprechend in eine Zeichenkette mit der doppelten Länge des Bytearray gewandelt.
Hexstrings sind nicht zu verwechseln mit dem ebenfalls im HIT-Protokoll verfügbaren Hex-Encoding in der %xx-Schreibweise!

Das Package bietet zwei Hilfsfunktionen, um in beide Richtungen wandeln zu können: von Bytearray zu Hexstring und zurück.

Beispiel: das Byte mit dem Wert 72 als Hexstring

import de.hi_tier.hitupros.crypto.CryptoHelpers;

byte   demo      = 72;
String hexstring = CryptoHelpers.hexEncode(new byte[] { demo });     // das einzelne Byte in ein Bytearray verpacken
System.out.println(hexstring);

Wird der Code ausgeführt, erhält man "48".

Decodieren läuft ähnlich:

import de.hi_tier.hitupros.crypto.CryptoHelpers;

String hexstring = "49485470";
byte[] demo      = CryptoHelpers.hexDecode(hexstring);

Öffentlicher Schlüssel

Der öffentliche Schlüssel für die asymmetrische Verschlüsselung ist hier veröffentlicht.

Dieser kann im Package als Bytearray oder als Hexstring vewendet werden:

import de.hi_tier.hitupros.crypto.HitAsymPubKey;

String        pubkeyHex = "4801525001000...0003010001";
HitAsymPubKey pubkey    = new HitAsymPubKey(pubkeyHex);

pubkey wird später dann für die Verschlüsselung des Anmeldebefehls (LOGON) verwendet.

Verbindungsaufbau

Unabhängig von unverschlüsseltem oder verschlüsseltem Datenaustausch müssen nach dem Verbindungsaufbau die Antworten als erstes gelesen werden. Nur wenn die Antworten keine Fehler anzeigen (erkennbar an <Schwere> in der Antwortstruktur), darf fortgesetzt werden. Anderenfalls ist zu einer anderen HIT-Serverinstanz zu verbinden. Diese Antworten (nur diese) sind immer unverschlüsselt.

Die Antwortzeile mit dem <AntwortCode> (=Plausinummer) 2600 ist für verschlüsselten Datenaustausch relevant und somit zwischenzuspeichern!

UTF-8

Der HIT-Server kann sowohl den Standard-Zeichensatz als auch UTF-8 verstehen. Man sollte daher beim Anlegen eines InputStreamReader und PrintWriter über den Netzwerkstream zum Lesen und Schreiben von Zeichenketten die gewünschte Codierung für die Umwandlung zwischen bytes und chars mitgeben.
Wichtig dabei: Der HIT-Server versteht UTF-8 nur ohne den Byte Order Mark (BOM).

Beispiel:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;

Charset UTF8_noBOM = StandardCharsets.UTF_8;	    // = UTF-8 ohne BOM
BufferedReader in  = new BufferedReader(new InputStreamReader(networkStream,UTF8_noBOM));
PrintStream    out = new PrintStream   (networkStream,true,UTF8_noBOM);

(networkStream ist die Verbindung zum HIT-Server)

Ohne UTF-8 wäre dies lediglich:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;

BufferedReader in  = new BufferedReader(new InputStreamReader(networkStream));
PrintStream    out = new PrintStream   (networkStream,true);

Wird UTF-8 verwendet, dann muss beim Anmelden (via Entity LOGON) die Spalte UTF8 mit dem Wert 1 (unabhängig von Verschlüsseln oder nicht) mitgesendet und auch in der Folge im Ver- und Entschlüsselungsprozess bei den Aufrufen der Ver-/Entschlüsselungsmethoden mitberücksichtigt werden!

Ablauf

Statt dass die einzelnen Komponenten für die beiden Verschlüsselungen separat gehandhabt werden, verwaltet die Klasse de.hi_tier.hitupros.crypto.HitCrypto alle notwendigen Parameter und Stati für sämtliche Ver- und Entschlüsselungen diese gemeinsam in einem Objekt. Daher wird davon eine Instanz angelegt und diese dann mit notwendigen Schlüsseln und dazugehörigen Parametern gefüttert, bevor die eigentlichen Transformationen stattfinden.

Pro HITP-Sitzung sollte nur eine HitCrypt-Instanz mitgeführt werden.

Asymmetrisch Verschlüsseln für Anmeldung

Für die verschlüsselte Anmeldung sind beim Senden der Entität LOGON neben den Anmeldeinformationen auch die Spalten SVR_HELO, RAND_CLI, SESS_KEY und ENC_SYM von Bedeutung. Auch die Spalte UTF8 spielt eine Rolle. Diese Vorarbeit ist zu leisten, bevor der komplette Anmelde-Befehl verschlüsselt wird!

bullet SVR_HELO:
bulleterforderlich
bulletist exakt der komplette Text des <Textelemente> der Antwort vom HIT-Server mit dem <AntwortCode> 2600, wenn die Verbindung aufgebaut wurde (siehe Verbindungsaufbau)
bulletbeginnt mit HitServer bereit. Version ... und endet mit der zufälligen Challenge
bulletRAND_CLI:
bulleterforderlich
bulletsorgt dafür, dass eine zufällige, nur dem Client bekannte Bytefolge mitverschlüsselt wird, um das "Erraten" der Anmeldung zu erschweren
bullethat Server-seitig keine Relevanz
bulletzufällige Bytefolge, die als Hexstring aufbereitet sein muss
bulletmindestens 30 Bytes (mind. 60 Zeichen als Hexstring)
bulletSESS_KEY:
bulletoptional; wird angegeben, wenn man nach der Anmeldung verschlüsselten Datenaustausch haben möchte
bulletzufällige Bytefolge, die als Hexstring aufbereitet sein muss
bulletmaximal 48 Bytes (also max. 96 Zeichen als Hexstring); ist abhängig vom verwendeten ENC_SYM!
bulletmuss zwischengespeichert werden für die folgende symmetrische Verschlüsselung des Datenaustausches
bulletENC_SYM:
bulletoptional; wird in Kombination mit SESS_KEY angegeben, um eine Transformationsvorschrift zum Ver- und Entschlüsseln der Datenaustausch-Zeilen festzulegen
bulletTransformationsvorschrift gemäß dem Java Cryptographic Extensions (JCE) Framework als Zeichenkette in der Form "algorithm/mode/padding", wobei algorithm für den Namen des Cipher steht, mode für den Blockmodus und padding die Block-Auffüllvorschrift festlegt
bulletohne Angabe eines ENC_SYM wird intern "Blowfish/CBC/TBC" verwendet;
möchte man vollständig abwärtskompatibel sein, muss "Blowfish/ECB/TBC" angegeben werden
bulletwird Bouncy Castle verwendet, können folgende Komponenten kombiniert werden:
bulletalgorithm: Blowfish, TripleDES, AES, Twofish
bulletmode: ECB, CBC, CFB, OFB
bulletpadding: TBC, PKCS7
bulletohne Bouncy Castle kann wie bisher nur "Blowfish/ECB/TBC" oder "Blowfish/CBC/TBC" verwendet werden!
bulletUTF8:
bulletoptional; wird angegeben, wenn man nach der Anmeldung Daten im Zeichensatz UTF-8 austauschen möchte
bulletnur der Wert 1 ist zulässig; will man kein UTF-8, dann die Spalte nicht angeben!
bulletwird UTF-8 verwendet, muss das bei den nachfolgenden Verschlüsselungen berücksichtigt werden
bulletohne die Spalte wird der System-Zeichensatz (unter Windows bspw. Windows-1252 verwendet)

Anlegen eines zufälligen SESS_KEY: anhand HitCrypto:

import de.hi_tier.hitupros.crypto.HitCrypto;
import de.hi_tier.hitupros.crypto.HitSymKey;

HitCrypto crypt = HitCrypto.getInstance();                  // Instanz für erkannte Crypto-Bibliothek anlegen
crypt.setEncSym("Blowfish/ECB/TBC");                        // Algorithmus, Blockmodus und Padding für symmetrische Verschlüsselung
HitSymKey sessionKey = crypt.generateSymKey();              // anhand ENC_SYM Schlüssel für symmetrische Verschlüsselung generieren
crypt.setSymKey(sessionKey);                                // Session-Key übernehmen in crypt
String sess_key = sessionKey.toHexstring();                 // für LOGON Spalte SESS_KEY

Man erhält dann beispielsweise in sess_key

DF3D8AAFD57F6601A6700801B725112A869E6542027CAE690E7F5F3B82ED7C5A05D3C1B6B4EF0C4F300DD4E0ACD7D7C8

 RAND_CLI kann auch mit HitCrypt zufällig erzeugt werden:

import de.hi_tier.hitupros.crypto.CryptoHelpers;

byte[] randCliBytes = new byte[32];                         // erzeuge Array
CryptoHelpers.nextRandomBytes(randCliBytes);                // fülle zufällige Bytes in Array
String rand_cli = CryptoHelpers.hexEncode(randCliBytes);    // für LOGON Spalte RAND_CLI

Beispiel

Anmeldung unverschlüsselt:

*1:XS:LOGON/BNR15;PIN;MELD_WG;CHA;MAXCERR;TIMEOUT:
09 199 000 0031;Aaaa$900000;3;0;0;1200

erweitert um Spalten für verschlüsselte Anmeldung ohne folgendem verschlüsselten Datenaustausch:

*1:XS:LOGON/BNR15;PIN;MELD_WG;CHA;MAXCERR;TIMEOUT;SVR_HELO;RAND_CLI:
09 199 000 0031;Aaaa$900000;3;0;0;1200
;HitServer bereit. Version 2600, 01.04.2023 00-00. Sie sind mit dem Testsystem T1B_HZ05 verbunden.
 Nur eingeschraenkt verfuegbar, da hier entwickelt wird. (Server benutzt neue Betriebstabellen/NEWADS)
 HI-Tierzeit 11.04.2023, 13-19-38h Challenge -2251982346156064082
;164FD11B482B793CAC1243ED076F731D73A34DB182329EF794C82E8590625B35F16BF3D89A84DA72DC1911ECC53A44B5

erweitert um Spalten für verschlüsselte Anmeldung mit folgendem verschlüsselten Datenaustausch (Spalte SESS_KEY dazu):

*1:XS:LOGON/BNR15;PIN;MELD_WG;CHA;MAXCERR;TIMEOUT;SVR_HELO;SESS_KEY;ENC_SYM;RAND_CLI:
09 199 000 0031;Aaaa$900000;3;0;0;1200
;HitServer bereit. Version 2600, 01.04.2023 00-00. Sie sind mit dem Testsystem T1B_HZ05 verbunden.
 Nur eingeschraenkt verfuegbar, da hier entwickelt wird. (Server benutzt neue Betriebstabellen/NEWADS)
 HI-Tierzeit 11.04.2023, 13-19-38h Challenge -2251982346156064082
;DF3D8AAFD57F6601A6700801B725112A869E6542027CAE690E7F5F3B82ED7C5A05D3C1B6B4EF0C4F300DD4E0ACD7D7C8
;AES/CBC/TBC
;164FD11B482B793CAC1243ED076F731D73A34DB182329EF794C82E8590625B35F16BF3D89A84DA72DC1911ECC53A44B5

Abschließend wird die ganze Zeile asymmetrisch verschlüsselt und dem erhaltenen Hexstring ein $ vorangestellt, damit der HIT-Server weiss, dass er diese Anfrage asymmetrisch entschlüsseln muss:

import de.hi_tier.hitupros.crypto.HitCrypto;
import de.hi_tier.hitupros.crypto.HitAsymPubKey;

String        pubkeyHex = "4801525001000...0003010001";         // 1:1 wie veröffentlicht
HitAsymPubKey pubkey    = new HitAsymPubKey(pubkeyHex);         // Schlüssel für asymmetrische Verschlüsselung des LOGON

HitCrypto crypt = HitCrypto.getInstance();                      // Instanz für erkannte Crypto-Bibliothek anlegen
crypt.setAsymKey(pubkey);                                       // PublicKey übernehmen
String request = "*1:XS:LOGON/BNR15;....11ECC53A44B5";          // kompletter String vom Absatz vorher
request = crypt.encodeAsymmetric(request,false,true);           // asymmetrisch verschlüsseln und mit $ versehen

pubkey beim Methodenaufruf setAsymKey() ist der öffentliche Schlüssel als Instanz vom Typ HitAsymPubKey (siehe oben).
false als Parameter zeigt an, dass keine UTF-8-Codierung verwendet wird. Gibt man true an, muss beim LOGON auch die Spalte UTF8 mit dem Wert 1 mitverschlüsselt werden. Als dritter Parameter true hängt HitCrypto automatisch das $ vorne an.

Das erhaltene request sieht dann beispielsweise so aus:

$0202002AC0888754C9E5062134D155...000D2E40BB0C79E8B028647971DA56

(ist inklusive SESS_KEY etwa 1500-1600 Zeichen lang!)

Die gesamte Zeile kann jetzt (abgeschlossen mit CRLF) als Zeichenkette über die bestehende Socket-Verbindung an HIT gesendet werden.

Hat man die Spalte SESS_KEY (und ggf. ENC_SYM) mitgegeben, erhält man bei einem erfolgreichen LOGON eine symmetrisch verschlüsselte Antwort. Ist die Anmeldung gescheitert oder wurde kein SESS_KEY mitgegeben, dann erhält man eine unverschlüsselte Antwort. Die symmetrisch verschlüsselte Antwort zeigt auch an, dass ab sofort bis zur Abmeldung der Datenaustausch mit Befehlen und Abfragen und deren Antworten symmetrisch verschlüsselt werden müssen und sind.

Übrigens: Client-seitig müssen und können keine asymmetrisch verschlüsselten Zeichenketten decodiert werden, da für den Vorgang der private Schlüssel benötigt wird, den nur die Zentrale Datenbank besitzt.

Symmetrisch ver- und entschlüsseln

Die Antworten einer erfolgreichen verschlüsselten Anmeldung und folgende Anfragen und Antworten sind symmetrisch verschlüsselt, wenn beim LOGON die Spalte SESS_KEY (ggf. mit ENC_SYM) mitgeliefert wurde. Dazu wird eine Anfrage und auch eine Antwort des HIT-Protokolls mit diesem SESS_KEY und ENC_SYM verschlüsselt und dem erhaltenen Hexstring ein # vorangestellt.

Verschlüsseln, z.B. die Abfrage von bestimmten CODES:

import de.hi_tier.hitupros.crypto.HitCrypto;

String request = "*2:RS:CODES/CODESET;CODENR;CODE;CODETEXT:CODENR;BW;1;4;ORDER;1;2";
request = crypt.encodeSymmetric(request,false,true);           // symmetrisch verschlüsseln mit obigem crypt und mit # versehen

Der dafür nötige sessionKey wurde zu Beginn vor dem LOGON per Zufall generiert und in der HitCrypt-Instanz hinterlegt. Diese bleibt während der gesamten Sitzung bis zur Abmeldung gleich. Daher muss lediglich verschlüsselt werden, da alle dafür nötigen Parameter in crypt vorhanden sind.

Die gesamte Zeile kann jetzt (abgeschlossen mit CRLF) als Zeichenkette über die bestehende Socket-Verbindung an HIT gesendet werden.

Entschlüsseln vom HIT-Server erhaltene Antwortzeilen:

import de.hi_tier.hitupros.crypto.HitCrypto;

// crypt von oben

String response;
while ( (response = readline()) != null) {
   // wenn verschlüsselt, dann erst decodieren
   if (HitCrypto.isSymmetricLine(response)) {
      response = crypt.decodeSymmetric(response.substring(1),false,true)
   }
   // entschlüsselte Antwort auswerten
   HitAntwort antwort = HitAntwort.parse(response);
   // wenn Antwort nicht die letzte, dann lies nächste Zeile von HIT
   if (antwort == null) {
      // es war keine HitAntwort oder entschlüsseln schlug fehl
      throw new IOException();
   }
   else if (antwort.IstLetzteAntwort) {
      // es ist die letzte, also Lesen abbrechen
      break;
   }
   // nächste lesen
}

Eine verschlüsselte HIT-Antwort beginnt mit einem #, also prüft man das ab (mit HitCrypto.isSymmetricLine()) und decodiert nur dann mit dem bekannten SESS_KEY und ggf. ENC_SYM (ab dem zweiten Zeichen ohne das #, daher das substring(1)).

readline() liefert beispielhaft die nächste Zeile von der HIT-Verbindung (z.B. via BufferedReader). Die Klasse HitAntwort ist eine beispielhaft selbst zu implementierende Klasse mit einem Parser (parse()) und Eigenschaften zum Abfragen wie z.B. IstLetzteAntwort. Der Lesealgorithmus ist hier näher beschrieben.

Aus einer response

#4C2BE5296EC91A8F1B2BADC9FD1462...14E77FE4B0715582DE23D82D6F7AE7

würde dann beispielsweise

=2%1652:1/121:CODES:Anzahl Datenzeilen - 1651;Select CODESET,...;Dauer=0.242[Sek.]

decodiert. Da die Antwort die letzte in einer Reihe ist (erkennbar am = zu Beginn), bricht hier die Lese-Schleife ab.

 

Kurz & bündig

Relevante Konstruktoren und Methodenaufrufe im Package de.hi_tier.hitupros

Bytes & Strings:

bulletBytes in Hexstring: Hexstring de.hi_tier.hitupros.crypto.CryptoHelpers.hexEncode(byte[])
bulletHexstring in Bytes: byte[] de.hi_tier.hitupros.crypto.CryptoHelpers.hexDecode(Hexstring)
bulletzufällige Bytes: de.hi_tier.hitupros.crypto.CryptoHelpers.nextRandomBytes(byte[])
bulletUTF-8 Encoding ohne BOM: java.nio.charset.StandardCharsets.UTF_8
bulletSystem-Encoding: java.nio.charset.Charset.defaultCharset()

Schlüssel:

bulletasymmetrisch, PublicKey: new de.hi_tier.hitupros.crypto.HitAsymPubKey(Hexstring)
bulletsymmetrisch: de.hi_tier.hitupros.crypto.HitCrypt.generateSymKey() oder new HitSymKey(Hexstring)

Asymmetrisch verschlüsseln:

bulletde.hi_tier.hitupros.crypto.HitCrypt.encodeAsymmetric(String,boolean,boolean)

Symmetrisch ver- und entschlüsseln

bulletString de.hi_tier.hitupros.crypto.HitCrypt.encodeSymmetric(String,boolean,boolean)
bulletString de.hi_tier.hitupros.crypto.HitCrypt.decodeSymmetric(Hexstring,boolean)