Hinweis: die deutsche Version dieses Artikels findet Ihr hier: H Tink Stringverschlüsselung mit PBE und GUI.
This is the shortened version of the German article. Google Tink is a nearly perfect cryptografic library but it looks like a „closed shop“. The developers try to hold the users back from using unsecure sourcecode and usage of keys to or from others systems. The key material is encapsulated in KeysetHandles and user that wants to exchange (e.g. symmetric) keys has to find a secure way to do so (maybe on a personal contact). The most easy way of exchanging a password could be done via phone and usage of a „Password based encryption“ (PBE) that strongens the password. Some Details can be found here on A08 PBKDF2-Verfahren.
Unfortunately the actual library of Tink does not support any kind of PBE so many users marked Tink as unusable for this task – maybe until now. I developed a class (TinkPbe.java) that hold the complete code and You just need 4 additional lines of sourcecode to implement a secure textbased encryption with a manual passwort input.
I created a simple graphical Desktop that shows how easy it is to implement AES GCM with 256 Bit keylength. If you prefer a non graphic software have look to H Tink Stringverschlüsselung mit PBE Konsole that shows how to implement it there.
Please keep in mind two facts: the unencrypted plaintext is hold in Strings so its inmutable and undeletable in your heap and so maybe accessible via a trojan. The second advice: there is no way to recover the plaintext from the ciphertext when the password is lost [no password – no data].
You can find the 4 additional lines of code in lines 53, 54, 83 (Encryption) and 97 (Decryption):
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); } } |
This is the class TinkPbe.java:
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; } } |
All sourcecodes to this solution are available in my Github-Archive with this link: https://github.com/java-crypto/H-Google-Tink. All programs run with Java 8 and Java 11.
The licence (or better unlicence) to my solution is available here: Lizenz-Seite.
Last edit: 20.11.2019