# AES Verschlüsselung - File Headers korrupt



## SaschaSquare (18. Mai 2011)

Hallo Java'lers,

Vorweg: Ich bin Umsteiger von C# nach Java und setze mich gerade mit den alltäglichen Problemen auseinander.
Jetzt komme ich an einem bestimmtem Problem aber einfach nicht weiter. Es geht um Verschlüsselung mit AES.

Folgender Ablauf:
Ich verschlüssel eine Datei (z.B. jpg/pdf) mit Hilfe von der Cipher Klasse und einem AES Key. -> Funktioniert (denke ich)
Wenn ich die Datei allerdings wieder entschlüssel, bekomme ich eine kaputte Datei zurück.
Bei einem Bild sagt Windows beispielsweise es sei defekt...
Ich habe beide Dateien (original und wieder-entschlüsselte) in einem Editor geöffnet und miteinander verglichen. Und folgendes festgestellt (dies gilt übrigens nicht nur bei Bild-Dateien sondern auch bei beispielsweise PDF-Dateien).

Die ersten paar Bytes (im Header der Datei) stimmen komplett nicht überein und sind sogar unterschiedlich lang. Überschreibe ich die entsprechenden Bytes in dem wieder-entschlüsselte File mit den originalen Bytes und speicher dieses File ab, so lässt sich das Bild/PDF einwandfrei öffnen.

Mir stellt sich nun natürlich die Frage, wieso die ersten paar Bytes jeder Datei nach dem Verschlüsseln/Entschlüsseln unterschiedlich sind !?

Zum Debuggen habe ich bereits überprüft ob es an der Art des Lesens/Schreibens liegt, aber dabei fiel kein Fehler auf (die Datei wurde erfolgreich 'kopiert')...

Zur Verdeutlichung hier etwas Code:

Ich danke schon mal im voraus für Bemühungen 

GenerateKey()
Erzeugt den AES Key der später zur Ver-/Entschlüsselung verwendet wird.

```
// GLOBAL
public String CryptMode = "AES/CBC/PKCS7Padding";

public void GenerateKey() {
		KeyGenerator keyGen;
		try {
			
			SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
			char[] password = {'a', 'b', 'c'};						
			byte[] salt = SecureRandom.getSeed(8);			
			KeySpec spec = new PBEKeySpec(password, salt, 8, 256);			
			SecretKey tmp = factory.generateSecret(spec);			
			this.secret = new SecretKeySpec(tmp.getEncoded(), "AES");			
			
			final Cipher cipher = Cipher.getInstance(this.CryptMode);
			//final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, this.secret);
			
			AlgorithmParameters params = cipher.getParameters();
			iv = params.getParameterSpec(IvParameterSpec.class).getIV();
			
		}
		catch (Exception ex) {
			LogToScreen(ex.getMessage());
		}
	}
```


Encrypt()
Diese Methode verschlüsselt die Übergebene Datei und legt sie unter einem neuen Dateinamen ab

```
public void Encrypt(String srcName, String destName) {
    	File path = Environment.getExternalStorageDirectory();
    	File file = new File(path, destName);  
    	
    	File fileinput = new File(path, srcName); 
    	    	    
    	if (path.canWrite()) {    		
    		try {				
    			final Cipher cipher = Cipher.getInstance(this.CryptMode);
    			cipher.init(Cipher.ENCRYPT_MODE, this.secret);
    			    			    			
    			final FileOutputStream fos  = new FileOutputStream(file);
    			final BufferedOutputStream bos = new BufferedOutputStream(fos);

    			final FileInputStream fis = new FileInputStream(fileinput);
    			final BufferedInputStream bis = new BufferedInputStream(fis); // ADDED
    			
    			byte[] bin = new byte[bis.available()];
    			bis.read(bin, 0, bin.length);
    			bos.write(cipher.doFinal(bin));
    			
    			bis.close();
    			fis.close();
    			
    			bos.flush();    			
    			bos.close();    			
			} catch (Exception e) { 
				e.printStackTrace();
				LogToScreen("Encrypt:" + e.getMessage());
			}    	
    	}
    	else {
    		LogToScreen("CanWrite = FALSE");
    	}
    }
```

Decrypt()
Entschlüsselt die Übergebene Datei und legt sie unter einem neuen Dateinamen ab

```
public void Decrypt(String srcName, String destName) {
    	File path = Environment.getExternalStorageDirectory();
    	File file = new File(path, srcName);
    	
    	File fileoutput = new File(path, destName);
        
        try {			        	
        	final Cipher cipher = Cipher.getInstance(this.CryptMode);
			cipher.init(Cipher.DECRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
										
			final FileInputStream fis  = new FileInputStream(file);
			final BufferedInputStream bis = new BufferedInputStream(fis);
			
			byte[] btest = new byte[bis.available()];			
			bis.read(btest, 0, btest.length);
			
			final FileOutputStream fwrite = new FileOutputStream(fileoutput);
			final BufferedOutputStream bwrite = new BufferedOutputStream(fwrite);
			
			bwrite.write(cipher.doFinal(btest));
			bwrite.flush();
			bwrite.close();
			fwrite.close();
			
			
		} catch (Exception e) {
			e.printStackTrace();
			LogToScreen("Decrypt:" + e.getMessage());
		}        
    }
```


Mit freundlichen Grüßen
SaschaSquare


----------



## HoaX (18. Mai 2011)

Mal die Dateigrößen verglichen? Ich vermute stark dass wegen

```
byte[] bin = new byte[bis.available()];
```
nicht die komplette Datei gelesen wird.


----------



## SaschaSquare (18. Mai 2011)

Die Dateigrößen der originalen Datei und der wieder-entschlüsselten Datei sind identisch.
Einzig die verschlüsselte Datei hat eine andere größe, was aber meiner Meinung nach sein kann.

Ein Diff-Vergleich mit einem Editor der beiden Dateien zeigt auch, dass in dem wieder-entschlüsselten im Header a) zu viele Zeichen sind und b) bis zu einem gewissen Punkt auch falsche Zeichen.

Kopiere ich die Zeichen aus dem Originalen und überschreibe damit die falschen Zeichen, ist die Datei auch korrekt und lässt sich öffnen...


----------



## Ebenius (18. Mai 2011)

Ad hoc: Vorsicht in Zeile 15: InputStream.read(byte[], int, int) gibt einen [c]int[/c] zurück. Lies die API-Doc nochmal genau!

Ebenius


----------



## SaschaSquare (18. Mai 2011)

Ebenius: Ich habe den Debugger gerade mal an der Zeile 15 mit dem Read angehalten.
Ich bekomme als Rückgabe der Methode ein -1 zurück was mir laut der API Doc sagt, dass ich den Stream komplett "ausgelesen" habe und nichts mehr übrig ist zum Lesen -> Korrekt?!

Das Problem scheint ja zu sein, dass ich nicht zu wenig auslese sondern anscheinend a) zu viel und b) etwas falsches...


----------



## Ebenius (18. Mai 2011)

Hm. In Deinem Code-Beispiel oben heißt [c]-1[/c] konkret, dass Du genau gar nichts gelesen hast. Daraus sollte folgen, dass 1. [c]btest[/c] null-lang ist und 2. die Datei eine Nullänge haben sollte. Oder hab ich ne Schleife überlesen?

Sehr sonderbar.

Ebenius


----------



## SaschaSquare (18. Mai 2011)

Wenn ich von der Read() Methode ein -1 zurück bekomme, heißt das laut der API Docu das ich den Stream "bis zum Ende" ausgelesen habe.
Der Debugger zeigt anschließen bei .available() eine 0 an was mich eigentlich darin bestätigt dass ich den Stream komplett gelesen habe.

Bei der Entschlüsselung habe ich nun auch mal den Rückgabewert der Read() Methode ausgewertet und diese zeigt mir kein -1 sondern die Anzahl Bytes die die Datei auch wirklich groß ist.
Heißt das nun dass ich den Stream nicht vollständig gelesen habe weil ich kein -1 zurück bekommen habe?

Und das verwirrende ist ja, dass ich in meiner wieder-entschlüsselten Datei nicht zu wenig Bytes habe, sondern zu viele...


----------



## Ebenius (18. Mai 2011)

Achso, Du bezogst Dich auf die encrypt-Methode. Ich dachte Du bekommst in Decrypt eine [c]-1[/c] in Zeile 15 (oben).

Ohne mich mit JCE wirklich auszukennen, sollte Dein Decrypt-Code mit Standard I/O meiner Meinung nach in etwa (Achtung: Code im Browser getippt) so aussehen:

```
public void decrypt(String srcName, String destName) {
        File path = Environment.getExternalStorageDirectory();
        File file = new File(path, srcName);
        
        File fileoutput = new File(path, destName);
        
        try {                       
            final Cipher cipher = Cipher.getInstance(this.CryptMode);
            cipher.init(Cipher.DECRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
                                        
            final FileInputStream fis  = new FileInputStream(file);
            final BufferedInputStream bis = new BufferedInputStream(fis);
            final FileOutputStream fwrite = new FileOutputStream(fileoutput);
            final BufferedOutputStream bwrite = new BufferedOutputStream(fwrite);
            final CipherOutputStream ciphwrite = new CipherOutputStream(bwrite, cipher);

            final byte[] arr = new byte[1024];
            int bytesRead;
            while((bytesRead = bis.read(arr, 0, arr.length)) != -1) {
                ciphwrite.write(arr, 0, bytesRead); // write read bytes from buffer to cipher output stream
            }
 
            cyphwrite.flush(); // flushes underlying streams as well; implicitly done in close() since JRE 6
            cyphwrite.close(); // closes underlying streams as well; calls doFinal on the cipher instance
        } catch (Exception e) {
            e.printStackTrace();
            LogToScreen("Decrypt:" + e.getMessage());
        }        
    }
```

Encrypt hab ich mir nicht angesehen; sollte aber aus meinem Code-Schnipsel einfach herleitbar sein.

Gleich mal zum Naming: In Java alle Methoden und Variablen mit kleinem Buchstaben vorn: [c]doSomething(someVariable)[/c].

HTH, Ebenius


----------



## SaschaSquare (18. Mai 2011)

Ich werde jetzt mal nach Hause fahren und etwas Essen, anschließend begebe ich mich noch mal an das Problem und versuche es auf deine Art.

Schon mal vielen Dank für die Unterstützung, sobald ich heute Abend was habe melde ich mich nochmal.
Und gegebenenfalls einen schönen Abend noch 


MfG
SaschaSquare


----------



## Andi_CH (19. Mai 2011)

Ohne alles zu analysieren - nimm doch zwischen lesen und schreiben die kryptologie raus - die geschrieben Datei sollte dann ja verwendbar sein. So kannst du prüfen ob der Fehler in den Lese- Schreibloops oder in der Kryptologie steckt.


----------



## SaschaSquare (19. Mai 2011)

Ja ich habe die Kryptographie bereits entfernt gehabt und das Lesen/Schreiben auf die selbe Weiße versucht und die Datei wurde vollständig und korrekt "kopiert".

Mich wundert es eben, dass nicht die gesamte Datei nach einem Verschlüsseln/Entschlüsseln defekt ist, sondern eben nur der Header bzw. die ersten Bytes einer Datei davon betroffen sind...

--  -- EDIT --  --

Ich habe im Anhang mal ein Bild angehangen was den Unterschied der beiden Dateien verdeutlicht.
Die obere Zeile ist das originale Bild.
Die untere Zeile hingegen ist das wieder-entschlüsselte Bild welches von Windows nicht mehr angezeigt werden kann.
Es sind jeweils die ersten Bytes der Datei welche sich ab dem roten Strich nicht mehr unterscheiden. Kopiere ich aus der ersten Zeile ab dem roten Strich bis zum Ende nach links und füge das in die untere Zeile ein und speichere, ist das Bild anschließend korrekt darstellbar.

Es sind also wirklich nur die ersten paar Bytes der Datei welche "defekt" sind.

Ich habe überhaupt keine Ahnung mehr woran das liegen könnte...


--  -- EDIT --  --


Das Problem ist gelöst!
Es lag daran, dass wir zwar den CBC (Cipher Block Chaining Mode) genutzt hatten, den IV (Initialisierungsvektor) allerdings beim Entschlüsseln nicht beim cipher.init() übergeben hatten.
Letztendlich also ein dummer Fehler...

Für jemanden mit dem selben Problem, hier ein interessanter Link zum IV: Initialisierungsvektor ? Wikipedia

Dennoch vielen vielen Dank für eure Mühe mir zu helfen! :toll:


----------

