XOR Verschlüsselung Key Datei

Hallo

ich habe mich ein wenig mit dem Thema Verschlüsselung beschäftigt und fand den Gedanken interessant, dass eine XOR - Verschlüsselung mit einer Schlüssellänge >= Input nicht zurück zu rechnen ist. Dabei dacht mir, ich könnt doch auch mal Dokumente gegen mp3s als Schlüssel ver - und entschlüsseln. Es ist folgendes kleines Projekt entstanden, was haltet ihr von dieser Umsetzung?

Vorteil:
sichere Verschlüssung

Nachteil:
hoher Zeitaufwand bei größeren Dateien

public class XORCrypter {
	public enum CrypterMode {
		ENCRYPT,
		DECRYPT;
	}
	
	private File inputFile;
	private File outputFile;
	private File keyFile;
	private CrypterMode mode;
	private List<CrypterListener> listener = new ArrayList<CrypterListener>();
	private boolean running = false;
	
	private XORCrypter(File inputFile, File outputFile, File keyFile, CrypterMode mode) throws IllegalArgumentException {
		this.inputFile = inputFile;
		this.outputFile = outputFile;
		this.keyFile = keyFile;
		this.mode = mode;
		
		if(inputFile.length() > keyFile.length()) {
			//throw exception
			//for save xor encryption key may not repeat
			throw new IllegalArgumentException("key file may not be smaller than input file");
		}
		
	}
	
	/**
	 * Creates a crypter object to encrypt
	 * 
	 * @param inputFile
	 * @param outputFile
	 * @param keyFile
	 * @throws Exception 
	 */
	public XORCrypter(File inputFile, File outputFile, File keyFile) throws IllegalArgumentException {
		this(inputFile, outputFile, keyFile, CrypterMode.ENCRYPT);
	}
	
	/**
	 * Creates a crypter object to decrypt
	 * 
	 * @param inputFile
	 * @param keyFile
	 * @throws Exception 
	 */
	public XORCrypter(File inputFile, File keyFile) throws IllegalArgumentException {
		this(inputFile, null, keyFile, CrypterMode.DECRYPT);
	}
	
	public void doWork() {
		running = true;
		
		FileInputStream inputFin = null;
		FileInputStream keyFin = null;
		FileOutputStream outputFout = null;
		
		try {
			inputFin = new FileInputStream(inputFile);
			keyFin = new FileInputStream(keyFile);
			
			if(mode == CrypterMode.DECRYPT) {
				//read prefix
				
				//get length
				byte[] prefixData = new byte[Integer.SIZE / 8];
				inputFin.read(prefixData);
				
				byte[] keyData = new byte[prefixData.length];
				keyFin.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				int prefixLength = byteArrayToInt(prefixData);
				
				if(prefixLength > 64) {
					//invalid key
					//and now?
				}

				//get prefix data
				prefixData = new byte[prefixLength];
				inputFin.read(prefixData);
				
				keyData = new byte[prefixData.length];
				keyFin.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				
				String outputFileName = getNextPossibleFileName(new String(prefixData));
				outputFile = new File(outputFileName);
			}
			
			outputFout = new FileOutputStream(outputFile);
			
			if(mode == CrypterMode.ENCRYPT) {
				//write prefix
				
				byte[] prefixData = generatePrefix();
				
				byte[] keyData = new byte[prefixData.length];
				keyFin.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				
				outputFout.write(prefixData);
			}
			
			
			//write data
			int dataByte;
			int keyByte;
			while(running && (dataByte = inputFin.read()) != -1) {
				keyByte = keyFin.read();
				
				dataByte ^= keyByte;
				
				outputFout.write(dataByte);
			}
			
			if(running) {
				for(CrypterListener l : listener) {
					l.finished();
				}
			
				running = false;
			}
			else {
				//aborted
				
				for(CrypterListener l : listener) {
					l.failed("process stopped");
				}
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			for(CrypterListener l : listener) {
				l.failed(e.getMessage());
			}
		}
		finally {
			try {
				if(inputFin != null)
					inputFin.close();
				
				if(keyFin != null)
					keyFin.close();
				
				if(outputFout != null)
					outputFout.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public void addListener(CrypterListener l) {
		listener.add(l);
	}
	
	public void removeListener(CrypterListener l) {
		listener.remove(l);
	}
	
	private byte[] crypt(byte[] data, byte[] key) {
		byte[] cryptedData = new byte[data.length];
		
		for(int i = 0; i < cryptedData.length; i++) {
			cryptedData** = (byte) (data**^key**);
		}
		
		return cryptedData;
	}
	
	public boolean isRunning() {
		return running;
	}
	
	public void stop() {
		running = false;
	}
	
	private byte[] generatePrefix() {
		String fileName = inputFile.getName();
		
		ByteBuffer b = ByteBuffer.allocate(Integer.SIZE / 8 + fileName.getBytes().length);
		b.putInt(fileName.getBytes().length);
		b.put(fileName.getBytes());
	
		return b.array();

	}
	
	private int byteArrayToInt(byte[] b) {
	    return   b[3] & 0xFF |
	            (b[2] & 0xFF) << 8 |
	            (b[1] & 0xFF) << 16 |
	            (b[0] & 0xFF) << 24;
	}
	
	private String getNextPossibleFileName(String name) {
		int counter = 0;
		String nameToTest = name;
		
		while(new File(nameToTest).exists()) {
			counter++;
			nameToTest = counter + "_" + name;
		}
		
		return nameToTest;
	}
}

dazu ein kleiner Listener

public interface CrypterListener {

	void finished();
	
	void failed(String msg);
}

und eine mögliche Anwendung

		File input = new File("file");
		File key = new File("key.mp3");
		File output = new File("file.enc");
		
		XORCrypter enc;

		enc = new XORCrypter(input, output, key);
		enc.doWork();

		XORCrypter dec;

		dec = new XORCrypter(output, key);
		dec.doWork();

Das nennt sich dann OTP (One Time Pad). Allerdings ist diese Art der Verschlüsselung angreifbar.
Beispiel: Man sichert Überweisungsdatensätze damit. Diese sind binär immer gleich aufgebaut. Nun weiß man, dass z. B. an der 48. Stelle der Überweisungsbetrag steht. Nun kann ich den Wert zufällig ändern - ohne dass es jemandem auffällt.
Um so ein Verfahren also nutzbar zu machen, muss man auch noch einiges mehr beachten. Die Integrität kann z. B. durch einen kryptographischen Hash gewährleistet werden. Die Authentizität müsste entweder durch ein asymmetrisches Verschlüsselungsverfahren (ggf. mit PKI) oder ganz einfach durch einen weiteren PSK (Pre-Shared-Key) geprüft werden.

Siehe auch hier:

Deinen Code schaue ich mir später nochmal an und schreibe dann ggf. noch etwas dazu. Habe gerade keine Zeit :wink:

wenn man Wissen voraussetzt, also der Angreifer das Verfahren kennt und deinen PC mit allen lokalen MP3s vorliegen hat,
dann könnte Ausprobieren schnell gehen, wirst ja wohl weniger MP3s haben als ein einfaches 8stelliges Passwort von (~70)^8 Möglichkeiten

oder unter anderen Voraussetzungen haben MP3s auch andere, etwas unnötig so bekannte Keys zu verwenden, kannst auch beliebige eigene lange Dateien erstellen,
mit Java-Programm Zufallszahlen, Sounds als MP3 aufnehmen oder was auch immer,
dann wäre wiederum wichtig dass niemand den langen Key bekommt


wozu Integer.SIZE / 8 im Code? du willst doch wohl 4 haben, dann schreibe auch 4,
durch Verkomplizierung wird es nicht besser,
schon gar nicht an zwei Stellen unabhängig voneinander, eine Konstante einführen

das byteweise Einlesen aus den Streams ist oder könnte zumindest extrem langsam sein, für jedes Byte neu auf die Festplatte zugreifen,
BufferedInputStream zwischengeschaltet kann dies effektiv lösen, Output genauso, Schreiben kann noch langsamer sein als Lesen,

oder du nutzt gleich dicke Arrays die nach und nach gelesen werden, dann z.B. nur einen Aufruf der Reader-Klasse je bisher 8000 einzelne,
spart zumindest Unmengen an Java-Arbeit im Hin und Her,
wobei man da nicht zu sicher sein sollte, Array-Zugriffe gegenzurechnen…, aber falls man einen BufferedInputStream hat, dann dort eh auch Arrayzugriffe,

am mit einfachen Mitteln schnellsten vielleicht die bisherigen Streams zusammen mit eigenen Arrays

das Nachladen von Key-Bytes könntest du allein der crypt-Methode überlassen,
dort immer genau so viele laden wie das übergebene Array lang ist, mit einem als Attribut gecachten Array (alle Streams usw. auch Attribute),
dann müsstest du zumindest dazu nicht 4 Codestellen haben

bedenke, dass die verschlüsselte Datei durch den Dateinamen noch länger werden kann, dafür muss auch Key da sein,
bis zu 64 oder 68 solltest du in die Rechnung noch einbeziehen,

falls diese genaue Info nicht einem Angreifer, der mit Dateilängen testet, verraten werden soll ;), dann pauschal + 1000

Erst einmal danke für die Antworten und Hinweise.

Bin gerade fleißig dabei an die Tafel zu schreiben „Wenn ich 4 haben möchte, schreibe ich auch 4“ :slight_smile:

Mp3s hab ich jetzt für diesen Prototypen gewählt, der Ursprungsgedanke war gegen die Mediathek der öffentlich rechtlichen zu Verschlüsseln, um einen sieben Tage lang gültigen wegwerf Schlüssel zu haben, da dieses aber wesentlich umfangreicher zu realisieren ist, enstand diese Version zum Testen der Funktionalität und Geschwindigkeit.

@SlaterB
Gleich zu Hause probier ich mal auf BufferedStreams umzustellen, dabei Geschwindigkeitsunterschiede messen und dann zu verstehen was du mit „oder du nutzt gleich dicke Arrays die nach und nach gelesen werden“ meinst.

Und danke für den Hinweis mit der länge und dem Dateinamen, habe ich glatt übersehen.

hier ein Beispiel mit 1000er-Array
http://www.java2s.com/Tutorial/Java/0180__File/ReadfromfilewithBufferedInputStream.htm
einfaches Kopieren bietet sich als Test an

edit: äh, im Beispiel wird gar nicht geschrieben, aber das kann man ja aus den anderen Beispielen des Kapitels zurechtkopieren,
wichtig ist beim Schreiben wie beim String-Konstruktor hier, bytesRead zu nutzen, nicht das komplette Array, welches vielleicht nur zum Teil gefüllt ist

Ich hatte irgendwie nicht so ganz realisiert, dass du als Schlüssel eine mp3 verwendest. Das macht gar keinen Sinn. Die Sicherheit beim xor mit einem langen Schlüssel liegt in der Zufälligkeit des Schlüssels. Wenn der Schlüssel nicht echt zufällig ist, dann kann man die Nachricht ohne Probleme entschlüsseln. Und dann auch noch eine mp3 zu verwenden hat den Nachteil, dass eine mp3 Struktur enthält. Z. B. kennt man auf jeden fall die ersten paar Bytes, weil da der mp3 Header kommt. Sicherlich gibt es noch viel mehr Regelmäßigkeiten, die sich ausnutzen lassen.
Mit Kryptographie hat das nichts mehr zu tun - als Programmierübung ist das aber vielleicht ganz interessant.

Mp3 ist doch nur als dummy für was auch immer anzusehen, es lässt sich jede beliebige Datei benutzen, daher hatte ich im ursprünglichen Titel das Wort “MP3” auch vermieden.

Gedankengang:
Person A will Datei an Person B senden. Beide haben sich vorher auf einen einmal zu verwendenden Schlüssel geeinigt, der an beiden Standorten zu Verfügung steht. Ob das jetzt eine mp3, eine video datei oder einfach nur eine schöne große Bilddatei ist spielt dabei doch keine Rolle, da der Angreifer keine Möglichkeit hat zu erahnen gegen was verschlüsselt wurde, oder hab ich da einen Denkfehler drin?

hab den Titel erneut angepasst :wink:

bei Bilddatei sind womöglich auch manche Bytes bekannt im Aufbau,
wenn zudem noch noch die zu verschlüsselden Datei am Anfang typischen Aufbau hat, bzw. bekannt ist was du noch vorschaltest, Dateiname mit Endung,
mag es da manche Fragezeigen geben,

aber alles sehr diffuse Möglichkeiten und von von ein paar Bytes kann man den Rest auch nicht entschlüsseln, gerade hier nicht mit One Time Pad

man kann sicher von z.B. privaten Bildern & Dateien ausgehen, die kein Mensch auf der Welt kennt, außer wenn von einen der PCs gestohlen
(dann kann man auch gleich die unverschlüsselte Datei mitnehmen…, sofern zu diesem Zeitpunkt vorhanden) oder in Netzwerken öffentlich usw.

setzt voraus dass die privaten Keys einmal übertragen wurden (so wie eben allgemein die Aufgabe mit One Time Pad-Key),
dann kann man auch ganz salopp den Key-Dateinamen dazuschreiben, vielleicht noch in dein Crypt-Format am Anfang einfügen,

in dem Fall halte ich das auch für unknackbar, paar Bytes am Anfang geschenkt,

könnte man notfalls auch noch verfeinern: erste xyz Bytes des Keys grundsätzlich weglassen,
von Dateilänge des Keys auch noch einen zusätzlichen variablen Faktor des Streichens,

  • oder schlicht immer rückwärts Key verwenden, aber am Ende vielleicht auch kleine Besonderheiten?
  • oder sonst wie durcheinander, in der Mitte anfangen, Algorithmus der Byte Mitte +1, Mitte -1, Mitte +2, Mitte -2, Mitte +3 usw. verwendet,
    da beide Seiten den Key haben wird das problemlos gleichmäßig gemacht

Nein, das einzige was wirklich sicher ist, ist eine zufällig generierte Datei. Und dabei kommt es extrem auf die stärke des verwendeten PRNG an. Am besten sind da echt zufällige Zahlen.

Man glaubt gar nicht, was mit statistischen Angriffen alles möglich ist.
Wie gesagt: als Fingerübung ist es nett sowas zu implementieren. Kryptographisch hat es keinen echten Wert.

also

Kryptologisch unsichere Schlüssel

Ein Kardinalfehler ist, als Einmalschlüssel keine zufällige Buchstabenfolge, sondern eine Textpassage zu benutzen. Selbst wenn der verwendete Text einmalig ist und niemandem (außer den beiden Kommunikationspartnern) bekannt ist, weisen Buchstabenfolgen, die aus einem „sinnvollen“ Text stammen, im Gegensatz zu zufälligen Buchstabenfolgen (Zufallstexten), statistisch auswertbare Abhängigkeiten auf, die eine Entschlüsselung möglich machen können.

Damit aus dem verschlüsselten Text ohne Schlüssel nicht wieder das Original rekonstruiert werden kann, muss der Schlüssel kryptologisch sicher sein, d. h. er darf von einer echt zufälligen Zahlen- bzw. Buchstabenfolge nicht zu unterscheiden sein. Ist er dies nicht, so kann ein Angreifer u. U. durch Kryptoanalyse, auch ohne Kenntnis des Schlüssels, den Text entschlüsseln.

Um einen kryptologisch sicheren Schlüssel zu erzeugen wird im Idealfall ein physikalischer Zufallszahlengenerator verwendet, da nur solche echt zufällige, d. h. nicht-deterministische, Werte liefern

Es ist nicht für den produktiven Einsatz gedacht, Fingerübung triffts schon ganz gut, find das Thema aber spannend, denn auch der beste Algorithmus hilft einem nicht, wenn der Zufallsgenerator manipuliert ist. Aber dass soll jetzt keine Diskussion darüber werden warum eine Bilddatei nicht random genug ist :slight_smile:

Was haltet ihr denn von der Umsetzung, mich persönlich stören irgendwie die beiden Kontruktoren die entscheiden, ob ver - oder entschlüsseln, allerdings sind beide Operation auch so gut wie identisch.

Kostruktoren gehen


noch was für die Gallerie:
wenn die Key-Datei nicht komplett zufällig ist, dann muss es eben die zu übertragende Datei sein :wink:
z.B. ein frisch aus dem Ofen akzeptabler Zufalls-One-Time-Pad-Key,

mit Key-Datei den Zufalls-Key sicher übertragen,
mit dem dann auf beiden Seiten bekannten Zufalls-Key die Zieldatei sicher übertragen :wink:

leider den Zufalls-Key damit 2x benutzt, das widerspricht auch einer Grundannahme („One-Time“),
gewiss wieder angreifbar, die XORs jeweils verknüpfen sicher sehr leicht,
nach Algorithmus noch hin und her springen abhängig vom Key?.. ein wenig mehr Knack-Aufwand

Ich könnte mir auch vorstellen, dass bei einer XOR-Verschlüsselung der Effekt auftritt, der hier in den Grafiken von Tux gezeigt ist. Das müsste man spaßeshalber einfach mal testen.

Edit: der Test ergab: OTP scheint auf den ersten Blick Muster komplett zu zerstören.

Ich glaube in der Beziehung Schlüssel > Input gibts beim Stichworkt Steganographie schon sehr viel ausgefallenere Verschlüsselungsmethoden. Steganographie ist das verstecken von Daten in anderen Daten, z.B Bildern, MP3s, Videos. Üblicherweise wird die Datei dabei verschlüsselt und quer über das Trägermedium verteilt.
Google mal ein bisschen, ist ganz interessant das Thema.

Wie du schon richtig schreibst, sind Steganographie und Verschlüsselung zwei verschiedene Paar Schuhe. Steganographie allein ist schon ein recht komplexes Thema. Steganographisch versteckte Daten lassen sich häufig relativ leicht aufspüren, weil sie in regelmäßigen Daten versteckt sind. Dort fällt das zusätzliche Rauschen dann auf, weil man es vom “Grundrauschen” (bei Fotografien, Audioaufnahmen, …) unterscheiden kann.
Deshalb müssen die Daten trotzdem noch verschlüsselt werden.

Die Umstellung auf BufferedStreams ergab bei mir eine Zeitoptimierung um ca den Faktor 6, was das ganze schon angenehmer macht.

aktueller Code
[spoiler]

public class XORCrypter {
	public enum CrypterMode {
		ENCRYPT,
		DECRYPT;
	}
	
	private File inputFile;
	private File outputFile;
	private File keyFile;
	private CrypterMode mode;
	private List<CrypterListener> listener = new ArrayList<CrypterListener>();
	private boolean running = false;
	
	private XORCrypter(File inputFile, File outputFile, File keyFile, CrypterMode mode) throws IllegalArgumentException {
		this.inputFile = inputFile;
		this.outputFile = outputFile;
		this.keyFile = keyFile;
		this.mode = mode;
		
		if(keyFile.length() - inputFile.length() < 100) {
			throw new IllegalArgumentException("key file needs to be larger than input file");
		}
		
	}
	
	/**
	 * Creates a crypter object to encrypt
	 * 
	 * @param inputFile
	 * @param outputFile
	 * @param keyFile
	 * @throws Exception 
	 */
	public XORCrypter(File inputFile, File outputFile, File keyFile) throws IllegalArgumentException {
		this(inputFile, outputFile, keyFile, CrypterMode.ENCRYPT);
	}
	
	/**
	 * Creates a crypter object to decrypt
	 * 
	 * @param inputFile
	 * @param keyFile
	 * @throws Exception 
	 */
	public XORCrypter(File inputFile, File keyFile) throws IllegalArgumentException {
		this(inputFile, null, keyFile, CrypterMode.DECRYPT);
	}
	
	public void doWork() {
		running = true;
		
		InputStream inputIn = null;
		InputStream keyIn = null;
		OutputStream outputOut = null;
		
		try {
			inputIn = new BufferedInputStream(new FileInputStream(inputFile));
			keyIn = new BufferedInputStream(new FileInputStream(keyFile));
			
			if(mode == CrypterMode.DECRYPT) {
				//read prefix
				
				//get length
				byte[] prefixData = new byte[4];
				inputIn.read(prefixData);
				
				byte[] keyData = new byte[prefixData.length];
				keyIn.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				int prefixLength = byteArrayToInt(prefixData);
				
				if(prefixLength > 64) {
					//invalid length
					//generate one and lets see what happens
					prefixLength = new Random().nextInt(61) + 4;
				}

				//get prefix data
				prefixData = new byte[prefixLength];
				inputIn.read(prefixData);
				
				keyData = new byte[prefixData.length];
				keyIn.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				
				String outputFileName = getNextPossibleFileName(new String(prefixData));
				outputFile = new File(outputFileName);
			}
			
			outputOut = new BufferedOutputStream(new FileOutputStream(outputFile));
			
			if(mode == CrypterMode.ENCRYPT) {
				//write prefix
				
				byte[] prefixData = generatePrefix();
				
				byte[] keyData = new byte[prefixData.length];
				keyIn.read(keyData);
				
				prefixData = crypt(prefixData, keyData);
				
				outputOut.write(prefixData);
			}
						
			byte[] dataBuffer = new byte[1024];
			byte[] keyBuffer = new byte[1024];
			int bytesRead = 0;
			
			while(running && (bytesRead = inputIn.read(dataBuffer)) != -1) {
				keyIn.read(keyBuffer);
				
				outputOut.write(crypt(dataBuffer, keyBuffer, bytesRead));
			}
			
			if(running) {
				for(CrypterListener l : listener) {
					l.finished();
				}
			
				running = false;
			}
			else {
				//aborted
				
				for(CrypterListener l : listener) {
					l.failed("process stopped");
				}
			}
			
		} catch (FileNotFoundException e) {
			for(CrypterListener l : listener) {
				l.failed(e.getMessage());
			}
		} catch (IOException e) {
			for(CrypterListener l : listener) {
				l.failed(e.getMessage());
			}
		}
		finally {
			try {
				if(inputIn != null)
					inputIn.close();
				
				if(keyIn != null)
					keyIn.close();
				
				if(outputOut != null)
					outputOut.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public void addListener(CrypterListener l) {
		listener.add(l);
	}
	
	public void removeListener(CrypterListener l) {
		listener.remove(l);
	}
	
	private byte[] crypt(byte[] data, byte[] key) {
		return crypt(data, key, data.length);
	}
	
	private byte[] crypt(byte[] data, byte[] key, int length) {
		byte[] cryptedData = new byte[length];
		
		for(int i = 0; i < cryptedData.length; i++) {
			cryptedData** = (byte) (data**^key**);
		}
		
		return cryptedData;
	}
	
	public boolean isRunning() {
		return running;
	}
	
	public void stop() {
		running = false;
	}
	
	private byte[] generatePrefix() {
		String fileName = inputFile.getName();
		
		ByteBuffer b = ByteBuffer.allocate(4 + fileName.getBytes().length);
		b.putInt(fileName.getBytes().length);
		b.put(fileName.getBytes());
	
		return b.array();

	}
	
	private int byteArrayToInt(byte[] b) {
	    return   b[3] & 0xFF |
	            (b[2] & 0xFF) << 8 |
	            (b[1] & 0xFF) << 16 |
	            (b[0] & 0xFF) << 24;
	}
	
	private String getNextPossibleFileName(String name) {
		int counter = 0;
		String nameToTest = name;
		
		while(new File(nameToTest).exists()) {
			counter++;
			nameToTest = counter + "_" + name;
		}
		
		return nameToTest;
	}
}

[/spoiler]

Zwecks Verbessungen lass ich mir die Anregungen aus diesem Thread weiter durch den Kopf gehen und gönn mir noch etwas Theorie :slight_smile:

die volle Stärke spielen BufferedStreams nur dann aus wenn wie am Anfang bytes einzeln gelesen werden,
wenn das aus bestimmten Gründen nicht zu vermeiden ist,
oder wenn man sich schlicht den Aufwand eigener Arrays sparen will

nun hast aber auch schon 1024er-Arrays, etwas doppelt gemoppelt, geht es damit mit normalen Streams langsam?
8192 als Array-Größe ist auch noch einen Versuch Wert, die interne Standardgröße der Buffer-Streams

[..]
public class BufferedInputStream extends FilterInputStream {

    private static int defaultBufferSize = 8192;

    /**
     * The internal buffer array where the data is stored. When necessary,
     * it may be replaced by another array of
     * a different size.
     */
    protected volatile byte buf[];
[..]

Ich hatte gestern getestet FileInputStream byte weise, BufferefStream byte weise und BufferedStream mit array, den FileInputStream mit arrays hab ich nicht getestet, hätt ich es mal gemacht. Jetzt nachgeholt und das Ergebnis ist super.

Datei 50mb, Key 52mb

FileInputStream byte weise 360sec
BufferedStream byte weise 60sec
BufferedStream array 60sec
FileInputStream array 6sec

nicht was ich vermutet hätte, eher kleiner 10% Overhead, aber bei positiven Ergebnissen soll man sich nicht beschweren :wink: