Kommen wir jetzt zu einem kleinen Highlight der Webseite – der AES Betriebsmodus GCM in Verbindung mit ergänzenden Daten (üblicherweise abgekürzt mit „AAD“). In den Beispielen B12 und B13 habe ich Euch einen recht sicheren Verschlüsselungsmodus vorgestellt, der durch die mögliche Nutzung von ergänzenden Daten noch besser wird.
Was sind nun „ergänzende Daten“ ? Eigentlich möchte ich, das die Daten nach einer Verschlüsselung nicht mehr lesbar sind. Stellt Euch aber jetzt einmal vor, Ihr habt ein paar Excel-Tabellen gemacht und verschlüsselt, könnt jetzt aber 2 oder mehr Tabellen nicht mehr auseinander halten und müsstet nun erst einmal alle Dateien entschlüsseln, um die richtige Datei zu finden. Das ist der Punkt, an dem Ihr Euch über ergänzende Daten freuen könntet (z.B. durch einen Kommentar „Berechnung Haus mit Balkon“).
Was ist die Besonderheit der „ergänzenden Daten“ beim Modus GCM ? Es gibt 2 Besonderheiten – Nummer 1 besagt das diese ergänzenden Daten im Klartext zusammen mit den verschlüsselten Daten verteilt werden. Die zweite Besonderheit ist noch wichtiger: die ergänzenden Daten werden mit in Berechnung des Hashwertes einbezogen, der die Daten vor einer (gewollten oder ungewollten) Veränderung schützt.
Worauf muss ich bei dieser Nutzung achten ? Die wichtigste Regel lautet: der Empfänger der Nachricht benötigt nicht nur das Passwort, den Initialisierungsvektor und die verschlüsselten Daten, sondern zusätzlich die ergänzenden Daten in unveränderter Form. Wurden die ergänzenden Daten auch nur durch ein zusätzliches Leerzeichen verändert gibt es keine Möglichkeit einer Entschlüsslung.
Das Beispiel bietet Euch in den Zeilen 123 und 124 die Möglichkeit, durch schlichtes Auskommentieren eine mutwillige Veränderung der ergänzenden Daten auf dem Weg vom Absender zum Empfänger zu simulieren. Die Auswirkung ist dramatisch – das Programm stürzt gnadenlos ab (ein fertiges Programm kann natürlich die ausgelösten Exceptions abfangen und entsprechend darauf reagieren :-).
Hier der Steckbrief des Verfahrens:
Verschlüsselungssteckbrief | |
Name des Verfahrens | AES/GCM/NOPADDING |
Langname | GCM Galois_Counter Mode |
Art der Chiffre | Blockchiffre, auch Stream-Chiffre |
Blocklänge (Byte) | 16 |
Schlüssellänge (Byte/Bit) | 16/128, 24/192, 32/256 |
Padding genutzt | Nein |
Sicherheit | sicher bei Nutzung von unterschiedlichen Initialvektoren |
Besonderes | Benötigt einen Initialvektor, der als Zufallszahl erzeugt wird. Möglich ist auch die Verbindung eines Nonce mit einem fortlaufenden Zähler. Zusätzlich können ergänzende Daten (AAD) genutzt werden. |
Bitte die nachfolgende Routine nur nach einer gründlichen Überprüfung für den Echteinsatz nutzen. Aus kryptographischer Sicht dürfte sie für viele Einsatzgebiete nutzbar sein.
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
package net.bplaced.javacrypto.symmetricencryption; /* * 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 * Datum/Date (dd.mm.jjjj): 19.11.2018 * Funktion: verschlüsselt einen Text im AESs GCM Modus kein Padding * die Ausgabe erfolgt als Base64-kodierter String * zusätzlich werden ergänzende Daten (aad) genutzt * Function: encrypts a text string using AES GCM modus with no padding * the output is decode as a Base64-string * additionally it uses Additional Associated Data (aad) * * 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 ! */ import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; public class B14_AesGcmNoPaddingRandomAadBase64String { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { System.out.println( "B14 AES im Betriebsmodus GCM Kein Padding mit Zufalls-GCM Nonce, AAD und Base64-Kodierung mit einem String"); // es werden ein paar variablen benötigt: String plaintextString = "Dieses ist der super geheime Text"; byte[] plaintextByte = plaintextString.getBytes("UTF-8"); // der gcm modus bietet an, ergänzende daten ohne verschlüsselung mit zu // speichern // diese daten werden ebenfalls mit dem hashwert gesichert String aadtextString = "Hier stehen die AAD-Daten"; byte[] aadtextByte = aadtextString.getBytes("utf-8"); final int GCMNONCELENGTH = 12; // = 96 bit String decryptedtextString = ""; // enthält später den entschlüsselten text // diese konstanten und variablen benötigen wir zur ver- und entschlüsselung // der schlüssel ist exakt 32 zeichen lang und bestimmt die stärke der // verschlüsselung. mögliche schlüssellängen sind 16 byte (128 bit), // 24 byte (192 bit) und 32 byte (256 bit) final byte[] keyByte = "12345678901234567890123456789012".getBytes("UTF-8"); // 32 byte // GENERATE random nonce (number used once) final byte[] gcmNonceByte = new byte[GCMNONCELENGTH]; SecureRandom secureRandomGcm = new SecureRandom(); secureRandomGcm.nextBytes(gcmNonceByte); // der verschluesselte (encrypted) text kommt in diese variable in form eines // byte arrays byte[] ciphertextByte = null; // die länge steht noch nicht fest, da sie von der größe des plaintextes abhängt // der entschlüsselte (decrypted) text kommt in dieses byte array, welches // später in einen string umkodiert wird byte[] decryptedtextByte = null; // die länge steht noch nicht fest, da sie von der größe des plaintextes // abhängt // ab hier arbeiten wir nun im verschlüsselungsmodus // umwandlung des klartextes in ein byte array plaintextByte = plaintextString.getBytes("UTF-8"); // hier erfolgt nun die verschlüsselung des plaintextes ciphertextByte = AesGcmNoPaddingAadEncrypt(plaintextByte, aadtextByte, keyByte, gcmNonceByte); // byte array aus gcmNonceByte und ciphertextByte erzeugen byte[] gcmNonceCiphertextByte = new byte[(GCMNONCELENGTH + ciphertextByte.length)]; System.arraycopy(gcmNonceByte, 0, gcmNonceCiphertextByte, 0, GCMNONCELENGTH); System.arraycopy(ciphertextByte, 0, gcmNonceCiphertextByte, GCMNONCELENGTH, ciphertextByte.length); // byte array in einen base64-string umwandeln String gcmNonceCiphertextString = Base64.getEncoder().encodeToString(gcmNonceCiphertextByte); // ausgabe der daten System.out.println(""); System.out.println("Klartextdaten verschlüsseln und als Base64-String anzeigen"); System.out.println("plaintextString :" + plaintextString); System.out.println("plaintextByte (hex) :" + DatatypeConverter.printHexBinary(plaintextByte)); System.out.println("gcmNonceByte (hex) :" + DatatypeConverter.printHexBinary(gcmNonceByte)); System.out.println("keyByte (hex) :" + DatatypeConverter.printHexBinary(keyByte)); System.out.println("aadtextString :" + aadtextString); System.out.println("aadtextByte (hex) :" + DatatypeConverter.printHexBinary(aadtextByte)); System.out.println("= = = Verschlüsselung = = ="); System.out.println("ciphertextByte (hex) :" + DatatypeConverter.printHexBinary(ciphertextByte)); System.out.println("= = = gcmNonceByte + ciphertextByte = = ="); System.out.println("gcmNonceCiphertextByte (hex) :" + DatatypeConverter.printHexBinary(gcmNonceCiphertextByte)); System.out.println("gcmNonceCiphertextString(B64):" + gcmNonceCiphertextString); // ab hier arbeiten wir nun im entschlüsselungsmodus // hier simulieren wir die eingabe des keybytes final byte[] keyByteDecrypt = "12345678901234567890123456789012".getBytes("UTF-8"); // 32 byte // hier simulieren wir den empfang der nachricht String receivedMessageString = gcmNonceCiphertextString; // umwandlung des base64-strings in ein byte array byte[] gcmNonceCiphertextByteReceived = Base64.getDecoder().decode(receivedMessageString); // ganz wichtig: wir benötigen zur entschlüsselung auch die aad-daten // simulation von falsch erhaltenen aad-daten - einfach die beiden zeilen ohne // kommentarvermerk mit ausführen // aadtextString = "Hier stehen die AAD-Daten1"; // aadtextByte = aadtextString.getBytes("utf-8"); // aufteilung gcmNonce + ciphertext byte[] gcmNonceByteReceived = Arrays.copyOfRange(gcmNonceCiphertextByteReceived, 0, GCMNONCELENGTH); byte[] ciphertextByteReceived = Arrays.copyOfRange(gcmNonceCiphertextByteReceived, GCMNONCELENGTH, gcmNonceCiphertextByteReceived.length); // nun wird der ciphertext wieder entschlüsselt decryptedtextByte = AesGcmNoPaddingAadDecrypt(ciphertextByteReceived, aadtextByte, keyByteDecrypt, gcmNonceByteReceived); // zurück-kodierung des byte array in text decryptedtextString = new String(decryptedtextByte, "UTF-8"); // ausgabe der daten System.out.println(""); System.out.println("= = = Erhaltene Daten = = = "); System.out.println("aadtextString :" + aadtextString); System.out.println("aadtextByte (hex) :" + DatatypeConverter.printHexBinary(aadtextByte)); System.out.println("receivedMessageString Base64 :" + receivedMessageString); System.out.println( "gcmNonceCiphertextByteR (hex):" + DatatypeConverter.printHexBinary(gcmNonceCiphertextByteReceived)); System.out.println("gcmNonceByteReceived (hex) :" + DatatypeConverter.printHexBinary(gcmNonceByteReceived)); System.out.println("ciphertextByteReceived (hex) :" + DatatypeConverter.printHexBinary(ciphertextByteReceived)); System.out.println("= = = geheimer Schlüssel = = ="); System.out.println("keyByteDecrypt (hex) :" + DatatypeConverter.printHexBinary(keyByteDecrypt)); System.out.println("= = = Entschlüsselung = = ="); System.out.println("decryptedtextByte (hex) :" + DatatypeConverter.printHexBinary(decryptedtextByte)); System.out.println("decryptedtextString :" + decryptedtextString); } public static byte[] AesGcmNoPaddingAadEncrypt(byte[] plaintextByte, byte[] aadtextByte, byte[] keyByte, byte[] gcmNonceByte) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { final int GCM_TAG_LENGTH = 128; byte[] ciphertextByte = null; // der schlüssel wird in die richtige form gebracht SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES"); // statt eines initvectors wird ein gcm parameter benoetigt GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, gcmNonceByte); // die verschlüsselungsroutine wird mit dem gewünschten parameter erstellt Cipher aesCipherEnc = Cipher.getInstance("AES/GCM/NoPadding"); // nun wird die routine mit dem schlüssel initialisiert aesCipherEnc.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec); // einbindung der aad-daten aesCipherEnc.updateAAD(aadtextByte); // hier erfolgt nun die verschlüsselung des plaintextes ciphertextByte = aesCipherEnc.doFinal(plaintextByte); return ciphertextByte; } public static byte[] AesGcmNoPaddingAadDecrypt(byte[] ciphertextByte, byte[] aadtextByte, byte[] keyByte, byte[] gcmNonceByte) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { final int GCM_TAG_LENGTH = 128; byte[] decryptedtextByte = null; // der schlüssel wird in die richtige form gebracht SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES"); // statt eines initvectors wird ein gcm parameter benoetigt GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, gcmNonceByte); // die verschlüsselungsroutine wird mit dem gewünschten parameter erstellt Cipher aesCipherDec = Cipher.getInstance("AES/GCM/NoPadding"); // nun wird die routine mit dem schlüssel initialisiert aesCipherDec.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); // einbindung der aad-daten aesCipherDec.updateAAD(aadtextByte); // hier erfolgt nun die verschlüsselung des plaintextes decryptedtextByte = aesCipherDec.doFinal(ciphertextByte); return decryptedtextByte; } } |
Sind alle Daten korrekt sehr Ihr auf der Konsole diese Ausgabe:
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 |
B14 AES im Betriebsmodus GCM Kein Padding mit Zufalls-GCM Nonce, AAD und Base64-Kodierung mit einem String Klartextdaten verschlüsseln und als Base64-String anzeigen plaintextString :Dieses ist der super geheime Text plaintextByte (hex) :44696573657320697374206465722073757065722067656865696D652054657874 gcmNonceByte (hex) :2EFAE15957C034109CD44214 keyByte (hex) :3132333435363738393031323334353637383930313233343536373839303132 aadtextString :Hier stehen die AAD-Daten aadtextByte (hex) :486965722073746568656E20646965204141442D446174656E = = = Verschlüsselung = = = ciphertextByte (hex) :A28D8D424D43CCAF4870B3991DBC8DC2554532A33D7ED5EB783D009856A14B70D29B7D82036B0F36F4D534B66FAF7B71B1 = = = gcmNonceByte + ciphertextByte = = = gcmNonceCiphertextByte (hex) :2EFAE15957C034109CD44214A28D8D424D43CCAF4870B3991DBC8DC2554532A33D7ED5EB783D009856A14B70D29B7D82036B0F36F4D534B66FAF7B71B1 gcmNonceCiphertextString(B64):LvrhWVfANBCc1EIUoo2NQk1DzK9IcLOZHbyNwlVFMqM9ftXreD0AmFahS3DSm32CA2sPNvTVNLZvr3txsQ== = = = Erhaltene Daten = = = aadtextString :Hier stehen die AAD-Daten aadtextByte (hex) :486965722073746568656E20646965204141442D446174656E receivedMessageString Base64 :LvrhWVfANBCc1EIUoo2NQk1DzK9IcLOZHbyNwlVFMqM9ftXreD0AmFahS3DSm32CA2sPNvTVNLZvr3txsQ== gcmNonceCiphertextByteR (hex):2EFAE15957C034109CD44214A28D8D424D43CCAF4870B3991DBC8DC2554532A33D7ED5EB783D009856A14B70D29B7D82036B0F36F4D534B66FAF7B71B1 gcmNonceByteReceived (hex) :2EFAE15957C034109CD44214 ciphertextByteReceived (hex) :A28D8D424D43CCAF4870B3991DBC8DC2554532A33D7ED5EB783D009856A14B70D29B7D82036B0F36F4D534B66FAF7B71B1 = = = geheimer Schlüssel = = = keyByteDecrypt (hex) :3132333435363738393031323334353637383930313233343536373839303132 = = = Entschlüsselung = = = decryptedtextByte (hex) :44696573657320697374206465722073757065722067656865696D652054657874 decryptedtextString :Dieses ist der super geheime Text |
Sollten aber die ergänzenden Daten verändert sein, gibt es diese heftige Fehlermeldung:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
B14 AES im Betriebsmodus GCM Kein Padding mit Zufalls-GCM Nonce, AAD und Base64-Kodierung mit einem String Klartextdaten verschlüsseln und als Base64-String anzeigen plaintextString :Dieses ist der super geheime Text plaintextByte (hex) :44696573657320697374206465722073757065722067656865696D652054657874 gcmNonceByte (hex) :B65FC5A06B1968E4444C41A1 keyByte (hex) :3132333435363738393031323334353637383930313233343536373839303132 aadtextString :Hier stehen die AAD-Daten aadtextByte (hex) :486965722073746568656E20646965204141442D446174656E = = = Verschlüsselung = = = ciphertextByte (hex) :B07A33717C3E2C3CFDDE968FB4A5ACB2B1558718FE5F6F62EB2F751065C920F3B1329D677ED89618D52281A4EDF32C02F1 = = = gcmNonceByte + ciphertextByte = = = gcmNonceCiphertextByte (hex) :B65FC5A06B1968E4444C41A1B07A33717C3E2C3CFDDE968FB4A5ACB2B1558718FE5F6F62EB2F751065C920F3B1329D677ED89618D52281A4EDF32C02F1 gcmNonceCiphertextString(B64):tl/FoGsZaORETEGhsHozcXw+LDz93paPtKWssrFVhxj+X29i6y91EGXJIPOxMp1nftiWGNUigaTt8ywC8Q== Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch! at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:578) at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1049) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:985) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847) at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at javax.crypto.Cipher.doFinal(Cipher.java:2164) at net.bplaced.javacrypto.symmetricencryption.B14_AesGcmNoPaddingRandomAadBase64String.AesGcmNoPaddingAadDecrypt(B14_AesGcmNoPaddingRandomAadBase64String.java:191) at net.bplaced.javacrypto.symmetricencryption.B14_AesGcmNoPaddingRandomAadBase64String.main(B14_AesGcmNoPaddingRandomAadBase64String.java:132) |
Die Lizenz zum obigen Beispiel findet Ihr auf der eigenen Lizenz-Seite.
Letzte Aktualisierung: 19.11.2018