Commit 6885daf2 authored by Arne Schmidt's avatar Arne Schmidt
Browse files

initial commit

parents
This diff is collapsed.
package kpp.schmidta.symmetric.passwd;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.util.Arrays;
import javax.crypto.Cipher;
import kpp.schmidta.symmetric.passwd.enums.HMac;
import kpp.schmidta.symmetric.passwd.enums.PBE;
import kpp.schmidta.symmetric.passwd.enums.SymmetricCipher;
// TODO: Re-Keying
// TODO: Fehlerbehandlung
public class FileEncryption {
private static final byte[] FILE_IDENTIFIER = { 0x05, 0x39 };
// TODO: Kownplaintext-Angriff
private static final byte[] MAGIC_STRING = { 0x2A };
public void encrypt(InputStream source, String target, char[] password, FileEncryptionConfig config)
throws Exception {
// Initialisierung
PasswordBasedEncryptionEngine pbe = new PasswordBasedEncryptionEngine();
pbe.init(config.pbe);
SymmetricCipherEngine pbeCipher = new SymmetricCipherEngine(Cipher.ENCRYPT_MODE);
pbeCipher.init(config.pbeCipher, pbe.getKey(password).getEncoded());
SymmetricCipherEngine encCipher = new SymmetricCipherEngine(Cipher.ENCRYPT_MODE);
encCipher.init(config.encCipher);
HMacEngine hMac = new HMacEngine();
hMac.init(config.hMac);
// Schreiben von Headern und Daten
int hMacPos;
try (//
DataOutputStream plain = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(target)));
DataOutputStream hashed = new DataOutputStream(hMac.getOutputStream(plain))) {
// Plain-Header schreiben
hashed.write(FILE_IDENTIFIER);
pbe.write(hashed);
pbeCipher.write(hashed);
hMac.write(hashed);
hashed.flush();
// Lnge des Plain-Headers merken --> Position des HMac
hMacPos = hashed.size();
// Platzhalter fr HMac schreiben
plain.write(Utils.ffBytes(hMac.getMacLength()));
// PBE-Header in Buffer schreiben
try (ByteArrayOutputStream pbeHeader = new ByteArrayOutputStream();
DataOutputStream pbeEnc = new DataOutputStream(pbeCipher.getOutputStream(pbeHeader))) {
pbeEnc.write(MAGIC_STRING); // TODO: Knownplaintext-Angriff
hMac.writeKey(pbeEnc);
encCipher.writeWithKey(pbeEnc);
pbeEnc.flush();
pbeEnc.close();
// Lnge des PBE-Header ber den Hashed-Stream schreiben
hashed.writeInt(pbeHeader.toByteArray().length);
// PBE-Header ber den Hashed-Stream schreiben
hashed.write(pbeHeader.toByteArray());
}
try (DataOutputStream enc = new DataOutputStream(encCipher.getOutputStream(hashed))) {
enc.write(MAGIC_STRING); // TODO: Knownplaintext-Angriff
int b;
while ((b = source.read()) >= 0) {
enc.write(b);
}
enc.flush();
enc.close();
}
}
// Einfgen des HMac
try (RandomAccessFile file = new RandomAccessFile(target, "rw")) {
file.seek(hMacPos);
file.write(hMac.getOutputMac());
}
}
public void verify(String source, char[] password) throws Exception {
decrypt(source, null, password);
}
public void decrypt(String source, OutputStream target, char[] password) throws Exception {
PasswordBasedEncryptionEngine pbe = new PasswordBasedEncryptionEngine();
SymmetricCipherEngine pbeCipher = new SymmetricCipherEngine(Cipher.DECRYPT_MODE);
SymmetricCipherEngine encCipher = new SymmetricCipherEngine(Cipher.DECRYPT_MODE);
HMacEngine hMac = new HMacEngine();
byte[] hMacToVerify;
try (DataInputStream raw = new DataInputStream(new BufferedInputStream(new FileInputStream(source)));
DataInputStream hashed = new DataInputStream(hMac.getInputStream(raw))) {
// Einlesen und prfen des File-Identifiers
byte[] identifier = new byte[FILE_IDENTIFIER.length];
hashed.read(identifier);
if (!Arrays.equals(identifier, FILE_IDENTIFIER)) {
throw new IllegalArgumentException("Source-File is no Vault!");
}
// Initialisierung durch Einlesen des Plain-Headers
pbe.read(hashed);
pbeCipher.read(hashed, pbe.getKey(password).getEncoded());
hMac.read(hashed);
hMacToVerify = new byte[hMac.getMacLength()];
raw.readFully(hMacToVerify);
// Einlesen des PBE-Headers
int pbeHeaderLength = hashed.readInt();
byte[] pbeHeader = new byte[pbeHeaderLength];
hashed.readFully(pbeHeader);
// Entschlsseln des PBE-Headers
try (DataInputStream pbeEnc = new DataInputStream(
pbeCipher.getInputStream(new ByteArrayInputStream(pbeHeader)))) {
// Einlesen und prfen des Magic-Strings
byte[] magicString = new byte[MAGIC_STRING.length];
pbeEnc.readFully(magicString);
if (!Arrays.equals(magicString, MAGIC_STRING)) {
throw new IllegalArgumentException("PBE-decryption failed!");
}
// Initialisierung
hMac.readKey(pbeEnc);
encCipher.read(pbeEnc);
pbeEnc.close();
}
// Entschlsseln des Dateiinhalts
try (DataInputStream enc = new DataInputStream(encCipher.getInputStream(hashed))) {
// Einlesen und prfen des Magic-Strings
byte[] magicString = new byte[MAGIC_STRING.length];
enc.readFully(magicString);
if (!Arrays.equals(magicString, MAGIC_STRING)) {
throw new IllegalArgumentException("PBE-decryption failed!");
}
int b;
while ((b = enc.read()) >= 0) {
if (target != null) {
target.write(b);
}
}
enc.close();
}
}
if (!Arrays.equals(hMacToVerify, hMac.getInputMac())) {
throw new IllegalArgumentException("HMAC verification failed!");
}
}
public static void main(String[] args) throws Exception {
String filename = "clear.txt";
FileEncryptionConfig config = new FileEncryptionConfig(PBE.PBKDF2WithHmacSHA512,
SymmetricCipher.AES_CBC_PKCS7Padding, SymmetricCipher.AES_CBC_PKCS7Padding, HMac.HMACKECCAK256);
FileEncryption enc = new FileEncryption();
try (BufferedInputStream source = new BufferedInputStream(new FileInputStream(filename))) {
enc.encrypt(source, filename + ".enc", "AliceInWonderland".toCharArray(), config);
}
// Manipulation
// try (RandomAccessFile file = new RandomAccessFile(filename + ".enc", "rw")) {
// file.seek(0x42);
// file.writeByte((byte) 0x42);
// }
Utils.hexDump(filename + ".enc");
// Utils.printHash(filename, config.hMac, Utils.ffBytes(16));
System.out.println("------------------------------");
enc = new FileEncryption();
try (BufferedOutputStream target = new BufferedOutputStream(new FileOutputStream(filename + ".dec"))) {
enc.decrypt(filename + ".enc", target, "AliceInWonderland".toCharArray());
}
// enc = new FileEncryption();
// enc.decrypt(filename, "AliceIsLost".toCharArray());
}
}
package kpp.schmidta.symmetric.passwd;
import kpp.schmidta.symmetric.passwd.enums.HMac;
import kpp.schmidta.symmetric.passwd.enums.PBE;
import kpp.schmidta.symmetric.passwd.enums.SymmetricCipher;
public class FileEncryptionConfig {
public final PBE pbe;
public final SymmetricCipher pbeCipher;
public final SymmetricCipher encCipher;
public final HMac hMac;
public FileEncryptionConfig(PBE pbe, SymmetricCipher pbeCipher,
SymmetricCipher encCipher, HMac hMac) {
this.pbe = pbe;
this.pbeCipher = pbeCipher;
this.encCipher = encCipher;
this.hMac = hMac;
}
}
package kpp.schmidta.symmetric.passwd;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import kpp.schmidta.symmetric.passwd.enums.HMac;
public class HMacEngine {
private HMacInputStream input;
private HMacOutputStream output;
private HMac algorithm;
private Key key;
private Mac hMac;
/**
* Initialisiert die HMacEngine mit dem angegebenen Algorithmus und einem
* zuflligen Schlssel
*
* @param algorithm
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public void init(HMac algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
SecureRandom rand = new SecureRandom(); // TODO: KeyGenerator
byte[] key = new byte[16]; // TODO ???
rand.nextBytes(key);
init(algorithm, key);
// Override IV
rand.nextBytes(key);
}
/**
* Initialisiert die HMacEngine mit dem angegebenen Algorithmus und
* Schlssel
*
* @param algorithm
* @param key
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public void init(HMac algorithm, byte[] key) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
setAlgorithm(algorithm);
setKey(key);
}
/**
* Initialisiert den Mac mit dem angegebenen Algorithmus
*
* @param algorithm
* @throws NoSuchAlgorithmException
*/
private void setAlgorithm(HMac algorithm) throws NoSuchAlgorithmException {
this.algorithm = algorithm;
this.hMac = Mac.getInstance(algorithm.toString());
}
/**
* Initialisiert den Mac mit dem angegebenen Schlssel
*
* @param algorithm
* @throws NoSuchAlgorithmException
*/
private void setKey(byte[] key) throws InvalidKeyException, IOException {
this.key = new SecretKeySpec(key, algorithm.toString());
hMac.init(this.key);
// Initialisierung des InputStreams
if (input != null) {
this.input.init(this.hMac);
}
}
/**
* Schreibt die Parameter der HMacEngine auf den angegebenen
* DataOutputStream (ohne Schlssel)
*
* @param out
* @throws IOException
*/
public void write(DataOutputStream out) throws IOException {
out.write(algorithm.value);
out.flush();
}
/**
* Schreibt den Schlssel auf den angegebenen DataOutputStream
*
* @param out
* @throws IOException
*/
public void writeKey(DataOutputStream out) throws IOException {
out.writeInt(key.getEncoded().length);
out.write(key.getEncoded());
out.flush();
}
/**
* Initialisiert die HMacEngine durch einlesen der Parameter vom angegebenen
* DataInputStream Die Initialisierung wird durch Aufruf von readKey(...)
* abgeschlossen.
*
* @param in
* @throws IOException
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
*/
public void read(DataInputStream in) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
HMac algorithm = HMac.fromByte(in.readByte());
this.setAlgorithm(algorithm);
}
/**
* Schliet die Initialisierung der HMacEngine durch einlesen des Schlssels
* vom angegebenen DataInputStream ab. Darf erst im Anschluss an read(...)
* aufgerufen werden.
*
* @param in
* @throws IOException
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
*/
public void readKey(DataInputStream in) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
int length = in.readInt();
byte[] key = new byte[length];
in.readFully(key);
this.setKey(key);
}
/**
* Erzeugt einen neuen InputStream ber dessen Daten ein HMac gebildet wird.
*
* @param in
* @return
*/
public InputStream getInputStream(InputStream in) {
if (this.input == null) {
this.input = new HMacInputStream(in);
return this.input;
} else {
throw new RuntimeException("InputStream already exists");
}
}
/**
* Erzeug einen neuen OutputStream ber dessen Daten ein HMac gebildet wird.
*
* @param out
* @return
*/
public OutputStream getOutputStream(OutputStream out) {
if (this.output == null) {
this.output = new HMacOutputStream(this.hMac, out);
return this.output;
} else {
throw new RuntimeException("OutputStream already exists");
}
}
/**
* Schliet ggf. die Berechnung des HMac ber den InputStream ab und liefert
* ihn zurck.
*
* @return
*/
public byte[] getInputMac() {
return this.input.getMac();
}
/**
* Schliet ggf. die Berechnung des HMac ber den OutputStream ab und
* liefert ihn zurck.
*
* @return
*/
public byte[] getOutputMac() {
return this.output.getMac();
}
public int getMacLength() {
return hMac.getMacLength();
}
/**
* Reicht den Aufruf an den angegebenen InputStream durch und aktuallisiert
* den Mac mit den gelesenen Daten. Bis zur Initialisierung werden die
* gelesenen Daten gepuffert.
*/
class HMacInputStream extends InputStream {
private InputStream in;
private Mac hMac;
private byte[] mac;
private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
HMacInputStream(InputStream in) {
this.in = in;
}
private void init(Mac hMac) throws IOException {
this.hMac = hMac;
this.buffer.flush();
this.buffer.close();
this.hMac.update(buffer.toByteArray()); // Mac mit den gepufferten
// Daten initialisieren
this.buffer = null;
}
@Override
public int read() throws IOException {
int b = this.in.read(); // Aufruf an den angegebenen InputStream
// weiterreichen
if (this.hMac == null) { // Wenn noch nicht initialisiert
this.buffer.write(b); // Ergebnis puffern
} else if (b != -1) { // sonst Mac aktuallisieren
this.hMac.update((byte) b);
}
// EOF (-1) wird nicht an Mac weitergereicht
return b; // Ergebnis an den Aufrufer zurckliefern
}
@Override
public void close() throws IOException {
// close() abfangen
}
/**
* Schliet ggf. die Berechnung des HMac ber den InputStream ab und
* liefert ihn zurck.
*
* @return
*/
private byte[] getMac() {
if (mac == null) {
this.mac = this.hMac.doFinal(); // Darf nur ein Mal aufgerufen
// werden
}
return this.mac;
}
}
/**
* Reicht den Aufruf an den angegebenen OutputStream durch und aktuallisiert
* den Mac mit den geschreibenen Daten.
*/
class HMacOutputStream extends OutputStream {
private OutputStream output;
private Mac hMac;
private byte[] mac;
HMacOutputStream(Mac hMac, OutputStream out) {
this.hMac = hMac;
this.output = out;
}
@Override
public void write(int b) throws IOException {
this.hMac.update((byte) b);
this.output.write(b);
}
@Override
public void close() throws IOException {
// close() abfangen
}
private byte[] getMac() {
if (mac == null) {
this.mac = this.hMac.doFinal(); // Darf nur ein Mal aufgerufen
// werden
}
return this.mac;
}
}
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
System.out.println(">>> Testing HMacEngine");
for (HMac hmac : HMac.values()) {
System.out.println("> Testing " + hmac);
HMacEngine engine = new HMacEngine();
engine.init(hmac);
try (BufferedOutputStream raw = new BufferedOutputStream(new FileOutputStream("tmp"));
DataOutputStream out = new DataOutputStream(engine.getOutputStream(raw))) {
engine.write(out);
engine.writeKey(out);
out.writeUTF(Utils.LOREM);
out.flush();
out.close();
raw.write(engine.getOutputMac());
raw.flush();
raw.close();
}
// Manipulation
// try (RandomAccessFile file = new RandomAccessFile("tmp", "rw")) {
// file.seek(0x42);
// file.writeByte((byte) 0x42);
// }
engine = new HMacEngine();
try (DataInputStream raw = new DataInputStream(new BufferedInputStream(new FileInputStream("tmp")));
DataInputStream in = new DataInputStream(engine.getInputStream(raw))) {
engine.read(in);
engine.readKey(in);
in.readUTF();
byte[] hMacToVerify = new byte[engine.getMacLength()];
in.close();
raw.readFully(hMacToVerify);
if (Arrays.equals(hMacToVerify, engine.getInputMac())) {
System.out.println("Mac Verification successful [" + Utils.toHex(engine.getInputMac()) + "]");
} else {
System.err.println(">> Mac Verification failed <<");
System.err.println("read from File: " + Utils.toHex(hMacToVerify));
System.err.println("calculated: " + Utils.toHex(engine.getInputMac()));
}
}
}
}
}
package kpp.schmidta.symmetric.passwd;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import kpp.schmidta.symmetric.passwd.enums.PBE;
import kpp.schmidta.symmetric.passwd.enums.SymmetricCipher;
public class PasswordBasedEncryptionEngine {
private PBE algorithm;
private byte[] salt;
private int iterations;
private int keyLength;
public void init(PBE algorithm) {
init(algorithm, 16);