// Path: src/main/java/org/cryptomator/cryptolib/api/InvalidPassphraseException.java
// public class InvalidPassphraseException extends MasterkeyLoadingFailedException {
// 
// 	public InvalidPassphraseException() {
// 		super(null);
// 	}
// }
// 
// Path: src/main/java/org/cryptomator/cryptolib/api/Masterkey.java
// public class Masterkey extends DestroyableSecretKey {
// 
// 	private static final String KEY_ALGORITHM = "MASTERKEY";
// 	public static final String ENC_ALG = "AES";
// 	public static final String MAC_ALG = "HmacSHA256";
// 	public static final int SUBKEY_LEN_BYTES = 32;
// 
// 	public Masterkey(byte[] key) {
// 		super(checkKeyLength(key), KEY_ALGORITHM);
// 	}
// 
// 	private static byte[] checkKeyLength(byte[] key) {
// 		Preconditions.checkArgument(key.length == SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES, "Invalid raw key length %s", key.length);
// 		return key;
// 	}
// 
// 	public static Masterkey generate(SecureRandom csprng) {
// 		byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
// 		try {
// 			csprng.nextBytes(key);
// 			return new Masterkey(key);
// 		} finally {
// 			Arrays.fill(key, (byte) 0x00);
// 		}
// 	}
// 
// 	public static Masterkey from(DestroyableSecretKey encKey, DestroyableSecretKey macKey) {
// 		Preconditions.checkArgument(encKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of encKey");
// 		Preconditions.checkArgument(macKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of macKey");
// 		byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
// 		try {
// 			System.arraycopy(encKey.getEncoded(), 0, key, 0, SUBKEY_LEN_BYTES);
// 			System.arraycopy(macKey.getEncoded(), 0, key, SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES);
// 			return new Masterkey(key);
// 		} finally {
// 			Arrays.fill(key, (byte) 0x00);
// 		}
// 	}
// 
// 	@Override
// 	public Masterkey copy() {
// 		return new Masterkey(getEncoded());
// 	}
// 
// 	/**
// 	 * Get the encryption subkey.
// 	 *
// 	 * @return A new copy of the subkey used for encryption
// 	 */
// 	public DestroyableSecretKey getEncKey() {
// 		return new DestroyableSecretKey(getEncoded(), 0, SUBKEY_LEN_BYTES, ENC_ALG);
// 	}
// 
// 	/**
// 	 * Get the MAC subkey.
// 	 *
// 	 * @return A new copy of the subkey used for message authentication
// 	 */
// 	public DestroyableSecretKey getMacKey() {
// 		return new DestroyableSecretKey(getEncoded(), SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES, MAC_ALG);
// 	}
// 
// }
// 
// Path: src/main/java/org/cryptomator/cryptolib/api/MasterkeyLoadingFailedException.java
// public class MasterkeyLoadingFailedException extends CryptoException {
// 
// 	public MasterkeyLoadingFailedException(String message, Throwable cause) {
// 		super(message, cause);
// 	}
// 
// 	public MasterkeyLoadingFailedException(String message) {
// 		super(message);
// 	}
// 
// }


import com.google.common.base.Preconditions;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import javax.crypto.Mac;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.Arrays;
import static java.nio.charset.StandardCharsets.UTF_8;

	/**
	 * Reencrypts a masterkey with a new passphrase.
	 *
	 * @param masterkey     The original JSON representation of the masterkey
	 * @param oldPassphrase The old passphrase
	 * @param newPassphrase The new passphrase
	 * @return A JSON representation of the masterkey, now encrypted with <code>newPassphrase</code>
	 * @throws IOException                If failing to read, parse or write JSON
	 * @throws InvalidPassphraseException If the wrong <code>oldPassphrase</code> has been supplied for the <code>masterkey</code>
	 */
	public byte[] changePassphrase(byte[] masterkey, CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
		try (ByteArrayInputStream in = new ByteArrayInputStream(masterkey);
			 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
			changePassphrase(in, out, oldPassphrase, newPassphrase);
			return out.toByteArray();
		}
	}

	public void changePassphrase(InputStream oldIn, OutputStream newOut, CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
		try (Reader reader = new InputStreamReader(oldIn, UTF_8);
			 Writer writer = new OutputStreamWriter(newOut, UTF_8)) {
			MasterkeyFile original = MasterkeyFile.read(reader);
			MasterkeyFile updated = changePassphrase(original, oldPassphrase, newPassphrase);
			updated.write(writer);
		}
	}

	// visible for testing
	MasterkeyFile changePassphrase(MasterkeyFile masterkey, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
// Path: src/main/java/org/cryptomator/cryptolib/api/InvalidPassphraseException.java
// public class InvalidPassphraseException extends MasterkeyLoadingFailedException {
// 
// 	public InvalidPassphraseException() {
// 		super(null);
// 	}
// }
// 
// Path: src/main/java/org/cryptomator/cryptolib/api/Masterkey.java
// public class Masterkey extends DestroyableSecretKey {
// 
// 	private static final String KEY_ALGORITHM = "MASTERKEY";
// 	public static final String ENC_ALG = "AES";
// 	public static final String MAC_ALG = "HmacSHA256";
// 	public static final int SUBKEY_LEN_BYTES = 32;
// 
// 	public Masterkey(byte[] key) {
// 		super(checkKeyLength(key), KEY_ALGORITHM);
// 	}
// 
// 	private static byte[] checkKeyLength(byte[] key) {
// 		Preconditions.checkArgument(key.length == SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES, "Invalid raw key length %s", key.length);
// 		return key;
// 	}
// 
// 	public static Masterkey generate(SecureRandom csprng) {
// 		byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
// 		try {
// 			csprng.nextBytes(key);
// 			return new Masterkey(key);
// 		} finally {
// 			Arrays.fill(key, (byte) 0x00);
// 		}
// 	}
// 
// 	public static Masterkey from(DestroyableSecretKey encKey, DestroyableSecretKey macKey) {
// 		Preconditions.checkArgument(encKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of encKey");
// 		Preconditions.checkArgument(macKey.getEncoded().length == SUBKEY_LEN_BYTES, "Invalid key length of macKey");
// 		byte[] key = new byte[SUBKEY_LEN_BYTES + SUBKEY_LEN_BYTES];
// 		try {
// 			System.arraycopy(encKey.getEncoded(), 0, key, 0, SUBKEY_LEN_BYTES);
// 			System.arraycopy(macKey.getEncoded(), 0, key, SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES);
// 			return new Masterkey(key);
// 		} finally {
// 			Arrays.fill(key, (byte) 0x00);
// 		}
// 	}
// 
// 	@Override
// 	public Masterkey copy() {
// 		return new Masterkey(getEncoded());
// 	}
// 
// 	/**
// 	 * Get the encryption subkey.
// 	 *
// 	 * @return A new copy of the subkey used for encryption
// 	 */
// 	public DestroyableSecretKey getEncKey() {
// 		return new DestroyableSecretKey(getEncoded(), 0, SUBKEY_LEN_BYTES, ENC_ALG);
// 	}
// 
// 	/**
// 	 * Get the MAC subkey.
// 	 *
// 	 * @return A new copy of the subkey used for message authentication
// 	 */
// 	public DestroyableSecretKey getMacKey() {
// 		return new DestroyableSecretKey(getEncoded(), SUBKEY_LEN_BYTES, SUBKEY_LEN_BYTES, MAC_ALG);
// 	}
// 
// }
// 
// Path: src/main/java/org/cryptomator/cryptolib/api/MasterkeyLoadingFailedException.java
// public class MasterkeyLoadingFailedException extends CryptoException {
// 
// 	public MasterkeyLoadingFailedException(String message, Throwable cause) {
// 		super(message, cause);
// 	}
// 
// 	public MasterkeyLoadingFailedException(String message) {
// 		super(message);
// 	}
// 
// }


// Path: src/main/java/org/cryptomator/cryptolib/common/MasterkeyFileAccess.java
import com.google.common.base.Preconditions;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import javax.crypto.Mac;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.Arrays;
import static java.nio.charset.StandardCharsets.UTF_8;


	/**
	 * Reencrypts a masterkey with a new passphrase.
	 *
	 * @param masterkey     The original JSON representation of the masterkey
	 * @param oldPassphrase The old passphrase
	 * @param newPassphrase The new passphrase
	 * @return A JSON representation of the masterkey, now encrypted with <code>newPassphrase</code>
	 * @throws IOException                If failing to read, parse or write JSON
	 * @throws InvalidPassphraseException If the wrong <code>oldPassphrase</code> has been supplied for the <code>masterkey</code>
	 */
	public byte[] changePassphrase(byte[] masterkey, CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
		try (ByteArrayInputStream in = new ByteArrayInputStream(masterkey);
			 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
			changePassphrase(in, out, oldPassphrase, newPassphrase);
			return out.toByteArray();
		}
	}

	public void changePassphrase(InputStream oldIn, OutputStream newOut, CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
		try (Reader reader = new InputStreamReader(oldIn, UTF_8);
			 Writer writer = new OutputStreamWriter(newOut, UTF_8)) {
			MasterkeyFile original = MasterkeyFile.read(reader);
			MasterkeyFile updated = changePassphrase(original, oldPassphrase, newPassphrase);
			updated.write(writer);
		}
	}

	// visible for testing
	MasterkeyFile changePassphrase(MasterkeyFile masterkey, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
		try (Masterkey key = unlock(masterkey, oldPassphrase)) {