• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

[Java] Passwort verschlüsseln

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Auf die schnelle habe ich das hier gefunden ein sehr schöner Artikel zu dem Thema:
https://crackstation.net/hashing-security.htm

Ob und wie "gut" bzw. sicher/aktuell die Java-Sources sind die dazu angeboten werden sind kann ich aber nicht beurteilen, ich hoffe das hier andere vielleicht noch etwas mehr auf deine Fragen eingehen können die sich besser mit der Materie auskennen. :)
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #22
@theSplit:
Durch den Artikel habe ich jetzt schon mal einen Richtwert für den Salt. Undzwar, dass der Salt etwa so lang sein sollte wie die Ausgabe der folgenden Hash-Funktion. In meinem Fall also 128 bis 256 Bits.
Zudem, dass doppeltes Hashen, je nach Hashfunktion, das Hashen unsicherer macht, als nur einmaliges Hashen es eventuell tut. Habe ich sowieso nie gemacht, aber dennoch gut zu wissen.
Um den Salt zu erzeugen soll man auch nicht die normalen Random-Funktionen nutzen, sondern die "sicheren" Random-FUnktionen, weil diese noch zufälliger sind.
Hilft doch schon mal alles weiter.
Und den Salt berechnet man auch nur ein mal pro Passworterzeugung.
Ich werde dann wohl nun so langsam anfangen meine Crypt-Struktur in dem Programm zu schreiben - mal gucken wann ich damit fertig bin, ich bin immer noch etwas .... verwundert, was ich alles beachten muss.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
@theSplit:
Zudem, dass doppeltes Hashen, je nach Hashfunktion, das Hashen unsicherer macht, als nur einmaliges Hashen es eventuell tut. Habe ich sowieso nie gemacht, aber dennoch gut zu wissen.

Rein von der Logik her, wenn du etwas versalzenes mit einem neuen Passwort Hashen würdest, dann wäre auch mehr Schutz da, weil es zwei Barrieren geben würde.
Von hinten nach vorne geht es dabei. Wenn dabei am Anfang Zeichen abgeschnitten werden bei der Hash Generierung, ist der Hash am Anfang nur 8 Zeichen lang zum Beispiel, hilft es auch nicht daraus einen 32 Zeichen Hash, oder mehr, daraus zu generieren. So in etwa, ich glaube ist verständlich was ich meine. Je mehr Input / Randomness als Basis, um so besser.

Maximale Länge, mehr Input, dann vielleicht weniger je höher man der Spirale kommt - wobei man immer sagen muß, auf jeder Stufe sollte ein anderes Passwort nötig sein. Sonst besitzt man ein MasterPasswort für den Kurzen wie auch langen "Unter"-Hashes knacken.
 

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Disclaimer: Ich hab den Thread nur überflogen...
  • Also ich erzeuge per Random einen Datenkey
  • Dann erzeuge ich einen Salt per Random
  • Dann nehme ich das Passwort und den Salt und hashe die zusammen
  • Ich verschlüssele den Datenkey mit dem Hash
  • Mit dem "normalen" Datenkey verschlüssele ich alle anderen Daten
  • fertig
Richtig so?

Klingt im Prinzip gut. Dabei solltest du jedoch ein paar Details der Verschlüsselung beachten:
  • Ich sehe in deinem Code keinen Initialisierungsvektor (IV). Daher nehme ich an, dass du AES standardmäßig im ECB-Mode benutzt. Das ist jedoch die Unsicherste aller Varianten, durch die man evtl. Aussagen über die verschlüsselten Daten treffen kann. Komplexere Modi wie CBC oder CTR sind da schon besser, state-of-the-art ist meines Wissens nach GCM (Galois Counter Mode).
  • Alle komplexeren Modi benötigen neben dem Key noch einen zufällig gewählten Initialisierungsvektor (IV). Wenn du immer den kompetten Datensatz verschlüsselst, solltest du den IV jedes mal neu bestimmen. Der IV kann zuammen mit den anderen Daten gespeichert werden, er ist nicht geheim. Durch ihn wird sichergestellt, dass gleiche Daten ohne den Key nicht mehr als "gleich" erkannt werden können (da verschiedene IV verschiedene Verschlüsselungen ergeben).
  • Der GCM-Mode gibt dir zusätzlich noch einen Authentication Code (MAC) aus, den du mitspeichern solltest. Dadurch kann verhindert werden, dass die Daten durch einen Angreifer verändert werden können (was bei ECB und CBC ohne Weiteres möglich ist). AES-GCM erfüllt damit die strengste Sicherheits-Notation (secure against Chosen-Ciphertext-Attacks). Außerdem kannst du damit prüfen, ob die Daten richtig entschlüsselt wurden (sprich das Passwort stimmt), und musst keinen Passworthash speichern.
  • Die Standard-Java-Klassen sind was Crypto angeht etwas eingeschränkt. So können bspw. die nicht-US-Versionen nur AES128, aber kein AES256. Die BouncyCastle-Libraries sind da deutlich flexibler (und unterstützen bspw. auch den genannten GCM-Mode).

Was die Hashfunktion angeht, solltest du darauf achten, die Ausgabe binär zu bekommen (wenn du sie als Key verwenden willst). Dann hast du pro Zeichen 256 Möglichkeiten (statt 16).

Mein Vorschlag für deine Encryption sähe also etwa so aus:
Initialisierung:
Generiere Daten-Key dk (256bit secure-random)

Password setzen/ändern:
salt = random(128-256bits)
pwhash = hash(pw + salt)
IV_pw = random(128bits) // Ich meine mich zu erinnern, dass auch AES256 auf 128bit-Blöcken operiert. Kann mich aber auch irren
(edk, edk_mac) = AES-GCM(key = pwhash, IV = IV_pw, dk)
Speichern: IV_pw, edk, edk_mac

Encrypten
IV_data = random(128bits)
(edata, edata_mac) = AES-GCM(key = dk, IV = IV_data, daten)
Speichern: IV_data, edata, edata_mac

Decrypten
Laden: salt, IV_pw, edk, edk_mac
pwhash = hash(passwort + salt)
dk = AES-GCM-Decrypt(key = pwhash, IV=IV_pw, edk, edk_mac) // --> Wirft ne Exception wenn das Passwort falsch ist
Laden: IV_data, edata, edata_mac
data = AES-GCM-Decrypt(key = dk, IV=IV_data, edata, edata_mac) // Wirft ne Exception, wenn die Daten manipuliert wurden

Sicher nicht ganz einfach umzusetzen, aber effizient und definitiv sicher.


Was den Hash angeht: SHA256/512 ist da bestimmt eine gute Wahl. bcrypt ist deutlich sicherer gegen Bruteforce-Attacken, aber es ist nicht ganz trivial, aus einem bcrypt-hash einen äquivalent sicheren Key zu bekommen (substring + base64-decode könnte funktionieren).

Viel Spaß beim Implementieren!
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #25
Super Pseudocode - hilft mir auf jedenfall weiter. Werde ich demnächst mal versuchen umzusetzten. Ich denke ich bleibe vorerst bei SHA256/512 - Irgendwann ist auch genug des Guten.

Ich werde mal gucken, ob ich mir die andere Crypto-Bibliothek runterlade und mit der arbeite - aber wie gesagt, das dauert noch etwas
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #26
Ich habe jetzt angefangen meine Klasse zu schreiben - aber ich habe doch ein Problem gefunden, welches ich mit Google nicht lösen kann.
Ich kann "normal" verschlüsseln oder entschlüsseln, allerdings habe ich dann keine Möglichkeit eine Mac zu kriegen oder zu überprüfen.
Leider finde ich auch kein Beispiel, wie das mit der BouncyCastle-Bibliothek gemacht wird.

Hier mal alles, was ich bisher habe - muss logischerweise noch erweitert werden und sortiert usw.

[src=java]
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import java.security.SecureRandom;
import java.security.Security;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.bouncycastle.util.encoders.Base64;


/**
*
* @author
*/
public class MyCryptClass {
private SecretKeySpec dataKey;
private String salt;
private String passHash;
//private byte[] IV;
private IvParameterSpec ivSpec;

private Cipher c;

public MyCryptClass() {
Security.addProvider(new BouncyCastleProvider());
}

public void generateSalt() {
SecureRandom random = new SecureRandom();
byte[] saltBytes = new byte[32];
random.nextBytes(saltBytes);
salt = String.valueOf(saltBytes);
}

public void generateDataKey() {
SecureRandom random = new SecureRandom();
byte[] keyBytes = new byte[32]; //32 Bytes = 256 Bits
random.nextBytes(keyBytes);
this.dataKey = new SecretKeySpec(keyBytes, "AES");
}

public void setSalt(String s) {
this.salt = s;
}

public void setDataKey(String dKey) {
this.dataKey = new SecretKeySpec(dKey.getBytes(), "AES");
}

public void setPasswort(String pass) {
try {
if(this.salt == null) {
this.generateSalt();
}

MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update((pass+this.salt).getBytes());

this.passHash = new String(md.digest());
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}
}

public void generateIV() {
SecureRandom random = new SecureRandom();
byte[] ivBytes = new byte[16]; //16 Bytes = 128 Bits
random.nextBytes(ivBytes);
this.ivSpec = new IvParameterSpec(ivBytes);
//this.IV = ivBytes;
}

public void createCypter() {
SecretKeySpec key = new SecretKeySpec(this.passHash.getBytes(), "AES");

try {
// (edk, edk_mac) = AES-GCM(key = pwhash, IV = IV_pw, dk)
// Speichern: IV_pw, edk, edk_mac

c = Cipher.getInstance("AES/GCM/NoPadding", "BC");
c.init(Cipher.ENCRYPT_MODE, key, ivSpec);

// GCMBlockCipher gcmC = new GCMBlockCipher(c); //Ich muss einen BlockCipher übergeben, aber den zu erstellen mit den entsprechenden Parametern klappt einfach gar nicht.
// byte[] mac = gcmC.getMac();
byte[] encDataKey = c.doFinal(this.dataKey.getEncoded());




//Nun sollte man IV und encDataKey speichern (die max ebenso)

} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
[/src]
 

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Hallo!
Sieht doch schonmal gut aus. Laut Dokumentation hängen die GCM-Klassen die MAC automatisch an den Ciphertext an, und lesen sie auch wieder automatisch. Der verschlüsselte Text ist also nur ein paar Bytes länger, du musst dich aber um nichts gesondert kümmern.


Ansonsten ist dein Passwort-Hash ein String. Das macht so nur Probleme, da der String (in meinem Testfall) 62 Byte lang ist, wo SHA-256 doch nur 256/8=32 Bytes erzeugt (die dann auf eine mir nicht bekannte Art in einen String konvertiert wurden). Da die Blockcipher nur 16 und 32 Byte nehmen, crasht das. Mein Vorschlag: passHash zu byte[] machen, und entsprechend "new String(...)" und ".getBytes()" weglassen. Ist auch sauberer.


Du scheinst BouncyCastle über die Java-Crypto-API anzusprechen, nicht über seine eigenen Klassen. Deshalb kannst du auch den (auskommentierten) GCMBlockCipher nicht nutzen. Damit das funktioniert, musst du den Provider noch registrieren, sonst findet Java den nicht: [src=java]static{
Security.addProvider(new BouncyCastleProvider());
}[/src]


Da du BouncyCastle mit der Java-Crypto-API (JCE) benutzt, hast du auch die Einschränkungen der JCE mit drin: dein AES kann nur 128-Bit-Schlüssel akzeptieren (solange man die Java-Installation nicht patcht). Wenn du dabei bleiben willst, musst du die 32 passHash-Bytes noch irgendwie auf 16 eindampfen. Alternativ kannst du die BouncyCastle-Klassen verwenden, die diese Einschränkung nicht haben. Sind in meinen Augen auch wesentlich einfacher zu benutzen. Mal ein Beispiel für AES-256-GCM mit BC:
[src=java]public void cryptWithBCClasses() throws IOException{
byte[] key = this.passHash; // ... Initialisieren mit 256 bits
byte[] IV = new byte[128 / 8]; // Auch AES256 nimmt nur 128bits IV
new SecureRandom().nextBytes(IV);

// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(true, new AEADParameters(new KeyParameter(key), 128, IV)); // 128 = Block-Größe (immer 128 für AES)

// Encrypten, bspw mit CipherOutputStream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream stream = new CipherOutputStream(baos, cipher); // output kann ein beliebiger OutputStream sein, bspw. FileOutputStream
// ... Daten in "stream" schreiben
byte[] input = new byte[16];
new SecureRandom().nextBytes(input);
stream.write(input);
stream.close(); // damit sollte die MAC an die Daten angehängt werden

// Verschlüsselte Daten aus dem ByteArray-Stream holen
byte[] encrypted = baos.toByteArray();
System.out.println("Ciphertext length: "+encrypted.length);


// Decrypten
cipher.reset();
cipher.init(false, new AEADParameters(new KeyParameter(key), 128, IV));
// Erzeugt entschlüsselnden Input-Stream. Wenn der Try-Block verlassen wird, wird der Stream geschlossen und die MAC geprüft.
// Alternativ: stream2.close() nutzen.
try (CipherInputStream stream2 = new CipherInputStream(new ByteArrayInputStream(encrypted), cipher)){
// Daten lesen wie bspw. aus einer Datei
byte[] decrypted = new byte[input.length];
System.out.println("Read: "+stream2.read(decrypted));
System.out.println("Check: "+Arrays.equals(decrypted, input));
}


// So siehts aus wenn die MAC falsch ist
encrypted[0]++;
cipher.reset();
cipher.init(false, new AEADParameters(new KeyParameter(key), 128, IV));
try (CipherInputStream stream3 = new CipherInputStream(new ByteArrayInputStream(encrypted), cipher)){
byte[] decrypted = new byte[input.length];
System.out.println("Read: "+stream3.read(decrypted));
} catch (InvalidCipherTextIOException e){
System.err.println("Ciphertext was manipulated, MAC check failed!");
}
}[/src]
Die Stream-Geschichte ist optional (du kannst auch direkt update() und doFinal() auf dem Cipher nutzen), sind aber in meinen Augen äußerst praktisch. In deiner Datei-Lade-Funktion musst du nurnoch den FileInputStream gegen den CipherInputStream(..., originalFileInputStream) tauschen, in der Speicher-Funktion eben den Output-Stream. Die ByteArrayStream-Geschichte ist nur da, damit ich keine Test-Dateien anlegen muss.
Falls dir die Try-Statements komisch vorkommen: Die Dinger heißen try-with-resource, und rufen effektiv .close() für dich auf, wenn der Block vorbei ist. Die MAC prüft erst mit dem close(), auch wenn du evtl. vorher schon alle Daten gelesen hast.

Hoffe das hilft dir weiter!
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #28
Du scheinst BouncyCastle über die Java-Crypto-API anzusprechen, nicht über seine eigenen Klassen. Deshalb kannst du auch den (auskommentierten) GCMBlockCipher nicht nutzen. Damit das funktioniert, musst du den Provider noch registrieren, sonst findet Java den nicht: [src=java]static{
Security.addProvider(new BouncyCastleProvider());
}[/src]
Eigentlich mache ich das doch direkt im Konstruktor?
Ich habe jetzt einfach mal deine Zeile bei mir eingefügt, sollte dann ja hoffentlich auch klappen.

[src=java]import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.ArrayUtils;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.io.InvalidCipherTextIOException;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.bouncycastle.util.encoders.Base64;

/**
*
* @author
*/
public class MyCryptClass {

private byte[] dataKey;
// private byte[] dataKeyMac;
private byte[] salt;
private byte[] passHash;
private byte[] iv;
// private IvParameterSpec ivSpec;

static {
Security.addProvider(new BouncyCastleProvider());
}

public MyCryptClass() {
// Security.addProvider(new BouncyCastleProvider());
}

public void generateSalt() {
SecureRandom random = new SecureRandom();
byte[] saltBytes = new byte[32]; //32 Bytes = 256 Bits
random.nextBytes(saltBytes);
this.salt = saltBytes;
}

public void generateDataKey() {
SecureRandom random = new SecureRandom();
byte[] keyBytes = new byte[32]; //32 Bytes = 256 Bits
random.nextBytes(keyBytes);
// this.dataKey = new SecretKeySpec(keyBytes, "AES");
this.dataKey = keyBytes;
}

// public SecretKeySpec getDataKey() {
// return this.dataKey;
// }
public byte[] getDataKey() {
return this.dataKey;
}

public byte[] getSalt() {
return this.salt;
}

public byte[] getPasswordHash() {
return this.passHash;
}

public byte[] getIV() {
return this.iv;
}

public void setSalt(byte[] s) {
if (s.length == 32) {
this.salt = s;
} else if (s.length > 32) {
//Der DataKey kann nicht verwendet werden
this.generateSalt();
} else {
SecureRandom random = new SecureRandom();
byte[] randomBytes = new byte[32 - s.length];
random.nextBytes(randomBytes);
this.salt = ArrayUtils.addAll(s, randomBytes);
}
}

public void setDataKey(String dKey) {
// this.dataKey = new SecretKeySpec(dKey.getBytes(), "AES");
if (dKey.getBytes().length == 32) {
this.dataKey = dKey.getBytes();
} else if (dKey.getBytes().length > 32) {
//Der DataKey kann nicht verwendet werden
this.generateDataKey();
} else {
SecureRandom random = new SecureRandom();
byte[] randomBytes = new byte[32 - dKey.getBytes().length];
random.nextBytes(randomBytes);
this.dataKey = ArrayUtils.addAll(dKey.getBytes(), randomBytes);
}
}

public void setPasswort(String pass) {
try {
if (this.salt == null) {
this.generateSalt();
}

MessageDigest md = MessageDigest.getInstance("SHA-256");
// md.update(pass.getBytes() + this.salt);
md.update(ArrayUtils.addAll(pass.getBytes(), this.salt));

this.passHash = md.digest();
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}
}

public void generateIV() {
SecureRandom random = new SecureRandom();
byte[] ivBytes = new byte[16]; //16 Bytes = 128 Bits
random.nextBytes(ivBytes);
// this.ivSpec = new IvParameterSpec(ivBytes);
this.iv = ivBytes;
}

public String getPasswordDataToSave() {
String data = "";

byte[] encDataKey = encryptDataKey();
byte[] salt = this.getSalt();
byte[] iv = this.getIV();
byte[] dataBytes = ArrayUtils.addAll(ArrayUtils.addAll(encDataKey, salt), iv);

data = Base64.toBase64String(dataBytes);

return data;
}

public void loadPasswordData(String pd) {
byte[] data = Base64.decode(pd);

byte[] encDataKey = Arrays.copyOfRange(data, 0, data.length - 64);
byte[] salt = Arrays.copyOfRange(data, data.length - 64, data.length - 32);
byte[] iv = Arrays.copyOfRange(data, data.length - 32, data.length);

/* Daten wieder in den Cipher einlesen */
}

private byte[] encryptDataKey() {
byte[] encDataKey = null;
// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(true, new AEADParameters(new KeyParameter(this.passHash), 128, this.iv)); // 128 = Block-Größe (immer 128 für AES)

// Encrypten, bspw mit CipherOutputStream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();

OutputStream stream = new CipherOutputStream(baos, cipher); // output kann ein beliebiger OutputStream sein, bspw. FileOutputStream
try {
// ... Daten in "stream" schreiben
stream.write(this.dataKey);
stream.close(); // damit sollte die MAC an die Daten angehängt werden

// Verschlüsselte Daten aus dem ByteArray-Stream holen
encDataKey = baos.toByteArray();
// System.out.println("Ciphertext length: "+encrypted.length);
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return encDataKey;
}

private byte[] decryptDataKey(byte[] encDataKey) {
byte[] decDataKey = new byte[32];

// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(false, new AEADParameters(new KeyParameter(this.passHash), 128, this.iv));

// dataKey = cipher.doFinal(encDataKey);
try (CipherInputStream stream = new CipherInputStream(new ByteArrayInputStream(encDataKey), cipher)) {

stream.read(decDataKey);

} catch (InvalidCipherTextIOException e) {
System.err.println("Ciphertext was manipulated, MAC check failed!");
decDataKey = null;
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return decDataKey;
}[/src]

Das verschlüsseln und entschlüsseln der eigentlichen Daten müsste jetzt noch dazu und dann muss natürlich alles vernünftig aufgerufen werden und die Sichtbarkeiten angepasst werden.
Findet ihr noch grobe Fehler?
Habe ich jetzt die richtigen Klassen importiert?
 
Zuletzt bearbeitet:

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Hallo,
Eigentlich mache ich das doch direkt im Konstruktor?
Stimmt, hab ich nicht gesehen. Static ist aber evtl. sauberer, weil du den Provider dann nur einmal registrierst. Wenn du GCMBlockCipher direkt verwendest, kannst du den Provider aber auch einfach ignorieren (die imports reichen).


[src=java]public void setDataKey(String dKey) {[/src]
Warum ist der Daten-Key hier ein String? Byte-Array wäre wohl praktischer. Hier hast du wieder .getBytes() verwendet, und nimmst damit alle nervigen Sachen von String mit (Encoding, Null-Byte am Ende, ...).


Ansonsten sehen die set-Methoden etwas merkwürdig aus. Wenn die übergebenen Daten zu lang oder zu kurz sind, würde ich eher eine Exception erwarten, als zufällig generierte Daten. Dann merkst du wenigstens direkt, dass etwas nicht stimmt, bevor du dir evtl. Daten mit einem zufällig generierten Key verschlüsselst...


[src=java]byte[] encDataKey = Arrays.copyOfRange(data, 0, data.length - 64);
byte[] salt = Arrays.copyOfRange(data, data.length - 64, data.length - 32);
byte[] iv = Arrays.copyOfRange(data, data.length - 32, data.length);[/src]
Dein IV ist nur 128 bits (= 16 Bytes) lang.

Ansonsten sollte das funktionieren.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #30
In etwa so?

[src=java] public void setDataKey(byte[] dKey) {
if (dKey.length == 32) {
this.dataKey = dKey;
} else {
throw new IllegalArgumentException( "dKey has to be 32 byte long but it is " + dKey.length + " byte long." );
}
}

public void setIV(byte[] iV) {
if (iV.length == 16) {
this.iv = iV;
} else {
throw new IllegalArgumentException( "iV has to be 16 byte long but it is " + iV.length + " byte long." );
}
}

public void setSalt(byte[] s) {
if (s.length == 32) {
this.salt = s;
} else {
throw new IllegalArgumentException( "Salt has to be 32 byte long but it is " + s.length + " byte long." );
}
}

public String getPasswordDataToSave() {
String data = "";

byte[] encDataKey = encryptDataKey();
byte[] salt = this.getSalt();
byte[] iv = this.getIV();
byte[] dataBytes = ArrayUtils.addAll(ArrayUtils.addAll(encDataKey, salt), iv);

data = Base64.toBase64String(dataBytes);

return data;
}

public boolean loadPasswordData(String pd) {
byte[] data = Base64.decode(pd);

byte[] encDataKey = Arrays.copyOfRange(data, 0, data.length - 48);
byte[] salt = Arrays.copyOfRange(data, data.length - 48, data.length - 16);
byte[] iv = Arrays.copyOfRange(data, data.length - 16, data.length);

/* Daten wieder in den Cipher einlesen */
byte[] decDataKey = this.decryptDataKey(encDataKey);

if(decDataKey == null) {
return false;
}

this.setDataKey(decDataKey);
this.setSalt(salt);
this.setIV(iv);

return true;
}

private byte[] encryptDataKey() {
byte[] encDataKey = null;
// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(true, new AEADParameters(new KeyParameter(this.passHash), 128, this.iv)); // 128 = Block-Größe (immer 128 für AES)

// Encrypten, bspw mit CipherOutputStream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();

OutputStream stream = new CipherOutputStream(baos, cipher); // output kann ein beliebiger OutputStream sein, bspw. FileOutputStream
try {
// ... Daten in "stream" schreiben
stream.write(this.dataKey);
stream.close(); // damit sollte die MAC an die Daten angehängt werden

// Verschlüsselte Daten aus dem ByteArray-Stream holen
encDataKey = baos.toByteArray();
// System.out.println("Ciphertext length: "+encrypted.length);
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return encDataKey;
}

private byte[] decryptDataKey(byte[] encDataKey) {
byte[] decDataKey = new byte[32];

// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(false, new AEADParameters(new KeyParameter(this.passHash), 128, this.iv));

// dataKey = cipher.doFinal(encDataKey);
try (CipherInputStream stream = new CipherInputStream(new ByteArrayInputStream(encDataKey), cipher)) {

stream.read(decDataKey);

} catch (InvalidCipherTextIOException e) {
System.err.println("Ciphertext was manipulated, MAC check failed!");
decDataKey = null;
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return decDataKey;
}[/src]

Ich habe auch noch eine Frage zu dem übergeben des Passworts.
Ich lese es aus einem JPassword-Field aus, bekomme also ein char-Array. Bisher dachte ich, es wäre vollkommen ok, dieses in einen String zu konvertieren usw.
Aber da mir hier nun mehrfach gesagt wurde, dass das mit String und byte[] nicht zu gut klappt, habe ich mir gedacht, dass ich mir eine Methode schreibe, welche ein char-Array in ein byte-Array umwandelt. Welche Methode ist da besser geeignet, die aktuelle oder die auskommentierte Variante?
Es scheint ja leider derzeit keine Methode dafür in einer der JDK-8-Bibliotheken zu geben.
[src=java]
public void setPasswort(char[] pass) {
if(pass.length < 8) {
throw new IllegalArgumentException( "The password is to short. It has to be 8 or more characters." );
}
if (this.salt == null) {
this.generateSalt();
}
try {


MessageDigest md = MessageDigest.getInstance("SHA-256");
// md.update(pass.getBytes() + this.salt);
md.update(ArrayUtils.addAll(charToBytes(pass), this.salt));

this.passHash = md.digest();
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}
}

static private byte[] charToBytes(char[] chars) {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(bOut);
writer.print(chars);
writer.close();

return bOut.toByteArray();

// byte[] bytes = new byte[chars.length*2];
//
// for(int i=0; i <= chars.length; i++) {
// bytes[2*i] = (byte) (chars & 0xff);
// bytes[2*i+1] = (byte) (chars >> 8 & 0xff);
// }
//
// return bytes;
}[/src]

EDIT:
Eine Frage habe ich noch:
Welche Datei benötige ich von Bouncy Castle, damit ich bei allen Klassen die JavaDocs dabei habe und ich alles nötige nutzen kann?
Derzeit habe ich die bcprov-jdk15on-154.jar importiert - aber mir scheint es, als seien ziemlich viele "unsinnige" Packages dabei mit reingeraten. Ich finde das auf der Website etwas unübersichtlich, wenn jemand wie ich dort etwas sucht, und selber normalerweise nie fremde Bibliotheken importiert.
 

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Ich würde gefühlt die auskommentierte Methode nehmen - ist weniger Overhead. Die Set-Methoden sehen gut aus.

Das Passwort wäre wohl das Einzige, das du wirklich in einen String konvertieren könntest, ohne interessante Fehler zu produzieren (ist ja nur Text, keine binären Daten). Allerdings liefert dir das JPasswordField eben einen char[], damit du das Passwort nach Benutzung überschreiben kannst (und das somit aus dem Arbeitsspeicher verschwindet). Von daher macht es schon Sinn, auch damit zu arbeiten. Zumindest solange du die gleiche Datei nicht noch aus einer anderen Programmiersprache heraus entschlüsseln musst (die evtl. eine andere Darstellung von char hat).

Die bcprov-jdk15on-154.jar hab ich auch genommen - BouncyCastle enthält eben noch deutlich mehr Verschlüsselungsverfahren als nur AES/GCM. Die zusätzlichen Klassen sollten aber eigentlich nicht stören. Die JavaDoc gibts in einer Extra-Datei, die du runterladen und in deine Entwicklungsumgebung einbinden kannst.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #32
Gut, wenn da jetzt keine groben Fehler mehr drin sind, kann ich mich demnächst ja mal dran machen, den Rest der Klasse zu schreiben. Daten mit dem Passwort zu verschlüsseln und zu entschlüsseln und für jede Datei einen neuen IV erzeugen und sowas.

Wenn das geschafft ist, komme ich mit meinem Programm auch deutlich besser weiter, weil ich mir keine Gedanken mehr um die Verschlüsselung machen muss...

Allerdings liefert dir das JPasswordField eben einen char[], damit du das Passwort nach Benutzung überschreiben kannst (und das somit aus dem Arbeitsspeicher verschwindet).
Genau das habe ich nämlich dabei auch noch gelesen, als ich nach einer Möglichkeit gesucht habe, dass Passwort direkt binär zu bekommen.
Da ich keine andere Programmiersprache angedacht habe, sollte das so ohne weiteres klappen.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #33
So, ich mal wieder:
Bezüglich des Passwortes bzw des datakey und des passhashs. Ich speichere diese beiden Daten ja in zwei Variablen in meiner Klasse, bzw meinem Objekt zwischen. Sollte ich das irgendwie anders machen? Geht das überhaupt anders?

So sehen jetzt meine beiden Methoden aus, die meine Daten (hauptsächlich Text und diverse Zeichen, die aber auf einer normalen Tastatur alle vorkommen sollten), die zuvor aus einem JTextField ausgelesen werden (also in UTF-16), verschlüsseln bzw entschlüsseln. Seht ihr da noch grobe Schnitzer?
[src=java]
public String encrypt(String dataToEncrypt) {
String encDataB64 = null;
byte[] encData;
byte[] decData = dataToEncrypt.getBytes();
byte[] iv = this.getNewIV();
// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(true, new AEADParameters(new KeyParameter(this.dataKey), 128, iv)); // 128 = Block-Größe (immer 128 für AES)

// Encrypten, bspw mit CipherOutputStream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();

OutputStream stream = new CipherOutputStream(baos, cipher); // output kann ein beliebiger OutputStream sein, bspw. FileOutputStream
try {
// ... Daten in "stream" schreiben
stream.write(decData);
stream.close(); // damit sollte die MAC an die Daten angehängt werden

// Verschlüsselte Daten aus dem ByteArray-Stream holen
encData = baos.toByteArray();

// IV an die Daten anhängen
encData = ArrayUtils.addAll(encData, iv);

// Verschlüsselte Daten in Base64 speichern
encDataB64 = Base64.toBase64String(encData);
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return encDataB64;
}

private String decrypt(String encDataB64) {
String decString = "";
byte[] decData = null;
byte[] encData = Base64.decode(encDataB64);
byte[] data = Arrays.copyOfRange(encData, 0, encData.length - 16);
byte[] iv = Arrays.copyOfRange(encData, encData.length - 16, encData.length);

// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(false, new AEADParameters(new KeyParameter(this.passHash), 128, iv));

try (CipherInputStream stream = new CipherInputStream(new ByteArrayInputStream(data), cipher)) {

stream.read(decData);

} catch (InvalidCipherTextIOException e) {
System.err.println("Ciphertext was manipulated, MAC check failed!");
decData = null;
} catch (IOException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

try {
decString = new String(decData, "UTF-16");

} catch (UnsupportedEncodingException ex) {
Logger.getLogger(MyCryptClass.class.getName()).log(Level.SEVERE, null, ex);
}

return decString;
}[/src]
 

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Wenn du beim Decrypten auf UTF-16 bestehst, solltest du das auch beim Encrypten tun - beim Umwandeln in ein byte[] (also getBytes("UTF-16") oder so).

Datenkey und Passwort in ner Variable speichern macht erstmal Sinn. Evtl. wäre es sinnvoll, beide zu überschreiben, sobald sie nichtmehr benötigt werden (byte/ charweise überschreiben, nicht das Array =null setzen). Ist aber eher zweitrangig, und wird nur bei speziellen Angriffsszenarien relevant (bei denen der Angreifer direkt oder indirekt Teile deines RAMs lesen kann - und dort Reste von Keys oder Passwörtern findet). Dann hast du aber meistens ohnehin andere Probleme...

Ansonsten sollte das so funktionieren.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #35
Ich habe leider ein Problem gefunden, welches ich irgendwie nicht ganz verstehe.
Scheint doch irgendwie ein Fehler drin zu sein...

Ich habe versucht einen verschlüsselten Text zu entschlüsseln. Leider bekomme ich derzeit immer Null-Pointer-Exceptions...

Ich teste mit dem folgenden, nachdem ich den verschlüsselten DataKey eingelesen und bestätigt habe (MAC-Check successfull), dass funktioniert also schonmal.
[src=java]
String data1 = "Headline und so weiter\n" +
"Hier steht dann so der Text\n" +
"und das ist der erste Absatz\n" +
"und dann weiß ich auch nicht mehr weiter, was ich hier denn alles so schreiben kann, weil das schon ganz schön viel text werden kann und so und und und und und so weiter...";

System.out.println("____________________");
System.out.println(data1);
System.out.println("____________________");

String data_enc = this.crypt.encrypt(data1);
System.out.println(data_enc);
System.out.println("____________________");

String data_dec = this.crypt.decrypt(data_enc);
System.out.println(data_dec);
System.out.println("____________________");
[/src]

Meine Ausgabe ist:
____________________
Headline und so weiter
Hier steht dann so der Text
und das ist der erste Absatz
und dann weiß ich auch nicht mehr weiter, was ich hier denn alles so schreiben kann, weil das schon ganz schön viel text werden kann und so und und und und und so weiter...
____________________
GAXc1jopiM645N47w1w1jC7reDeua6tDP+uEjBHHgiUvhQQjfT5mXx+NTnFTKil7JtiYz4jfDerygh0JbOSYEJMR/QeDMS2VQTie7DutYSN4lGV/+BQv9QJwTReJCl38wRdzhjRAq2J94lE5AzuCgqCJSxZEekCCNSkR58RckjWOfQ2NIYwxTTKvdwGp3B3dl5plLflL6zy27/3RYDPlOZxS2wXyAvZHgCuAnMYYHqZFVRS1wXHMK6HOLiH/zUPfg7ZGDP+Idq83trydNZ/ek7X3VwLxOADug6dAW5N977+DRBe/MPlhd7iTyUKHiVJZehPpF+YU6bjE215Z4SBYspCc7phNncIuZxkhnt+xsEAjLzgJtO5FEYNUIP+QGp6Jk9KtIzKzOQo/HcjiKZq7N7jt8QmURWMAmdbqhynfjT+m/28wZ1UnlpCxkKmJLCdJfriiLaoCsVgP56LY5thkcXpTckMhDuVTShIHb7SYNXgLLOXdvA5/1O9M1XN+vGJ2KWBbCAa3CB1zuKZUHdTI+uDt7zFBhK4G58eKxhoehVk2vieVqYH3BZtLL7Sv7idz345xYF3XNjyy7+PJHW63f4gOqvXBZQnU6LqdoG7ZJMqSQVIH5wkh9ZBm15yW95wsaTZkicqXC+s+N01HVf5c5G8VEBz60xPkePM1UQWMmcDsN+ZKzs6s6jDrWMxZU54TpcqoIw6u+YPwmA==
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
____________________
at org.bouncycastle.crypto.io.CipherInputStream.read(Unknown Source)
at mypackage.AES256_Crypt.decrypt(AES256_Crypt.java:472)
at mypackage.MyClass.open_journal(Myowndiary.java:509)
at mypackage.MyClass.lambda$buildMainWindow$2(Myowndiary.java:261)
...

Mein derzeitiger Code der AES256_Crypt-Methoden:
[src=java]
/**
* Verschlüsselt einen beliebigen String mit dem Datenkey.
*
* @param dataToEncrypt Unverschlüsselte Daten
* @return Mit Base64 codierte verschlüsselte Daten
*/
public String encrypt(String dataToEncrypt) {
String encDataB64 = null;
byte[] encData;
byte[] decData;

if (this.dataKey == null) {
throw new IllegalArgumentException("A dataKey must be defined to encrypt any data.");
}

try {
decData = dataToEncrypt.getBytes("UTF-16");

byte[] iv = this.getNewIV();
// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(true, new AEADParameters(new KeyParameter(this.dataKey), 128, iv)); // 128 = Block-Größe (immer 128 für AES)

// Encrypten, bspw mit CipherOutputStream:
ByteArrayOutputStream baos = new ByteArrayOutputStream();

OutputStream stream = new CipherOutputStream(baos, cipher); // output kann ein beliebiger OutputStream sein, bspw. FileOutputStream

// ... Daten in "stream" schreiben
stream.write(decData);
stream.close(); // damit sollte die MAC an die Daten angehängt werden

// Verschlüsselte Daten aus dem ByteArray-Stream holen
encData = baos.toByteArray();

// IV an die Daten anhängen
encData = ArrayUtils.addAll(encData, iv);

// Verschlüsselte Daten in Base64 speichern
encDataB64 = Base64.toBase64String(encData);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(AES256_Crypt.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(AES256_Crypt.class.getName()).log(Level.SEVERE, null, ex);
}

return encDataB64;
}

/**
* Entschlüsselt den übergebenen String mithilfe des Datenkeys
*
* @param encDataB64 Base64 codierte verschlüsselte Daten
* @return Entschlüsselte Daten
*/
public String decrypt(String encDataB64) {
String decString = "";
byte[] decData = null;

// Prüfen, ob die Zeichenkette Base64-Codiert ist.
if (!this.isBase64Encoded(encDataB64)) {
// throw new IllegalArgumentException("String ist nicht Base64 codiert");
System.err.println("Text is not Base64 encoded and cannot be decoded. No decryption tried.");
return decString;
}

byte[] encData = Base64.decode(encDataB64);

if (this.dataKey == null) {
throw new IllegalArgumentException("A dataKey must be defined to encrypt any data.");
}
if (encData.length < 17) {
throw new IllegalArgumentException("The input is to short to be valid. It has to be more than 16 Bytes. It is only " + encData.length + " Bytes long.");
}

byte[] data = Arrays.copyOfRange(encData, 0, encData.length - 16);
byte[] iv = Arrays.copyOfRange(encData, encData.length - 16, encData.length);

// Initialisieren
GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
cipher.init(false, new AEADParameters(new KeyParameter(this.dataKey), 128, iv));

try (CipherInputStream stream = new CipherInputStream(new ByteArrayInputStream(data), cipher)) {

stream.read(decData);
decString = new String(decData, "UTF-16");

} catch (InvalidCipherTextIOException e) {
System.err.println("Ciphertext was manipulated, MAC check failed!");
// decData = null;
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(AES256_Crypt.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(AES256_Crypt.class.getName()).log(Level.SEVERE, null, ex);
}

// try {
// decString = new String(decData, "UTF-16");
//
// } catch (UnsupportedEncodingException ex) {
// Logger.getLogger(AES256_Crypt.class.getName()).log(Level.SEVERE, null, ex);
// }
return decString;
}

public boolean isBase64Encoded(String string) {
String pattern = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(string);
return m.find();
}
[/src]

Wie entsteht da "plötzlich" die NullPointerException?
 

Timon3

Team ModMii

Registriert
17 Juli 2013
Beiträge
499
In welcher Zeile fangen die Methoden denn an? Im Stacktrace steht 472, welche Zeile wäre das in deinem Post?
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #37
@GameChamp98: Das müsste Zeile 86 entsprechen.

Ich habe es nun noch einmal ausgeführt, dieses mal ist der Fehler minimal anders:
____________________
Headline und so weiter
Hier steht dann so der Text
und das ist der erste Absatz
und dann weiß ich auch nicht mehr weiter, was ich hier denn alles so schreiben kann, weil das schon ganz schön viel text werden kann und so und und und und und so weiter...
____________________
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
dNHUD+BgMvFCwb8Jzm9SBbIcTG3+AitEIStTwX6qhhdUFu/uL6UR6GqFHdKJjucsA9jLl0HI7/y9/jrZcgR/ItZLGyw3gAgGgxWXQ1wPe01SBdFKoBoScfV5u8e4xHwOlT3mJJSLs6eLr8S0D0eIwWT+90Ye+3tTJ4QS7aQu/U2VsTGKn8ERXp+Fqt2o9MjI8qSFbjpHpITQHIbKspEvcNse4jhA5pl0Ab3MaecjmqXxRoiw5t8OuAQZNPO8zbdtQw4tuvStlhyu8EZJnLiDO1Qf3pXb36hduZGGCIVMF/Y/wlmjTyamsR4VaeSQuWrFeaFA/3QxeAQiW1D+A1GMeALRgMCHuC6hC/Ywn3V2B7EjQoRnRrMChMF9H16/JcesieA3EhdQ5EMDrCGRvh0s95pgdeM07MKQE175tWgpGDZ4ugzMbUK4pLak4Iu+cHwBn9p+nsZX++oVGPbIHSBPPnTSGCUkwu/jQaDcf9+OtuFrmsVvPJxvhCB8GWF1zt5PJOucRqy+0yMcgI7LPZZPwJxn6mwX+xKIqbezNmOxatALOsxu+WbCSUgSL0eAIAZm0Wgsg2DRcSZTR+iFPWOcX25bDW6q3ox9FaSepbE8DqLwvfpauc4+fmHNiijiL75l7kFENIKBW4Q3JCXVSl3ol67Omtvbk5DpU5cag+KHCrUCKBhE2FNmPvUAhTW5xtDnRCJP9l4F5P9eAQ==
____________________
at org.bouncycastle.crypto.io.CipherInputStream.read(Unknown Source)
...

Die Exception wird nun davor ausgegeben...

EDIT:
Um die Daten oben eventuell selber zu entschlüsseln:
Der verwendete Datenkey lautet (Base64-codiert):
0IblYQrZQXUnrLMQmq7Bo2sxTtn0i1x2YOZX/V3S020=
Der verwendete IV sind jeweils die letzten 16 Byte des codierten Ciphertextes
 
Zuletzt bearbeitet:

Timon3

Team ModMii

Registriert
17 Juli 2013
Beiträge
499
Lass dir doch einfach mal in Zeile 85 ausgeben, ob der Stream gleich null ist. Wenn das der Fall ist, wird der Fehler entweder in einem der von dir überlieferten Parameter oder in der Bibliothek selber liegen, da musst du dann evtl herumarbeiten. Das einfachsten wäre natürlich, in dem Falle, dass der Stream null ist, einen leeren String zu returnen, aber es wäre interessanter die Ursache selbst herauszufinden.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #39
@GameChamp98: Mit einem if(stream.equals(null)) oder if(stream == null) passiert nichts. Das wird einfach übergangen.

EDIT:
Ich sehe gerade ganz weit unten bei dem Fehler-Stack:
Suppressed: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher
at org.bouncycastle.crypto.io.CipherInputStream.finaliseCipher(Unknown Source)
at org.bouncycastle.crypto.io.CipherInputStream.close(Unknown Source)
at mypackage.AES256_Crypt.decrypt(AES256_Crypt.java:477)
... 41 more
Caused by: org.bouncycastle.crypto.InvalidCipherTextException: data too short
at org.bouncycastle.crypto.modes.GCMBlockCipher.doFinal(Unknown Source)
... 44 more
477 ist die erste catch-Anweisung

EDIT2:
Wenn ich die Fehlermeldung google, komme ich leider nur zu Minecraft-Crash-Reports, die mir in keinster Weise helfen.
Ich habe gerade auch noch mal die Base64-Stellen überprüft. Das codieren und decodieren passt perfekt - die Bytes sind exakt die, die sie sein sollen.

EDIT3:
Beim "Nachverfolgen" der Schritte, an denen der Error entsteht, gelange ich nach dem Aufruf von stream.read(...) leider nur sofort zur NullPointerException - allerdings ist stream nicht Null und die übergebene Variable muss anfangs null sein. muss ein ausreichend großes Byte-Array sein. (ich habe mich für 9999999 Bytes entschieden). Die Größe des Byte-Arrays entspricht der Größe des verschlüsselten Byte-Arrays.

EDIT4:
Leider doch noch nicht ganz am Ziel:
die letzten paar Zeichen werden abgeschnitten...


EDIT5:
Anscheinend muss ich den Text auf 16 Blöcke vollpadden, damit da nichts verloren geht. Ich habe einfach die fehlenden Bytes durch '0' aufgefüllt und beim decrypten wieder abschneiden lassen. Ich speichere nun als letztes Byte in meinen Daten, wieviele Bytes ich angehängt habe. Ist das "sicher" oder zählt das schon wieder als unsicher, weil ich damit eine Information an den Angreifer geben würde?
 
Zuletzt bearbeitet:

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Hallo,
dein Problem liegt nicht am Padding. GCM ist ein "Streaming Mode", d.h. die Länge von Input und Output muss kein Vielfaches der Blocklänge sein.

Dein Problem liegt offenbar in der Länge des Ausgabe-Arrays. Das könnte man dadurch lösen, dass man auch beim Decoden wieder in einen ByteArrayOutputStream schreibt - dann musst du dich vorher nicht auf eine Länge festlegen.
Problem 2 (das mit dem fehlenden letzten Block) liegt daran, dass der CipherStream die Daten immer blockweise verarbeitet - der letzte, halbe Block wird also erst entschlüsselt, wenn der Stream zu Ende ist (geschlossen wird).

Zusammengefasst sähe das etwa so aus:
[src=java]try {
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
CipherOutputStream decoder = new CipherOutputStream(decoded, cipher);
decoder.write(data);
decoder.close();
decString = new String(decoded.toByteArray(), "UTF-16");
} catch (InvalidCipherTextIOException e) {
//...[/src]


PS: Für Padding gilt wie für alle anderen Crypto-Konstruktionen: Möglichst wenig selber machen! Deine Konstruktion oben hört sich an, als könnte man Text-Bytes "abschneiden", indem man das letzte Byte verändert. Sowas könnte zu einen "Padding Oracle" führen (auch wenn es in deinem Fall wohl keinen Angriffsvektor dafür gibt). Wenn man mit Verfahren arbeitet, die Padding benötigen (z.B. AES-CBC), sollte man ein standardisiertes, vorgefertigtes Padding-Verfahren aus der Library nehmen (bspw. PKCS7Padding).
 
Oben