Note: the english version of this article is found here: H Tink String encryption using PBE and GUI.
Die Verschlüsselungsroutinen von Google Tink sind aus kryptographischer Sicht hervorragend programmiert, allerdings betrachten die Entwickler das Ganze wie ein geschlossenes System. Aus Sicherheitsgründen kapseln sie das ganze System und verhindern damit, das der Anwender in direkten Kontakt zum Programmcode und zu den verwendeten Schlüsseln kommt.
So werden die von Tink erzeugten Schlüssel in „KeysetHandle“ verpackt, welche möglichst sicher aufzubewahren sind. Möchte der Anwender nun verschlüsselte Daten mit einem anderen Anwender austauschen, so steht er vor dem Problem, zuerst die Schlüsseldatei „sicher“ auszutauschen – was z.B. bei einem persönlichen Kontakt geschehen kann. In der Realität ist dieses Vorgehen nur selten möglich und es wäre doch toll, wenn die beiden Anwender ein Passwort (=Schlüssel) telefonisch vereinbaren und dann die damit verschlüsselten Daten per Mail austauschen. Für diesen Fall hat sich die „Password Based Encryption“ (oder kurz PBE) durchgesetzt, welche eine ein manuell erfasstes Passwort solange mit Hashwerten bearbeitet, bis das finale Passwort sehr sicher ist. Für Einzelheiten schaut Ihr Euch am Besten die Seite A08 PBKDF2-Verfahren an.
Leider ist in der aktuellen Implementierung von Tink kein PBE-Verfahren vorgemerkt und viele haben sich dann von Tink abgewendet – bis jetzt. Nachfolgend stelle ich Euch eine eigene Java-Klasse namens TinkPbe.java vor, welche mit nur 4 zusätzlichen Zeilen von Programmcode die komplette Ver- und Entschlüsselung mittels Tink durchführt.
Die praktische Anwendung zeige ich Euch anhand einer einfachen grafischen Oberfläche, welche die Ver- und Entschlüsselung sehr einfach durchführt. Danach seht Ihr den Programmcode der TinkPbe.java-Klasse. Der komplette Programmcode ist wie immer in meinem Github-Archiv vorhanden – viel Spass bei der Nutzung. Solltet Ihr eine Konsolenanwendung wünschen, schaut einfach in mein Beispiel H Tink Stringverschlüsselung mit PBE Konsole.
Beachtet bei der Nutzung allerdings zwei Ratschläge: zum Einen sind die unverschlüsselten Texte in Strings gespeichert und daher ggfls. für längere Zeit im Hauptspeicher Eures Rechners vorhanden [den können intelligente Trojaner auslesen und an einen Angreifer senden] und zum Anderen: die Verschlüsselung ist derart gut das es KEINE Möglichkeit gibt, einen verschlüsselten Text ohne Kenntnis des Passwortes zu entschlüsseln [Passwort weg, Daten weg].
Die 4 zusätzlichen Zeilen findet Ihr in den Zeilen 53, 54, 83 (Verschlüsselung) und 97 (Entschlüsselung):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
package tinkPbe; /* * Herkunft/Origin: http://javacrypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 8 Update 191 x64 * getestet mit/tested with: Java Runtime Environment 11.0.1 x64 * Datum/Date (dd.mm.jjjj): 20.11.2019 * Funktion: verschlüsselt und entschlüsselt einen Text mittels Google Tink * im Modus AES GCM 256 Bit. Der Schlüssel wird mittels PBE * (Password based encryption) erzeugt. * Function: encrypts and decrypts a text message with Google Tink. * Used Mode is AES GCM 256 Bit. The key is generated with PBE * (Password based encryption). * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! * * Das Programm benötigt die nachfolgenden Bibliotheken (siehe Github Archiv): * The programm uses these external libraries (see Github Archive): * jar-Datei/-File: tink-1.2.2.jar * https://mvnrepository.com/artifact/com.google.crypto.tink/tink/1.2.2 * jar-Datei/-File: protobuf-java-3.10.0.jar * https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java/3.10.0 * jar-Datei/-File: json-20190722.jar * https://mvnrepository.com/artifact/org.json/json/20190722 * */ import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.security.GeneralSecurityException; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JTextArea; import com.google.crypto.tink.aead.AeadConfig; public class H_TinkPbeGui { public static void main(String[] args) throws GeneralSecurityException { AeadConfig.register(); // tink initialisation TinkPbe tpbe = new TinkPbe(); // tink pbe initialisation JFrame f = new JFrame("Text Verschlüsselung mit Google TINK / Text Encryption with Google TINK"); final JLabel lb1 = new JLabel("Input text:"); lb1.setBounds(30, 30, 95, 30); final JTextArea ta1 = new JTextArea(); ta1.setBounds(100, 35, 500, 100); ta1.setLineWrap(true); ta1.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); final JLabel lb2 = new JLabel("Password:"); lb2.setBounds(30, 145, 95, 30); final JPasswordField pf = new JPasswordField(); pf.setBounds(100, 150, 150, 20); final JLabel lb3 = new JLabel("Output text:"); lb3.setBounds(30, 230, 95, 30); final JTextArea ta2 = new JTextArea(); ta2.setBounds(100, 235, 500, 150); ta2.setLineWrap(true); ta2.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); final JLabel lb4 = new JLabel( "Created by Michael Fehr http://javacrypto.bplaced.net https://github.com/java-crypto/H-Google-Tink/"); lb4.setBounds(30, 395, 590, 30); JButton encrypt = new JButton("Encrypt"); encrypt.setBounds(100, 185, 95, 30); encrypt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String ciphertextString = ""; try { ciphertextString = tpbe.encrypt(pf.getPassword(), ta1.getText()); } catch (GeneralSecurityException | IOException e1) { e1.printStackTrace(); ta2.setText("* * * Error * * *"); } ta2.setText(String.valueOf(ciphertextString)); } }); JButton decrypt = new JButton("Decrypt"); decrypt.setBounds(200, 185, 95, 30); decrypt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String decryptedtextString = ""; try { decryptedtextString = tpbe.decrypt(pf.getPassword(), ta1.getText()); ta2.setText(decryptedtextString); } catch (GeneralSecurityException | IOException e1) { e1.printStackTrace(); ta2.setText("* * * Error * * *"); } } }); JButton clear = new JButton("Clear"); clear.setBounds(300, 185, 95, 30); clear.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ta1.setText(""); ta2.setText(""); pf.setText(null); ta1.requestFocusInWindow(); } }); f.add(lb1); f.add(ta1); f.add(lb2); f.add(pf); f.add(encrypt); f.add(decrypt); f.add(clear); f.add(lb3); f.add(ta2); f.add(lb4); f.setSize(650, 470); f.setLayout(null); f.setVisible(true); } } |
Hier ist nun die Klasse TinkPbe.java, welche die ganze Arbeit übernimmt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
package tinkPbe; /* * * Diese Klasse gehört zu diesen beiden Hauptklassen * This class belongs to these main classes: * TinkPbeConsole.java | TinkPbeGui.java * * Herkunft/Origin: http://javacrypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 8 Update 191 x64 * getestet mit/tested with: Java Runtime Environment 11.0.1 x64 * Datum/Date (dd.mm.jjjj): 20.11.2019 * Funktion: verschlüsselt und entschlüsselt einen Text mittels Google Tink * im Modus AES GCM 256 Bit. Der Schlüssel wird mittels PBE * (Password based encryption) erzeugt. * Function: encrypts and decrypts a text message with Google Tink. * Used Mode is AES GCM 256 Bit. The key is generated with PBE * (Password based encryption). * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! * * Das Programm benötigt die nachfolgenden Bibliotheken (siehe Github Archiv): * The programm uses these external libraries (see Github Archive): * jar-Datei/-File: tink-1.2.2.jar * https://mvnrepository.com/artifact/com.google.crypto.tink/tink/1.2.2 * jar-Datei/-File: protobuf-java-3.10.0.jar * https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java/3.10.0 * jar-Datei/-File: json-20190722.jar * https://mvnrepository.com/artifact/org.json/json/20190722 * */ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import com.google.crypto.tink.Aead; import com.google.crypto.tink.CleartextKeysetHandle; import com.google.crypto.tink.JsonKeysetReader; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadFactory; public class TinkPbe { public static String encrypt(char[] passwordChar, String plaintextString) throws GeneralSecurityException, IOException { byte[] keyByte = pbkdf2(passwordChar); String valueString = buildValue(keyByte); String jsonKeyString = writeJson(valueString); KeysetHandle keysetHandleOwn = CleartextKeysetHandle.read(JsonKeysetReader.withString(jsonKeyString)); // initialisierung Aead aead = AeadFactory.getPrimitive(keysetHandleOwn); // verschlüsselung byte[] ciphertextByte = aead.encrypt(plaintextString.getBytes("utf-8"), null); // no aad-data return Base64.getEncoder().encodeToString(ciphertextByte); } public static String decrypt(char[] passwordChar, String ciphertextString) throws GeneralSecurityException, IOException { byte[] keyByte = pbkdf2(passwordChar); String valueString = buildValue(keyByte); String jsonKeyString = writeJson(valueString); KeysetHandle keysetHandleOwn = CleartextKeysetHandle.read(JsonKeysetReader.withString(jsonKeyString)); // initialisierung Aead aead = AeadFactory.getPrimitive(keysetHandleOwn); // verschlüsselung byte[] plaintextByte = aead.decrypt(Base64.getDecoder().decode(ciphertextString), null); // no aad-data return new String(plaintextByte, StandardCharsets.UTF_8); } private static byte[] pbkdf2(char[] passwordChar) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException { final byte[] passwordSaltByte = "11223344556677881122334455667788".getBytes("UTF-8"); final int PBKDF2_ITERATIONS = 10000; // anzahl der iterationen, höher = besser = langsamer final int SALT_SIZE_BYTE = 256; // grösse des salts, sollte so groß wie der hash sein final int HASH_SIZE_BYTE = 256; // größe das hashes bzw. gehashten passwortes, 128 byte = 512 bit byte[] passwordHashByte = new byte[HASH_SIZE_BYTE]; // das array nimmt das gehashte passwort auf PBEKeySpec spec = new PBEKeySpec(passwordChar, passwordSaltByte, PBKDF2_ITERATIONS, HASH_SIZE_BYTE); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); passwordHashByte = skf.generateSecret(spec).getEncoded(); return passwordHashByte; } private static String buildValue(byte[] gcmKeyByte) { // test for correct key length if ((gcmKeyByte.length != 16) && (gcmKeyByte.length != 32)) { throw new NumberFormatException("key is not 16 or 32 bytes long"); } // header byte depends on keylength byte[] headerByte = new byte[2]; // {26, 16 }; // 1A 10 for 128 bit, 1A 20 for 256 Bit if (gcmKeyByte.length == 16) { headerByte = new byte[] { 26, 16 }; } else { headerByte = new byte[] { 26, 32 }; } byte[] keyByte = new byte[headerByte.length + gcmKeyByte.length]; System.arraycopy(headerByte, 0, keyByte, 0, headerByte.length); System.arraycopy(gcmKeyByte, 0, keyByte, headerByte.length, gcmKeyByte.length); String keyBase64 = Base64.getEncoder().encodeToString(keyByte); return keyBase64; } private static String writeJson(String value) { int keyId = 1234567; // fix String str = "{\n"; str = str + " \"primaryKeyId\": " + keyId + ",\n"; str = str + " \"key\": [{\n"; str = str + " \"keyData\": {\n"; str = str + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesGcmKey\",\n"; str = str + " \"keyMaterialType\": \"SYMMETRIC\",\n"; str = str + " \"value\": \"" + value + "\"\n"; str = str + " },\n"; str = str + " \"outputPrefixType\": \"TINK\",\n"; str = str + " \"keyId\": " + keyId + ",\n"; str = str + " \"status\": \"ENABLED\"\n"; str = str + " }]\n"; str = str + "}"; return str; } } |
Alle Quellcodes zu Google Tink findet Ihr zum Download in meinem Github-Repository, welches Ihr über diesen Link erreicht: https://github.com/java-crypto/H-Google-Tink. Alle Programme sind sowohl unter Java 8 als auch unter Java 11 lauffähig.
Die Lizenz zum obigen Beispiel findet Ihr auf der eigenen Lizenz-Seite.
Letzte Aktualisierung: 20.11.2019