public class CipherStuff {
	
	private static CipherStuff cipherStuff = new CipherStuff();
	
	private static BlockCipher cipherAlgo;
	
	private static AuthenticatedEncryption authenticatedEncryption = new EAXMode();
	
	private static String errorMessage = null;
	
	private static SessionKeyCrypt skc = null;
	
	private static boolean bound = true;
	
	// returns required length of key in bytes
	public final static int getKeySize() {
		
		int keySize = 0;
		
		if (cipherAlgo == null) {
			System.err.println("CipherStuff getKeySize: cipherAlgo null");
			System.exit(1);
		}
		
		if (cipherAlgo.getAlgorithmName().startsWith("Threefish") ) {
			// key size = block size for Threefish 256/512/1024
			keySize = cipherAlgo.getBlockSize();
		} else if ( cipherAlgo.getAlgorithmName().equals("Twofish" )) {
			keySize = 32; // Bytes
		} else if (cipherAlgo.getAlgorithmName().equals("Serpent") ) {
			//System.out.println("SerpentEngine");
			keySize = 32; // Bytes			
		} else if (cipherAlgo.getAlgorithmName().equals("AES") ) {
			keySize = 32; // Bytes		
		} else if (cipherAlgo.getAlgorithmName().equals("AESFast") ) {
			keySize = 32; // Bytes	
		} else if (cipherAlgo.getAlgorithmName().equals("Shacal2") ) {
			keySize = 64; // Bytes
		} else {
			System.err.println("CipherStuff getKeySize: invalid Algorithm");
			System.exit(1);
		}
		return keySize;
	}
	
	/**
	 * Encrypt/decrypt an array of bytes. This function is only used to 
	 * encrypt the session key
	 * 
	 * @param forEncryption - true for encryption, false for decryption
	 * @param input			- plain text or cipher text
	 * @param key			- the cryptographic key for the cipher
	 * @param zeroize		- fills the key with 0 for true (when the key is no longer used)
	 * @return				- plain text or cipher text
	 */
	public final static byte[] processCTR(boolean forEncryption, byte[] input, byte[] key,
			boolean zeroize) {
		//Help.printBytes("key",  input);
		KeyParameter kp = new KeyParameter(key);
		if (zeroize == true) {
			Zeroizer.zero(key);
		}
		
		byte[] iv = null;
		
		if (forEncryption == false) {
			// input is ciphertext, IV is stored on top of ciphertext
			// get IV:
			iv = new byte[CipherStuff.getCipherAlgo().getBlockSize()];
			System.arraycopy(input,  0,  iv,  0,  iv.length);
			// truncate ciphertext:
			byte[] tmp = new byte[input.length - iv.length];
			System.arraycopy(input,  iv.length, tmp,  0,  tmp.length);
			input = tmp;
		} else {
			// input is plaintext
			iv = new RandomStuff().createRandomBytes(CipherStuff.getCipherAlgo().getBlockSize());
		}
		ParametersWithIV ivp = new ParametersWithIV(kp, iv);

		BufferedBlockCipher b = new BufferedBlockCipher(new SICBlockCipher(cipherAlgo));

        b.init(true, ivp);
        byte[] out = new byte[input.length];

        int len = b.processBytes(input, 0, input.length, out, 0);

        try {
			len += b.doFinal(out, len);
		} catch (DataLengthException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidCipherTextException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        Zeroizer.zero(kp.getKey());

		if (forEncryption == false) {// decryption: output plaintext
			return out;
		} else { // encryption: output (IV || ciphertext)
			Zeroizer.zero(input);
			// store IV on top of ciphertext
			byte[] tmp = new byte[out.length + iv.length];
			System.arraycopy(iv,  0,  tmp,  0,  iv.length);
			System.arraycopy(out,  0,  tmp,  iv.length,  out.length);
			return tmp;
		}		
	}

	
	/*
	 * Encryption if plainBytes can be loaded in memory in one block
	 */
	/**
	 * Encrypt an array of bytes. 
	 * 
	 * @param plainBytes			the plain text to encrypt
	 * @param keyMaterial			the derived key or null (there
	 * 								is a session key available)
	 * @param encryptBySessionKey	if true, the derived key is encrypted, 
	 * 								otherwise the key is cleared
	 * @return						the cipher text
	 */
	public final byte[] encrypt(byte[] plainBytes, byte[] keyMaterial, 
			boolean encryptBySessionKey){
		
		checkSessionKeyCrypt();
		
		// Check if plainBytes is too long to be loaded in memory:
		if (plainBytes.length > 8192 * 64 * 16) { // fileBlockSize = 8192 * 64 * 16
			
			// check free memory to avoid swapping:
			System.gc(); // this might not work...
			long freeMemory = Runtime.getRuntime().maxMemory() 
					- (Runtime.getRuntime().totalMemory()
							- Runtime.getRuntime().freeMemory());
			//System.out.println("Free memory: " + freeMemory);
			if (plainBytes.length <= freeMemory) {			
				System.out.println("Warning: long plain text");
			} else {
				JOptionPane.showMessageDialog(null,
					    "The content you want to encrypt is too large \n"
					    + "to encrypt in one block on your system: \n"
					    + plainBytes.length + "\n\n"
					    + "Use the file encrytion pea instead. \n"
					    + "(If you blew up an existing text based pea, \n"
					    + "you have to save the content by copy-and-paste).",
					    "Memory error",
					    JOptionPane.ERROR_MESSAGE);
				System.exit(1);
			}			
		}
		
		// unique for each encryption
		byte[] iv = Attachments.getNonce();
		if (iv == null) {
			// for every file: new unique and random IV
			iv = Attachments.generateNonce( );
			Attachments.setNonce(iv);
		}
		byte[] key = detectKey(keyMaterial);
		//
		//------ENCRYPTION: 
		//				
		// add pswIdentifier to check password in decryption after check of Mac
		plainBytes =  Attachments.addPswIdentifier(plainBytes);

		byte[] cipherBytes = authenticatedEncryption.processBytes(true, plainBytes, key, iv);	
		Zeroizer.zero(plainBytes);				
		// prepare to save:
		cipherBytes = Attachments.addNonce(cipherBytes, iv);	
		if (CipherStuff.isBound() == false) {
			cipherBytes = Attachments.attachBytes(cipherBytes, KeyDerivation.getAttachedSalt());
		}
		cipherBytes = Attachments.addFileIdentifier(cipherBytes);	
		//
		// handle the keys
		//
		handleKey(key, encryptBySessionKey);

//	Help.printBytes("cipherBytes", cipherBytes);
		return cipherBytes;
	}
	

	
	// changes the encryptedKey for new keyMaterial
	// used by changing password
	/**
	 * Encrypt the derived key and use as session key
	 * 
	 * @param keyMaterial	the derived key
	 */
	public final void encryptKeyFromKeyMaterial(byte[] keyMaterial) {
		
		checkSessionKeyCrypt();
		
		if (keyMaterial == null) {
			System.err.println("Error: new keyMaterial null (CryptStuff.encryptKeyFromKeyMaterial");
			System.exit(1);
		}		
		byte[] key = detectKey(keyMaterial);
		handleKey(key, true);
	}

	
	
	//
	// returns null for incorrect password or input
	//
	/**
	 * Decrypt an array of bytes. 
	 * 
	 * @param cipherText			the cipher text to decrypt
	 * @param keyMaterial			the derived key or null if 
	 * 								there is a session key
	 * @param encryptBySessionKey	encrypt the key or clear it
	 * @return						the plain text or null if the
	 * 								decryption failed
	 */
	public final byte[] decrypt(byte[] cipherText, byte[] keyMaterial, 		
			boolean encryptBySessionKey) {
		
		checkSessionKeyCrypt();
		//Help.printBytes("ciphertext + Nonce + fileId",  cipherText);
		// check
		if (cipherText == null) {
			errorMessage = "no cipher text";
			return null;
		}
		cipherText = Attachments.checkFileIdentifier(cipherText, true);
		if (cipherText == null) { // check returns null if failed
			errorMessage = "unsuitable cipher text (identifier)";
			return null;
		}
		if (CipherStuff.isBound() == false){ // cut salt
			cipherText = Attachments.cutSalt(cipherText);
		}

		byte[] iv = Attachments.calculateNonce(cipherText);
		if (iv == null) {
			errorMessage = "unsuitable cipher text (length)";
			return null;
		} else {
			cipherText = Attachments.cutNonce(cipherText);
		}		

		// Check if plainBytes is too long to be loaded in memory:
		if (cipherText.length > 8192 * 64 * 16) { // fileBlockSize = 8192 * 64 * 16
			
			// get free memory to avoid swapping:
			System.gc(); // this might not work...
			long freeMemory = Runtime.getRuntime().maxMemory() 
					- (Runtime.getRuntime().totalMemory()
							- Runtime.getRuntime().freeMemory());
			//System.out.println("Free memory: " + freeMemory);
			if (cipherText.length <= freeMemory) {			
				System.out.println("Warning: long plain text");
				
				if (cipherText.length > (freeMemory - 8192 * 64)){
					JOptionPane.showMessageDialog(null,
						    "The content you want to decrypt is already large: \n"
						    + cipherText.length + "\n\n"
						    + "Adding more content may cause a memory error. \n",
						    "Memory warning",
						    JOptionPane.WARNING_MESSAGE);					
				}
			} else {
				JOptionPane.showMessageDialog(null,
					    "The content you want to decrypt is too large \n"
					    + "to decrypt in one block on your system: \n"
					    + cipherText.length + "\n\n"
					    + "It was probably encrypted on a system with more memory. \n"
					    + "You have to decrypt it on another system... \n",
					    "Memory error",
					    JOptionPane.ERROR_MESSAGE);
				System.exit(1);
			}			
		}
		
		byte[] key = detectKey(keyMaterial);

		int macLength = CipherStuff.getCipherAlgo().getBlockSize();		
		// check mac length:
		if (cipherText.length < macLength) {
			errorMessage = "unsuitable cipher text (Mac length)";
			return null;			
		}

		byte[] plainText = authenticatedEncryption.processBytes(false, cipherText, key, iv);
		if (plainText == null) { // wrong password
			errorMessage = "authentication failed for password";
			return null;
		}
		byte[] rescueText = null;
		if (PswDialogBase.getWorkingMode().equals("-r")) { // rescue mode: make copy of plainText
			rescueText = new byte[plainText.length];
			System.arraycopy(plainText, 0, rescueText, 0, plainText.length);
		}
		plainText = Attachments.checkAndCutPswIdentifier(plainText); // truncates pswIdentifier

		if (plainText == null) {
			
			System.out.println("Password identifier failed.");
			
			if (PswDialogBase.getWorkingMode().equals("-r")) { // rescue mode
				 
				 Object[] options = {"Continue",
	                    "Break"};
				 int n = JOptionPane.showOptionDialog(null,
						 "Password identifier failed:  \n"
								 + "The password identifier checks if the beginning and the end \n"
								 + "of the content.\n "
								 + "An error means that wether the beginning or the end is not correct.\n"
								 + "Normally that means, that the password is wrong, \n"
								 + "but there is the possibility that a file is damaged. \n"
								 + "In this case, some parts of the file might be restored \n"
								 + "by the decryption. \n"
								 + "If you are sure, the password is correct, continue.\n"
								 + "Warning: For incorrect password files may be irretrievably lost. ",
						"Password Identification Error",
						JOptionPane.YES_NO_OPTION,
						JOptionPane.ERROR_MESSAGE,
						null,
						options,
						options[1]);
			 
				 if (n == JOptionPane.YES_OPTION) {
					// do nothing
					 System.out.println("continue");
					 plainText = rescueText;
				 } else {
					 System.out.println("break");
					return null;
				 }		 
			 } else {// not recue mode
					errorMessage = "password failed (identifier)";
					return null;
			 }				
		} 

		//
		// handle the keys
		//		
		handleKey(key, encryptBySessionKey);
		return plainText;
	}

	/**
	 * Get the key from keyMaterial or the session key
	 * 
	 * @param keyMaterial	the derived key or null
	 * @return				the key
	 */
	protected final byte[] detectKey(byte[] keyMaterial) {
		
		byte[] key = null;
		if (keyMaterial != null) { // use as key 
			
			// check size:
			int keySize = CipherStuff.getKeySize();
			if (keyMaterial.length != keySize) {
				System.err.println("CryptStuff detectKey: invalid size of keyMaterial");
				System.exit(1);
			} else {
				key = keyMaterial;
			}
			
		} else { // keyMaterial == null: decrypt encryptedKey to get key
			if (skc == null) {
				skc = new SessionKeyCrypt();
			}
			key = skc.getKey();
		}
		return key;
	}
	/**
	 * Encrypt or clear the key
	 * 
	 * @param key				the key
	 * @param encryptSessionKey	if true, encrypt the key, if
	 * 							false clear it
	 */
	protected final void handleKey(byte[] key, boolean encryptSessionKey) {		

		if (encryptSessionKey == false) {
			Zeroizer.zero(key);
			
		} else {		
			if (skc == null) {
				skc = new SessionKeyCrypt();
			}
			skc.storeKey(key);
		}
	}
	private final void checkSessionKeyCrypt(){
		if (skc == null) {
			skc = new SessionKeyCrypt();
		}
	}

	
	//=========================
	// Getter & Setter:
	public final static void setCipherAlgo (BlockCipher _cipherAlgo) {
		cipherAlgo = _cipherAlgo;
	}
	public final static BlockCipher getCipherAlgo () {
		return cipherAlgo;
	}	
	
	public final static void setCipherMode (AuthenticatedEncryption _cipherMode) {
		authenticatedEncryption = _cipherMode;
	}
	public final static AuthenticatedEncryption getCipherMode(){
		return authenticatedEncryption;
	}
	
	public final SessionKeyCrypt getSessionKeyCrypt(){
		return skc;
	}
	// used in PswDialogFile for initialization
	public final static void setSessionKeyCrypt(SessionKeyCrypt _skc){ 
		skc = _skc;
	}
	public final static String getErrorMessage() {
		return errorMessage;
	}
	public final static void setErrorMessage(String _errorMessage) {
		errorMessage = _errorMessage;
	}

	/**
	 * @return the cipherStuff
	 */
	public final static CipherStuff getInstance() {
		if(cipherStuff == null){
			cipherStuff = new CipherStuff();
		}
		return cipherStuff;
	}

	/**
	 * @return the bound
	 */
	public static boolean isBound() {
		return bound;
	}

	/**
	 * @param bound the bound to set
	 */
	public static void setBound(boolean bound) {
		CipherStuff.bound = bound;
	}
} public class HashStuff {
		
	private static Digest hashAlgo;
	
	/**
	 * Perform a hash function and clear the input immediately
	 * 
	 * @param input		the input to hash
	 * @return			the output of the hash function
	 */
	public static final byte[] hashAndOverwrite(byte[] input) {

        hashAlgo.update(input, 0, input.length);
        
        byte[] digest = new byte[hashAlgo.getDigestSize()];

        hashAlgo.doFinal(digest, 0);

        Zeroizer.zero(input);

        return digest;
	}

	/**
	 * Perform a hash function
	 * 
	 * @param input		the input to hash
	 * @return			the output of the hash function
	 */
	public static final byte[] hash(byte[] input) {
		
        hashAlgo.update(input, 0, input.length);

        byte[] digest = new byte[hashAlgo.getDigestSize()];

        hashAlgo.doFinal(digest, 0);

        return digest;
	}
	
	//=================
	// Getter & Setter:
	/**
	 * Get the used hash function
	 * 
	 * @return	the hash function
	 */
	public static final Digest getHashAlgo() {
		return hashAlgo;
	}
	/**
	 * Set the hash function
	 * 
	 * @param _hashAlgo	the hash function to be used
	 */
	public static final void setHashAlgo(Digest _hashAlgo) {
		hashAlgo = _hashAlgo;
	}	
} public abstract class KeyDerivation {
	
	private static KeyDerivation kdf = null;
	
	private static int tCost = 0;
	private static int mCost = 0;
	private static int arg3 = 0;
	private static int arg4 = 0;
	private static int arg5 = 0;
	private static int arg6 = 0;
	
	private final static int SALT_SIZE = 32;// at most 129 byte for fixed initialized salt
	private static byte[] salt;
	private static byte[] attachedSalt;
	

	private static byte[] extraValues = "peafactory_extra".getBytes(); // 16 byte
	
	private static String versionString = "";
	
	/**
	 * Derive the key. All parameters are set in child classes
	 * 
	 * @param pswMaterial	material build from the password
	 * 
	 * @return				the derived key
	 */
	public abstract byte[] deriveKey( byte[] pswMaterial);
	
	/**
	 * Get the name of the key derivation scheme
	 * 
	 * @return the name of the key derivation scheme
	 */
	public abstract String getName();

	/**
	 * Get a key of required length (expand or extract)
	 * 
	 * @param keyMaterial	the material from key derivation
	 * @return				the key
	 */
	public final static byte[] adjustKeyMaterial(byte[] keyMaterial){

		if (keyMaterial == null) {
			System.err.println("KDFScheme: keyMaterial null");
			System.exit(2);
		}	
		int requiredSize = CipherStuff.getKeySize();

		byte[] result = null;
	
		if (keyMaterial.length >= requiredSize) {
			result = new byte[requiredSize];
			// there is enough entropy in keyMaterial: just cut
			System.arraycopy(keyMaterial, 0, result, 0, result.length);
		} else {
			// stretch keyMaterial: HMAC-based Expand Key Derivation Function
			result = expandKey(keyMaterial, requiredSize); 
		}
		return result;
	}
	
	private final static byte[] expandKey( byte[] keyMaterial, int requiredSize) {
		byte[] result = new byte[requiredSize];
		System.arraycopy(keyMaterial, 0, result, 0, keyMaterial.length);
		int resultPosition = keyMaterial.length;
		short counter = 0;
		byte[] tmp = new byte[HashStuff.getHashAlgo().getDigestSize() + salt.length + 2];
		
		while (resultPosition + 1 < requiredSize) {
			
			// prepare hash input:
			System.arraycopy(keyMaterial, 0, tmp, 0, 
					(keyMaterial.length < tmp.length)? keyMaterial.length : tmp.length);
			System.arraycopy(salt, 0, tmp, tmp.length - (salt.length + 2), salt.length);
			tmp[tmp.length - 2] = (byte) (counter >> 8);
			tmp[tmp.length -1] = (byte) counter;
			
			keyMaterial = HashStuff.hashAndOverwrite(tmp);
			
			for (int i = 0; i < keyMaterial.length && (i + resultPosition) < result.length; i++) {
				result[resultPosition + i] = keyMaterial[i];
			}		

			resultPosition += keyMaterial.length;
			counter++;
		}
		//Help.printBytes("expandedKey in KDFScheme", result);
		return result;	
	}
	/**
	 * Print informations about the used key derivation function
	 * 
	 * @param print 	if true, informations are printed
	 */
	public final static void printInfos(boolean print) {
		if (print == true) {
			System.out.print("Key derivation: " +  kdf.getName() );
			
			if (! versionString.equals("")){
				System.out.print(", " + versionString);
			}
			System.out.print(", time parameter: " + tCost 
					+ ", memory parameter: " + mCost
					);
			if (arg3 != 0) {
				System.out.print(", parameter 3: " + arg3);
			}
			if (arg4 != 0) {
				System.out.print(", parameter 4: " + arg4);
			}
			if (arg5 != 0) {
				System.out.print(", parameter 5: " + arg5);
			}
			if (arg6 != 0) {
				System.out.print(", parameter 6: " + arg6);
			}
			System.out.println("");
		}		
	}
	/**
	 * @param attachedSalt the attachedSalt to set
	 */
	public static void setAttachedAndUpdateSalt(byte[] _attachedSalt) {
		
		if (_attachedSalt.length != SALT_SIZE){
			System.err.println("KeyDerivation: Wrong size of attached salt" );
			System.exit(1);
		}
		KeyDerivation.attachedSalt = _attachedSalt;
		// xor default programRandomBytes with attached salt
		for (int i = 0; i < SALT_SIZE; i++) {
			salt[i] ^= attachedSalt[i];
		}
	}

	//=============================================
	// Getter & Setter  
	/**
	 * @return the tCost
	 */
	public static int gettCost() {
		return tCost;
	}

	/**
	 * @param tCost the tCost to set
	 */
	public static void settCost(int _tCost) {
		KeyDerivation.tCost = _tCost;
	}

	/**
	 * @return the mCost
	 */
	public static int getmCost() {
		return mCost;
	}

	/**
	 * @param mCost the mCost to set
	 */
	public static void setmCost(int _mCost) {
		KeyDerivation.mCost = _mCost;
	}

	/**
	 * @return the arg3
	 */
	public static int getArg3() {
		return arg3;
	}

	/**
	 * @param arg3 the arg3 to set
	 */
	public static void setArg3(int _arg3) {
		KeyDerivation.arg3 = _arg3;
	}

	/**
	 * @return the arg4
	 */
	public static int getArg4() {
		return arg4;
	}

	/**
	 * @param arg4 the arg4 to set
	 */
	public static void setArg4(int _arg4) {
		KeyDerivation.arg4 = _arg4;
	}

	/**
	 * @return the arg5
	 */
	public static int getArg5() {
		return arg5;
	}

	/**
	 * @param arg5 the arg5 to set
	 */
	public static void setArg5(int _arg5) {
		KeyDerivation.arg5 = _arg5;
	}

	/**
	 * @return the arg6
	 */
	public static int getArg6() {
		return arg6;
	}

	/**
	 * @param arg6 the arg6 to set
	 */
	public static void setArg6(int _arg6) {
		KeyDerivation.arg6 = _arg6;
	}

	/**
	 * @return the salt
	 */
	public static byte[] getSalt() {
		return salt;
	}

	/**
	 * @param salt the salt to set
	 */
	public static void setSalt(byte[] _salt) {
		salt = new byte[SALT_SIZE];
		System.arraycopy(_salt,  0,  KeyDerivation.salt,  0,  SALT_SIZE);
	}

	/**
	 * @return the extraValues
	 */
	public static byte[] getExtraValues() {
		return extraValues;
	}

	/**
	 * @param extraValues the extraValues to set
	 */
	public static void setExtraValues(byte[] _extraValues) {
		KeyDerivation.extraValues = _extraValues;
	}

	/**
	 * @return the kdf
	 */
	public static KeyDerivation getKdf() {
		return kdf;
	}

	/**
	 * @param kdf the kdf to set
	 */
	public static void setKdf(KeyDerivation kdf) {
		KeyDerivation.kdf = kdf;
	}

	/**
	 * @return the versionString
	 */
	public static String getVersionString() {
		return versionString;
	}

	/**
	 * @param versionString the versionString to set
	 */
	public static void setVersionString(String versionString) {
		KeyDerivation.versionString = versionString;
	}
	
	public static int getSaltSize(){
		return SALT_SIZE;
	}

	/**
	 * @return the attachedSalt
	 */
	public static byte[] getAttachedSalt() {
		return attachedSalt;
	}
} public class RandomStuff {
	
	
	private final static int pswLength = 50;
	
	
	// 90 Printable 7-Bit ASCII (without "  \ 0)
	private final static char[] allChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!#$%&()*+,-./:;<=>?@[]^_'{|}~".toCharArray();
	private static byte[] personalBytes = null;
	
	private static int entropyPoolCounter = 0;
	private static final int POOL_SIZE = EntropyPool.getPoolSize();
	
	private final static int PERSONAL_BYTES_SIZE = 16;
	private final static int RESEED_BYTES_SIZE = 12;
	
	/**
	 * Create random values
	 * 
	 * @param length	number of bytes (size of output)
	 * @return			the random values
	 */
	public final byte[] createRandomBytes(int length) {				
		
		byte[] result = new byte[length];// return value
		
		byte[] seed = new byte[52];
		int seedIndex = 0;
		
		// seed of SecureRandom alone might not be appropriate for keys > 20 byte because it uses state of SHA-1
		byte[] scSeed = new byte[20];
		SecureRandom sc = new SecureRandom(); // get system random bytes if available
		sc.nextBytes(scSeed);
		for (int i = 0; i < scSeed.length; i++) {
			seed[i] = scSeed[i];
			scSeed[i] = (byte) 0;
		}
		seedIndex += scSeed.length;
		//Help.printBytes("seed scSeed", seed);

		
		// get values from RandomCollector: 		
		byte[] tableSeed = getAndUpdateTableValue( 2 );
		System.arraycopy(tableSeed,  0,  seed,  seedIndex, tableSeed.length);
		Zeroizer.zero(tableSeed);
		seedIndex += tableSeed.length;


		// add personal bytes: 16 byte
		if (personalBytes == null) {
			personalBytes = generatePersonalBytes();// 16 byte
		}
		System.arraycopy(personalBytes,  0,  seed,  seedIndex, PERSONAL_BYTES_SIZE);
		//Help.printBytes("seed personalBytes", seed);


		// hash seed
		byte[] data = HashStuff.hashAndOverwrite(seed);
		Zeroizer.zero(seed);
		
		// truncate if output size of hash >= required length
		if (data.length >= length) {
			
			System.arraycopy(data,  0,  result,  0,  length);
	
		} else {
		
			// expand data to result.length:
			int dataIndex = 0; // index of hash value
			int resultIndex = 0; // index of result
			while (resultIndex < length) {
				
				while ( dataIndex < data.length && resultIndex < result.length){ 
					result[resultIndex++] = data[dataIndex++];
				}
				//Help.printBytes("result",  result);
				if (resultIndex < result.length){
					// create new data: 
					byte[] newSeed = new byte[data.length 
					                          + RESEED_BYTES_SIZE 
					                          + PERSONAL_BYTES_SIZE];// reseed with extraSeed + personalBytes
					int newSeedIndex = 0;
					
					System.arraycopy(data,  0,  newSeed,  0,  data.length);// 64 byte
					Zeroizer.zero(data);
					newSeedIndex += data.length;
					// reseed:
					byte[] newExtraSeed = generateExtraSeed(); // 12 byte
					System.arraycopy(newExtraSeed, 0, newSeed, newSeedIndex, RESEED_BYTES_SIZE);
					Zeroizer.zero(newExtraSeed);
					newSeedIndex += newExtraSeed.length;
					// add personal bytes:
					System.arraycopy(personalBytes,  0,  newSeed, newSeedIndex, PERSONAL_BYTES_SIZE);
					
					data = HashStuff.hashAndOverwrite(newSeed);
					dataIndex = 0;				
				}
			}
		}
		Zeroizer.zero(seed);
		Zeroizer.zero(data);
		return result;
	}
	
	//
	// two independent random functions: first mix allChars pseudo-randomly 
	// then choose chars pseudo-randomly
	//
	/**
	 * Create an array of characters randomly
	 * 
	 * @return	randomly chosen array of characters
	 */
	public final char[] createNewRandomChars() {
		
		char[] newRandomChars = new char[pswLength];// return value
		
		int[] mixPositions = getMixPositions (allChars.length / 2, 100);
		mixChars(mixPositions);		
		Zeroizer.zero(mixPositions);

		SecureRandom sRandom = new SecureRandom();

		for(int i = 0; i < pswLength; i++) {
			newRandomChars[i] = allChars[sRandom.nextInt(allChars.length)];
		}				
		return newRandomChars;
	}
	
	//
	// get array of values > 0 and < maxValue to mix allChars
	// mixNumber = times to swap values / 2
	//
	private final int[] getMixPositions (int maxValue, int mixNumber) {
		int[] result = new int[mixNumber];
		
		byte[] hash = createRandomBytes(mixNumber * 2);// this may not be enough		
		
		// get values from hash and fill result, compute new hashes if necessary
		int resultPosition = 0;
		int inputPosition = 0;
		while(resultPosition < mixNumber) {
			if ( (Math.abs(hash[inputPosition])) < maxValue) {
				result[resultPosition] = Math.abs(hash[inputPosition] );
				hash[inputPosition] = (byte) 0xFF;
				resultPosition++;
				inputPosition++;
			} else { 
				// throw away: 
				inputPosition++;
			}
			if (inputPosition >= hash.length) {
				// new hash
				hash = HashStuff.hashAndOverwrite(hash);
				inputPosition = 0;
			}
		}
		Zeroizer.zero(hash);
		return result;
	}
	
	//
	// mix allChars with positions > 0 and < allChars/2
	private final void mixChars(int[] mixPositions) {
		
		char swap;
		int half = allChars.length/2;
		for (int i = 0; i < mixPositions.length; i+=2) {
			swap = allChars[ mixPositions[i] ];
			allChars[ mixPositions[i] ] = allChars[ mixPositions[i+1] + half];
			allChars[ mixPositions[i+1] + half] = swap;
		}
		Zeroizer.zero(mixPositions);
	}
	
	
	private final byte[] generateExtraSeed() {
		byte[] seed = new byte[RESEED_BYTES_SIZE];
		int index = 0;
			
		// nanoTime: 8 bytes (four of them are mostly null) -> take 4 bytes
		long nanoSeed =  System.nanoTime();
		ByteBuffer nanoBuffer = ByteBuffer.allocate(8);
		byte[] nanoBytes = nanoBuffer.putLong(nanoSeed).array();
		System.arraycopy(nanoBytes,  2, seed ,index, 4);
		Zeroizer.zero(nanoBytes);
		index += 4; 
		//Help.printBytes("seed nano", seed);
		
		if (index >= RESEED_BYTES_SIZE){
			return seed;
		}
		    
		// currentTime: 4 bytes (two of them may be null, two are mostly the same) -> take 4 bytes
		long timeSeed =  System.currentTimeMillis(); 
		ByteBuffer buffer = ByteBuffer.allocate(8);
		byte[] timeBytes = buffer.putLong(timeSeed).array();
		System.arraycopy(timeBytes,  4, seed, index, 4);
		Zeroizer.zero(timeBytes);
		index += 4;
		//Main.printBytes("seed nano + time", seed);
		
		if (index >= RESEED_BYTES_SIZE){
			return seed;
		}
		    
		// free memory: 4 bytes (bad values)
		long freeMemory = Runtime.getRuntime().freeMemory();
		seed[index++] = (byte) freeMemory;
		seed[index++] = (byte) (freeMemory >>> 8);
		seed[index++] = (byte) (freeMemory >>> 16);
		seed[index++] = (byte) (freeMemory >>> 32);
		freeMemory = 0;
		//Main.printBytes("seedForStir time + pid + loc + mem", seed);	
		
		if (index >= RESEED_BYTES_SIZE){
			return seed;
		}
		
		// tmp files
/*		File f = new File(System.getProperties().getProperty("java.io.tmpdir"));
		File[] tempList =  f.listFiles();
		// number of temp files: 2 bytes
		seed[index++] = (byte) tempList.length;
		seed[index++] = (byte) (tempList.length >>> 8);
		// choose two files "light randomly" and get size: 4 bytes
		Random rand = new Random();
		int randomInt = rand.nextInt(tempList.length - 1);
		long fileSize = tempList[randomInt].length();
		seed[index++] = (byte) fileSize;
		seed[index++] = (byte) (fileSize >>> 8);
		randomInt = rand.nextInt(tempList.length - 1);
		fileSize = tempList[randomInt].length();
		seed[index++] = (byte) fileSize;
		seed[index++] = (byte) (fileSize >>> 8);
		randomInt = 0;*/

		 //Help.printBytes("seed", seed);		
		 return seed;
	}
	

	
	private final byte[] generatePersonalBytes() {
		
		byte[] personalBytes = new byte[PERSONAL_BYTES_SIZE];
		int index = 0;

		// ip address of local host: 4 byte
		byte[] ipBytes = new byte[4];
		try {
			InetAddress ia = InetAddress.getLocalHost();
			ipBytes = ia.getAddress();
			if (ipBytes != null) {
				System.arraycopy(ipBytes,  0,  personalBytes, index,  ipBytes.length);
				index += ipBytes.length;
			} else {
				System.err.println("RandomStuff: personal bytes for random seed: ip adress null");
			}
		} catch (UnknownHostException e) {
			System.err.println("RandomStuff: " + e.toString() + ": personal bytes for random seed: ip adress failed");
		} catch (NullPointerException e) {
			System.err.println("RandomStuff: " + e.toString() + ": personal bytes for random seed: ip adress failed");
		}
		
		// fill rest with system properties
		byte[] systemProps = ( System.getProperty("user.name") + System.getProperty("user.dir")
				+ System.getProperty("os.version")  ).getBytes();//+ System.getProperty("sun.boot.class.path")	
		for (int i = 0; i < systemProps.length && index < PERSONAL_BYTES_SIZE; i++) {
			personalBytes[index++] = systemProps[i];
		}			
		return personalBytes;
	}
	
	// get values of RandomCollectors table and overwrite these values
	// with new values from hash
	private final byte[] getAndUpdateTableValue(int number){
		EntropyPool rc = EntropyPool.getInstance();
		
		byte[] result = new byte[number * 8];
		byte[] tableSeed = null;
		for (int i = 0; i < number; i++) {

			// get 8 bytes from one value of the table
			tableSeed = Converter.long2bytesBE(rc.getValue(true));
			entropyPoolCounter++;
			//System.out.print(" " + entropyPoolCounter);
			if (entropyPoolCounter >= POOL_SIZE){
				reseedEntropyPool(rc);
			}
			// store in result:
			System.arraycopy(tableSeed,  0, result, i * 8, tableSeed.length);
		}
		return result;
	}
	// The pool is always reseeded if entropyPoolCounter >= POOL_SIZE
	// so every value is used at most once
	private final void reseedEntropyPool(EntropyPool ep){
		
		//System.out.println("\nreseed, counter: " + entropyPoolCounter);
		
		byte[] updateBytes = new byte[POOL_SIZE * 8];
		int hashLen = HashStuff.getHashAlgo().getDigestSize();
		// get old value from pool to hash
		byte[] reseedBytes = Converter.longs2bytesBE(ep.getValuesToReseed());// 32 bytes			
		// hash values
		byte[] hashedBytes = HashStuff.hash(reseedBytes);
		if (hashLen >= updateBytes.length) {
			System.arraycopy(hashedBytes,  0,  updateBytes,  0,  updateBytes.length);
		} else {
			
			int index = 0;
			while (index < updateBytes.length) {				
				hashedBytes = HashStuff.hash(hashedBytes);
				System.arraycopy(hashedBytes,  0,  updateBytes,  index,  
						(index + hashLen > updateBytes.length) ? (updateBytes.length - index) : hashedBytes.length);
				index += hashLen;
			}
		}

		// convert to long[]
		long[] reseedValues = Converter.bytes2longsLE(updateBytes);
		// reseed the pool
		ep.reseedPool(reseedValues);
		Zeroizer.zero(updateBytes);
		Zeroizer.zero(reseedBytes);
		Zeroizer.zero(hashedBytes);
		Zeroizer.zero(reseedValues);
		entropyPoolCounter = 0;
	}
} public class CatenaKDF extends KeyDerivation {
	
	// Default setting:
	private static String versionID = "Dragonfly-Full";
	private static int lambdaDragonfly = 2;
	private static int garlicDragonfly = 18;
	
	private static int lambdaButterfly = 4;
	private static int garlicButterfly = 14;

	public CatenaKDF() {
		if ((versionID.equals("Dragonfly-Full")) || (versionID.equals("Dragonfly"))){
			settCost(lambdaDragonfly);
			setmCost(garlicDragonfly);	    	
			setArg3(0);
			setArg4(0);
			setArg5(0);
			setArg6(0);
			setVersionString(versionID);
			setKdf(this);
		} else if ((versionID.equals("Butterfly-Full")) || (versionID.equals("Butterfly"))){
			settCost(lambdaButterfly);
			setmCost(garlicButterfly);	    	
			setArg3(0);
			setArg4(0);
			setArg5(0);
			setArg6(0);
			setVersionString(versionID);
			setKdf(this);
		} else {
			System.err.println("Invalid Catena version ID");
		}
		setKdf(this);
	}

	@Override
	public byte[] deriveKey(byte[] pswMaterial) {
		
		Catena cat = null;
		if (getVersionString().equals("Dragonfly-Full")){
			cat = new CatenaBRG(false, true); // not fast, wipe password
			cat.setFast(false);
		} else if (getVersionString().equals("Butterfly-Full")){
			cat = new CatenaDBG(false, true);
			cat.setFast(false);
		} else if (getVersionString().equals("Butterfly")){
			cat = new CatenaDBG(true, true);
			cat.setFast(true);
			cat.setReducedDigest(new Catena_Blake2b_1());
		} else if (getVersionString().equals("Dragonfly")){
			cat = new CatenaBRG(true, true);
			cat.setFast(true);
			cat.setReducedDigest(new Catena_Blake2b_1());
		} else {
			throw new IllegalArgumentException("invalid version ID");
		}
		byte[] key = new byte[CipherStuff.getKeySize()];
		//System.out.println("butterfly " + lambdaButterfly + "  " + gettCost());
		cat.deriveKey(
				pswMaterial, getSalt(), getExtraValues(),  
		       gettCost(), getmCost(), getmCost(), 
		       0, key); // key id = 0
		printInfos(true);
		return key;
	}

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "Catena";
	}

	/**
	 * @return the versionID
	 */
	public static String getVersionID() {
		return versionID;
	}

	/**
	 * @param versionID the versionID to set
	 */
	public static void setVersionID(String versionID) {
		CatenaKDF.versionID = versionID;
	}

	/**
	 * @return the lambdaDragonfly
	 */
	public static int getDragonflyLambda() {
		return lambdaDragonfly;
	}

	/**
	 * @param lambdaDragonfly the lambdaDragonfly to set
	 */
	public static void setLambdaDragonfly(int lambda) {
		CatenaKDF.lambdaDragonfly = lambda;
	}

	/**
	 * @return the garlicDragonfly
	 */
	public static int getGarlicDragonfly() {
		return garlicDragonfly;
	}

	/**
	 * @param garlicDragonfly the garlicDragonfly to set
	 */
	public static void setGarlicDragonfly(int garlic) {
		CatenaKDF.garlicDragonfly = garlic;
	}

	/**
	 * @return the lambdaButterfly
	 */
	public static int getLambdaButterfly() {
		return lambdaButterfly;
	}

	/**
	 * @param lambdaButterfly the lambdaButterfly to set
	 */
	public static void setLambdaButterfly(int lambdaButterfly) {
		CatenaKDF.lambdaButterfly = lambdaButterfly;
	}

	/**
	 * @return the garlicButterfly
	 */
	public static int getGarlicButterfly() {
		return garlicButterfly;
	}

	/**
	 * @param garlicButterfly the garlicButterfly to set
	 */
	public static void setGarlicButterfly(int garlicButterfly) {
		CatenaKDF.garlicButterfly = garlicButterfly;
	}
} @SuppressWarnings("serial")
public final class MainView extends JFrame implements ActionListener {

	private static MainView frame; 
	
	// file to encrypt: 
	// if null, this indicates that resource is saved beside jar archive in same directory
	private static String openedFileName = null;

	// screen: 
	Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
	double screenWidth = screenSize.getWidth();
	double screenHeight = screenSize.getHeight();
	
	private JLabel projectLabel; // Notebook, Image...
	
	private static FileTypePanel filePanel;
	
	// MainView is several times disposed and new created, so
	// password fields must be static
	private static JPasswordField pswField;
	private static JPasswordField pswField2;
	
	private JLabel qualityLabel;
	private JLabel qualityLabelStars;

	private JTextField jarNameField;
	private JTextField labelTextField;	

	protected JLabel pathLabel;
	public static JProgressBar progressBar;
	
	private JCheckBox scriptCheck;	
	private static boolean unixScript = false;
	
	private static boolean blankPea = false;
	
	protected static String language; 
	protected static ResourceBundle languageBundle;
	
	private static Image image = new ImageIcon("resources" + File.separator + "pea-lock.png").getImage();

	private MainView () {
		
		frame = this;
		this.setTitle("Pea Factory");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		language = PeaFactory.getLanguage();
		languageBundle = PeaFactory.getLanguagesBundle();
				
		DataType dataType = DataType.getCurrentType();

		//Image icon = new ImageIcon("resources" + File.separator + "pea-lock.png").getImage();
	    this.setIconImage(image);

		JPanel contentPane = (JPanel)this.getContentPane();
		contentPane.setBorder(new EmptyBorder(5, 15, 5, 15));
		// use frame to collect random values: 
		contentPane.addMouseMotionListener(new MouseRandomCollector() );
		JScrollPane frameScrollPane = new JScrollPane(contentPane);
		this.setContentPane(frameScrollPane);
		
		BoxLayout boxLayout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
		contentPane.setLayout(boxLayout);
		
		JPanel menuPanel = new JPanel();
		menuPanel.setLayout(new BoxLayout(menuPanel, BoxLayout.X_AXIS));		
		// Menu:
		Menu menu = new Menu(languageBundle);
		JLabel languageLabel = new JLabel(language + "  ");
		LanguageMenu languageMenu = new LanguageMenu();
		languageMenu.setToolTipText(languageBundle.getString("tooltip_language"));

		menuPanel.add(menu);		
		menuPanel.add(Box.createHorizontalGlue() );
		menuPanel.add(languageLabel);
		menuPanel.add(languageMenu);

		contentPane.add(menuPanel);
		

		projectLabel = new JLabel();
		projectLabel.setText(dataType.getDescription());
		projectLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 14));
		projectLabel.setBorder(new LineBorder(Color.GRAY, 1));
		contentPane.add(Box.createVerticalStrut(10));
		contentPane.add(projectLabel);
		contentPane.add(Box.createVerticalStrut(10));
		
		if (blankPea == false) {		
			
			if (dataType instanceof NotesType || // text
					dataType instanceof EditorType ){	
				
				TextTypePanel textTypePanel = TextTypePanel.getInstance(languageBundle);
				contentPane.add(textTypePanel);			

			} else if (dataType instanceof ImageType) {	
				
				ImageTypePanel imageTypePanel = ImageTypePanel.getInstance(languageBundle);
				contentPane.add(imageTypePanel);
			
			} else if (dataType instanceof FileType) {	
							
				filePanel = new FileTypePanel(300, 120, true);
		
				contentPane.add(Box.createVerticalStrut(20));
				contentPane.add(filePanel);				
			}
			
			JPanel pathPanel = new JPanel();
			pathPanel.setLayout(new FlowLayout(FlowLayout.LEADING));
			pathLabel = new JLabel();

			if (openedFileName != null) {
				pathLabel.setText(openedFileName);
				pathLabel.setBorder(new LineBorder(Color.BLACK,1));
				pathPanel.add(pathLabel);
			}
			contentPane.add(pathPanel);

			
			JPanel pswPanel = new JPanel();		
			pswPanel.setLayout(new BoxLayout(pswPanel, BoxLayout.Y_AXIS));

			JPanel pswLabelPanel = new JPanel();
			pswLabelPanel.setLayout(new BoxLayout(pswLabelPanel, BoxLayout.X_AXIS));
			JLabel pswLabel = new JLabel(languageBundle.getString("psw_label"));
			pswLabelPanel.add(pswLabel);
			
			pswLabelPanel.add(Box.createHorizontalStrut(20));		
			
			qualityLabel = new JLabel();
			qualityLabel.setFont(new Font(Font.MONOSPACED, Font.BOLD, 18));
			pswLabelPanel.add(qualityLabel);
			
			qualityLabelStars = new JLabel();
			qualityLabelStars.setOpaque(true);
			qualityLabelStars.setBackground(Color.WHITE);
			qualityLabelStars.setFont(new Font(Font.MONOSPACED, Font.BOLD, 18));
			pswLabelPanel.add(qualityLabelStars);
			
			pswLabelPanel.add(Box.createHorizontalGlue());
			
			JButton charTableButton = new JButton(languageBundle.getString("char_table"));
			charTableButton.addActionListener(this);
			charTableButton.setActionCommand("charTable1");
			pswLabelPanel.add(charTableButton);
			pswPanel.add(pswLabelPanel);

			pswField = new JPasswordField(30);
			pswField.setToolTipText(languageBundle.getString("tooltip_psw"));
			// collect random values from key events:
			pswField.addKeyListener(new KeyRandomCollector() );
			pswField.addKeyListener(new KeyListener() {

				@Override
				public void keyPressed(KeyEvent arg0) {}
				@Override
				public void keyReleased(KeyEvent arg0) {}
				@Override
				public void keyTyped(KeyEvent arg0) {
					updatePasswordCheck();				
				}			
			});
			pswPanel.add(pswField);
			
			pswPanel.add(Box.createVerticalStrut(10));
			
			JPanel pswLabelPanel2 = new JPanel();
			pswLabelPanel2.setLayout(new BoxLayout(pswLabelPanel2, BoxLayout.X_AXIS));
			JLabel pswLabel2 = new JLabel(languageBundle.getString("psw_label_2"));
			pswLabel2.setPreferredSize(new Dimension(300,30));
			pswLabelPanel2.add(pswLabel2);
			pswLabelPanel2.add(Box.createHorizontalGlue());
			JButton charTableButton2 = new JButton(languageBundle.getString("char_table"));
			charTableButton2.addActionListener(this);
			charTableButton2.setActionCommand("charTable2");
			pswLabelPanel2.add(charTableButton2);
			pswPanel.add(pswLabelPanel2);


			pswField2 = new JPasswordField(30);
			pswField2.setToolTipText(languageBundle.getString("tooltip_psw2"));
			// collect random values from key events:
			pswField2.addKeyListener(new KeyRandomCollector() );
			pswPanel.add(pswField2);
			
			contentPane.add(Box.createVerticalStrut(10));
			
			contentPane.add(pswPanel);
		
		} // end if blankPea == false

		
		JPanel jarNamePanel = new JPanel();		
		jarNamePanel.setLayout(new BoxLayout(jarNamePanel, BoxLayout.Y_AXIS));
		
		JLabel jarNameLabel = new JLabel(languageBundle.getString("jar_name_label"));
		jarNameLabel.setPreferredSize(new Dimension(300,30));
		jarNamePanel.add(jarNameLabel);
		
		jarNameField = new JTextField(30);
		jarNameField.setToolTipText(languageBundle.getString("tooltip_jarname"));
		// collect random values from key events:
		jarNameField.addKeyListener(new KeyRandomCollector() );

		jarNamePanel.add(jarNameField);
		contentPane.add(Box.createVerticalStrut(10));
		contentPane.add(jarNamePanel);
	
		
		JPanel labelTextPanel = new JPanel();		
		labelTextPanel.setLayout(new BoxLayout(labelTextPanel, BoxLayout.Y_AXIS));		
		JLabel abovePswfieldLabel = new JLabel(languageBundle.getString("above_pswfield_label"));
		abovePswfieldLabel.setPreferredSize(new Dimension(300,30));
		labelTextPanel.add(abovePswfieldLabel);		
		labelTextField = new JTextField(30);
		labelTextField.setToolTipText(languageBundle.getString("tooltip_text_above"));
		// collect random values from key events:
		labelTextField.addKeyListener(new KeyRandomCollector() );
		labelTextPanel.add(labelTextField);
		contentPane.add(Box.createVerticalStrut(10));
		contentPane.add(labelTextPanel);

		
		JPanel newProjectPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
		JButton newProjectButton = new JButton(languageBundle.getString("new_project"));
		newProjectButton.setActionCommand("newProject");
		newProjectButton.addActionListener(this);
		newProjectPanel.add(newProjectButton);
		contentPane.add(Box.createVerticalStrut(10));
		contentPane.add(newProjectPanel);

		
		JPanel createFilePanel = new JPanel();
		createFilePanel.setLayout(new BoxLayout(createFilePanel, BoxLayout.X_AXIS));
		
		progressBar = new JProgressBar(0,100);
		progressBar.setValue(0);
		progressBar.setStringPainted(false);
		progressBar.setPreferredSize(new Dimension(200, 30));
		progressBar.setBorderPainted(false);// not visible if no task
		createFilePanel.add(progressBar);
		createFilePanel.add(Box.createHorizontalStrut(50));		
/*		
		blankCheck = new JCheckBox(languageBundle.getString("blank_check"), false);
		blankCheck.setToolTipText(languageBundle.getString("tooltip_blank_check"));
		blankCheck.setSelected(false);
		blankCheck.addActionListener(this);
		blankCheck.setActionCommand("blankCheck");
		contentPane.add(blankCheck);
		
		boundCheck = new JCheckBox(languageBundle.getString("bound_check"), false);
		boundCheck.setToolTipText(languageBundle.getString("tooltip_bound_check"));
		boundCheck.setSelected(true);
		boundCheck.addActionListener(this);
		boundCheck.setActionCommand("boundCheck");
		contentPane.add(boundCheck);
*/		
		scriptCheck = new JCheckBox(languageBundle.getString("script_check"), false);
		scriptCheck.setToolTipText(languageBundle.getString("tooltip_script_check"));
		contentPane.add(scriptCheck);
		
		JButton createFileButton = new JButton(languageBundle.getString("create_jarfile_button"));
		createFileButton.setActionCommand("create");
		createFileButton.addActionListener(this);
		createFileButton.setToolTipText(languageBundle.getString("tooltip_create"));
		createFilePanel.add(createFileButton);
		contentPane.add(Box.createVerticalStrut(10));
		contentPane.add(createFilePanel);

		setLocation(100, 10);		
	
		int prefHeight = (int) this.getLayout().preferredLayoutSize(contentPane).getHeight();
		int prefWidth = (int) this.getLayout().preferredLayoutSize(contentPane).getWidth();

		// size of frame depends on screen size: 
		if ( (prefHeight + 120) > screenHeight) { // width + 50, because of scrollpanes' width
			prefHeight = (int) screenHeight - 130;
		}
		if ( (prefWidth + 130) > screenWidth) {
			prefWidth = (int) screenWidth - 150;
		}
		// set JScrollPane size for frame:
		frameScrollPane.setPreferredSize(new Dimension(prefWidth, prefHeight + 50));
	}
	
	public final static MainView getInstance() {
		if (frame == null) {
			return new MainView();
		} else {
			System.err.println("MainView: existing instance.");
			return null;
		}
	}



	@Override
	public void actionPerformed(ActionEvent ape) {
		String com = ape.getActionCommand();
		
		if (com.equals("newProject")) {
			
			ProjectSelection proj = new ProjectSelection();
			proj.setLocation( (int)this.getLocation().getX() + 100, (int)this.getLocation().getY() + 60 );
			proj.setVisible(true); 

			
		} else if ( com.startsWith("charTable")) {	
			if(com.equals("charTable1")){
				CharTable table = new CharTable(this, pswField);
				table.setVisible(true);
			} else {
				CharTable table = new CharTable(this, pswField2);
				table.setVisible(true);				
			}
						
		} else if ( com.equals("create")) {	
		
			EntropyPool.getInstance().stopCollection();
			
			if (scriptCheck.isSelected() ) {
				unixScript = true;
			} else {
				unixScript = false;
			}

			if (jarNameField.getText() == null) {
				JarStuff.setJarFileName("default.jar");				
			} else if (jarNameField.getText().length() > 0) {
				JarStuff.setJarFileName(jarNameField.getText() + ".jar");
			} else {
				JarStuff.setJarFileName("default.jar");
			}
			
			JarStuff.setLabelText(labelTextField.getText() );
			
			if (DataType.getCurrentType() instanceof ImageType
					&& ImageType.getImageName() == null && blankPea == false) {
				JOptionPane.showMessageDialog(frame,
						languageBundle.getString("open_image_button"),//"No image selected.",
					    "Error",
					    JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			if (DataType.getCurrentType() instanceof FileType) {
				
				if (filePanel.getSelectedFileNames().length == 1) { // only one valid file selected
					
					// ask for default file: -> openedFileName
					int n = JOptionPane.showConfirmDialog(this,
							languageBundle.getString("ask_default_file"),			    			
			    			"Warning", 
			    			JOptionPane.YES_NO_CANCEL_OPTION,
			    			JOptionPane.QUESTION_MESSAGE);
					if (n == JOptionPane.YES_OPTION) { // ok			
						MainView.setOpenedFileName(filePanel.getSelectedFileNames()[0]);
					} else if (n == JOptionPane.NO_OPTION) {
						MainView.setOpenedFileName(null);
					} else { // cancel, close
						return;
					}
				} else if (filePanel.getSelectedFileNames() == null || 
						filePanel.getSelectedFileNames().length == 0){
					
					if (CipherStuff.isBound() == true) {
						JOptionPane.showMessageDialog(frame,
								"No file selected.",
							    "Error",
							    JOptionPane.ERROR_MESSAGE);
						return;
					}
				} else{
					MainView.setOpenedFileName(null);
				}
			}			
			
			progressBar.setBorderPainted(true);
			progressBar.setStringPainted(true);

			PeaFactory.createFile();
					
			if (frame != null) {
				frame.dispose(); 
				frame = null;
			}				

			ProjectSelection proj = new ProjectSelection();
			proj.setLocation( (int)this.getLocation().getX() + 100, (int)this.getLocation().getY() + 60 );
			proj.setVisible(true); 			
		}	
	}
	
	private final void updatePasswordCheck() {
		char[] pwd = pswField.getPassword();

		int quality = PasswordQualityCheck.checkQuality(pwd);//pswField.getPassword() );
		if (pwd != null){
			Zeroizer.zero(pwd);
		}
		pwd = null;
		char[] q = new char[quality];
		Arrays.fill(q,  '*');
		
		if (quality < 10) {
			qualityLabel.setText("weak ");
			qualityLabelStars.setText(new String(q)); 
			qualityLabelStars.setForeground(Color.RED);
		} else if (quality < 15) {
			qualityLabel.setText("medium ");
			qualityLabelStars.setText(new String(q)); 
			qualityLabelStars.setForeground(Color.YELLOW);
		} else {
			qualityLabel.setText("strong ");
			qualityLabelStars.setText(new String(q)); 
			qualityLabelStars.setForeground(Color.GREEN);
		}
	}
	
	public final static void updateFrame() {
		
		if (frame != null) {
			frame.dispose(); 
			frame = null;
		}			
		frame = MainView.getInstance();
		frame.pack();
		frame.setVisible(true);
	}
	
	
	//==========================================
	// Getter & Setter

	public static char[] getPsw() {
		return pswField.getPassword();
	}
	public static char[] getPsw2() {
		return pswField2.getPassword();
	}
	// Re-"Setter":
	public static void resetPsw() {
		pswField.setText("");
	}
	public static void resetPsw2() {
		pswField2.setText("");
	}
	public final static String getOpenedFileName() {
		return openedFileName;
	}
	public final static void setOpenedFileName(String _openedFileName) {
		openedFileName = _openedFileName;
	}
	public final static Point getFrameLocation() {
		return frame.getLocation();
	}
	public final static FileTypePanel getFilePanel() {
		return filePanel;
	}
	public final static boolean isDisplayed() {// used in ProjectSelection
		if (frame == null) {
			return false; 
		} else if ( frame.isVisible() == false ) {
			return false;
		} else {
			return true;
		}
	}
	public final static Image getImage() {
		return image;
	}

	public static boolean getUnixScript() {
		return unixScript;
	}

	/**
	 * @return the blankPea
	 */
	public static boolean isBlankPea() {
		return blankPea;
	}

	/**
	 * @param blankPea the blankPea to set
	 */
	public static void setBlankPea(boolean blankPea) {
		MainView.blankPea = blankPea;
	}
} @SuppressWarnings("serial")
public class ProjectSelection extends JDialog 
		implements ActionListener, ItemListener {
	
	protected static ProjectSelection projectSelection;
	
	private JRadioButton notesButton;
	private JCheckBox simpleNotesCheck;


	protected ResourceBundle languageBundle;
	
	public ProjectSelection() {
		
		projectSelection = this;

		// i18n
		languageBundle = PeaFactory.getLanguagesBundle();

		this.setAlwaysOnTop(false);
		this.setTitle(languageBundle.getString("project_selection_title"));
		// to close program if this closed after start
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		this.addWindowListener( new WindowAdapter(){
			public void windowClosing(WindowEvent we) {
				// close program if ManView is not instantiated or not visible (program start)
				// else: close this dialog
				if (PeaFactory.isFrameInstantiated() == false  || MainView.isDisplayed() == false) {
					//System.out.println("Program terminates");
					System.exit(0);
				} else {
					projectSelection.dispose();
				}	
		}});
		
		// use frame to collect random values: 
		this.addMouseMotionListener(new MouseRandomCollector());

		this.setIconImage(MainView.getImage());
		
		JPanel contentPane = (JPanel) this.getContentPane();
		contentPane.setBorder(new EmptyBorder(10,10,10,10));
		
	    //--------------//
	    // COMPONENTS:  //
	    //--------------//		
		
		JLabel typeLabel = new JLabel();
		typeLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16));
		typeLabel.setText(languageBundle.getString("selection_type_label"));
		
		BufferedImage image = null;
		 try {
			//image = ImageIO.read(new File("src" + File.separator + "resources" + File.separator + "pea-factory.png"));
			image = ImageIO.read(new File("resources" + File.separator + "peafactory.png"));

		} catch (IOException e) {
			System.err.println("ProjectSelection: image read failed");
		}	 
		Image scaledImage = image.getScaledInstance(256, 128, Image.SCALE_FAST);//92, 92, Image.SCALE_FAST);
		JLabel imageLabel = new JLabel(new ImageIcon(scaledImage));
	    
		JRadioButton fileButton = new JRadioButton();
		fileButton.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
		fileButton.setText(languageBundle.getString("selection_file_button"));
		fileButton.setToolTipText(languageBundle.getString("tooltip_selection_file_button"));
	    fileButton.setMnemonic(KeyEvent.VK_F);
	    fileButton.setMargin(new Insets(10,10,10,10));//oben, links, unten, rechts
	    fileButton.setActionCommand("file");
	    fileButton.addItemListener(this);

		notesButton = new JRadioButton();
		notesButton.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
		notesButton.setText(languageBundle.getString("selection_notes_button"));
		notesButton.setToolTipText(languageBundle.getString("tooltip_selection_notes_button"));
	    notesButton.setMnemonic(KeyEvent.VK_N);
	    notesButton.setMargin(new Insets(10,10,0,10));//oben, links, unten, rechts
	    notesButton.setActionCommand("notes");
	    notesButton.addItemListener(this);
	    
	    simpleNotesCheck = new JCheckBox();	
	    simpleNotesCheck.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
	    simpleNotesCheck.setText(languageBundle.getString("selection_simple_notes"));
	    simpleNotesCheck.setToolTipText(languageBundle.getString("tooltip_selection_simple_check"));
	    simpleNotesCheck.setMnemonic(KeyEvent.VK_E);    
	    simpleNotesCheck.addItemListener(this);
	    simpleNotesCheck.setActionCommand("simple_notes");	    
	    
	    JRadioButton imageButton = new JRadioButton();
		imageButton.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
		imageButton.setText(languageBundle.getString("selection_image_button"));
		imageButton.setToolTipText(languageBundle.getString("tooltip_selection_image_button"));
	    imageButton.setMnemonic(KeyEvent.VK_I);
	    imageButton.setMargin(new Insets(10,10,20,10));//oben, links, unten, rechts
	    imageButton.setActionCommand("image");
	    imageButton.addItemListener(this);	    	    
	    
	    ButtonGroup typeGroup = new ButtonGroup();
	    typeGroup.add(notesButton);
	    typeGroup.add(imageButton);
	    typeGroup.add(fileButton);	    
	    
	    fileButton.setSelected(true);//NullPointer if before pswChangeCheck is initialized
	    
	    JButton okButton = new JButton();
	    okButton.setText("ok");
	    okButton.addActionListener(this);
	    okButton.setActionCommand("ok");
	    okButton.setToolTipText(languageBundle.getString("tooltip_project_ok_button"));
	    	    
	    //-----------//
	    // LAYOUT:   //
	    //-----------//
	    
	    contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
	    
	    JPanel typeLabelPanel = new JPanel();
	    typeLabelPanel.setLayout(new BoxLayout(typeLabelPanel, BoxLayout.X_AXIS));
	    typeLabelPanel.add(typeLabel);
	    typeLabelPanel.add(Box.createHorizontalGlue());
	    contentPane.add(typeLabelPanel);
	    contentPane.add(Box.createVerticalStrut(10));
	   
		JPanel imagePanel = new JPanel();
		imagePanel.setLayout(new BoxLayout(imagePanel, BoxLayout.X_AXIS));
		imagePanel.add(Box.createHorizontalGlue());
		imagePanel.add(imageLabel);
		imagePanel.add(Box.createHorizontalStrut(10));
		contentPane.add(imagePanel);
//		contentPane.add(Box.createVerticalStrut(10));
	    
	    JPanel fileButtonPanel = new JPanel();
	    fileButtonPanel.setLayout(new BoxLayout(fileButtonPanel, BoxLayout.X_AXIS));
	    fileButtonPanel.add(fileButton);
	    fileButtonPanel.add(Box.createHorizontalGlue());
	    contentPane.add(fileButtonPanel);	    
	    contentPane.add(Box.createVerticalStrut(20));	    
	    
	    JPanel textButtonPanel = new JPanel();
	    textButtonPanel.setLayout(new BoxLayout(textButtonPanel, BoxLayout.X_AXIS));
	    textButtonPanel.add(notesButton);
	    textButtonPanel.add(Box.createHorizontalGlue());	    
	    contentPane.add(textButtonPanel);
	    
	    JPanel editorPanel = new JPanel();
	    editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
	    editorPanel.add(Box.createHorizontalStrut(50));
	    editorPanel.add(simpleNotesCheck);
	    editorPanel.add(Box.createHorizontalGlue());
	    contentPane.add(editorPanel);		    
	    contentPane.add(Box.createVerticalStrut(20));	    	    
	    
	    JPanel imageButtonPanel = new JPanel();
	    imageButtonPanel.setLayout(new BoxLayout(imageButtonPanel, BoxLayout.X_AXIS));
	    imageButtonPanel.add(imageButton);
	    imageButtonPanel.add(Box.createHorizontalGlue());
	    contentPane.add(imageButtonPanel);	    
	    
	    JPanel okButtonPanel = new JPanel();
	    okButtonPanel.setLayout(new BoxLayout(okButtonPanel, BoxLayout.X_AXIS));
	    okButtonPanel.add(Box.createHorizontalGlue());
	    okButtonPanel.add(okButton);
	    contentPane.add(okButtonPanel);
	    	    
		pack();
	}

	@Override
	public void actionPerformed(ActionEvent ape) {
		
		String com = ape.getActionCommand();

		if (com.equals("ok")) {
			
			// reset openedFileName:
			MainView.setOpenedFileName(null);			
			// reset data in DataType from previous setting
			DataType.getCurrentType().setData(null);
			
			MainView.updateFrame();			
			this.dispose();
		} else {
			//
		}
	}

	//
	// logic of selection and deselection
	//
	@Override
	public void itemStateChanged(ItemEvent ie) {
		AbstractButton aButton = (AbstractButton)ie.getSource();
        int state = ie.getStateChange();
        String com = aButton.getActionCommand();
        
        if (com.equals("file")) {
           	if (state == ItemEvent.SELECTED) {				
				DataType.setCurrentType( new FileType() );
        		simpleNotesCheck.setSelected(false);
        	} else { // ItemEvent.DESELECTED
        		//
        	}
        } else if (com.equals("notes")) {
        	if (state == ItemEvent.SELECTED) {
				DataType.setCurrentType( new EditorType() );
        	} else { // ItemEvent.DESELECTED
        	}
        } else if (com.equals("simple_notes")) {
        	if (state == ItemEvent.SELECTED) {
        		notesButton.setSelected(true);
        		simpleNotesCheck.setSelected(true);
				DataType.setCurrentType( new NotesType() );
        	} else { // ItemEvent.DESELECTED
        	}
        } else if (com.equals("image")) {
        	if (state == ItemEvent.SELECTED) {
				DataType.setCurrentType( new ImageType() );
        		simpleNotesCheck.setSelected(false);
        		//DataType.getCurrentType().setData(null);
        		ImageType.setImageName(null);
        	} else { // ItemEvent.DESELECTED
        		//
        	}
        }
        DataType.getCurrentType().setData(null);
	}
} public abstract class DataType {
	
	private static DataType dataType = new FileType();
		
	
	public abstract String getDescription();
	public abstract String getTypeName();
	
	public abstract void processData(byte[] keyMaterial);
	public abstract byte[] getData();
	public abstract void setData(byte[] data);
	

	
	// delete all files and directories in this directory
	protected static final void clearDirectory(File dir) {
	    for (File file: dir.listFiles()) {
	        if (file.isDirectory()) clearDirectory(file);
	        file.delete();
	    }
	}
	
	//====================================
	// Getter & Setter
	public final static DataType getCurrentType() { // used in Main
		return dataType;
	}
	public final static void setCurrentType(DataType _dataType) { // used in ProjectSelection
		dataType = _dataType;
	}
} public class JarStuff {	

	private static String jarFileName;// set in MainView, get in FileModifier
	private static String labelText;// set in MainView, get in FileModifier

	private static final String fileToCompile1 = "settings" + File.separator + "PeaSettings.java";// file to be modified: 
	private static final String fileToCompile2 = "start" + File.separator + "Start.java";// file to be modified: 


	public final void generateJarFile (byte[] keyMaterial) {
		
		DataType dataType = DataType.getCurrentType();		
		
		//=======================
		// generate java files:
		FileModifier.generateStart();
		FileModifier.generatePeaSettings();

		MainView.progressBar.setValue(45);
		MainView.progressBar.paint(MainView.progressBar.getGraphics());
		
		//=====================================
		// compile with new variables
		//			
		// java files to compile
		String javaFileNames[] = {fileToCompile1, fileToCompile2};	
		FileCompiler compiler = new FileCompiler();
		compiler.compile(javaFileNames);
		MainView.progressBar.setValue(60);
		MainView.progressBar.paint(MainView.progressBar.getGraphics());		
		//
		// clear secret values in java file:
		//
		//FileModifier.generatePeaSettings();
		
		//=======================================
        // Create jar archive with compiled files
        //
		// put in directory peas if openedFileName == null
		if ( MainView.isBlankPea() == true ||
				(MainView.getOpenedFileName() == null 
				&& !(dataType instanceof FileType ) ) ) {
			if (dataType instanceof ImageType && dataType.getData() != null){
					//&& MainView.isBlankPea() == false){ // internal image
				//
				System.out.println("internal image");
				dataType.processData(keyMaterial);
				dataType.setData( "     ".getBytes() );// overwrite later
			} else {
				String dirName = JarStuff.getJarFileName().substring(0, JarStuff.getJarFileName().length() - 4); // extract ".jar"
				dirName = "peas" + File.separator + dirName;
				File dir = new File(dirName);// directory containing jar archive and resources
				if (dir.exists()){
					System.out.println("Warning: directory exists");
					// delete all files:
					DataType.clearDirectory(dir);
				} else {
					if ( !dir.mkdir() ) { // try to create
						System.err.println("JarStuff: Can not create directory: " + dirName);
						return;
					}
				}
			}
		}		
		CreateJarFile.createJarFile();// includes text.lock in jar if changeable false
		
		dataType.processData(keyMaterial);
		Zeroizer.zero(keyMaterial);		
		
		// overwrite random values:
		FileModifier.generatePeaSettings();
		compiler.compile(javaFileNames);

		MainView.progressBar.setValue(100);
		MainView.progressBar.paint(MainView.progressBar.getGraphics());
	}
	
	
	public final static String getJarFileName() {
		return jarFileName;
	}
	public final static void setJarFileName(String _jarFileName) {
		jarFileName = _jarFileName;
	}
	public final static String getLabelText() {
		return labelText;
	}
	public final static void setLabelText(String _labelText) {
		labelText = _labelText;
	}
	public final static String getFileToCompile1(){
		return fileToCompile1;
	}
	public final static String getFileToCompile2(){
		return fileToCompile2;
	}
} public abstract class PswDialogBase { 

	// special panel for PswDialogView, currently only used in fileType:
	private static JPanel typePanel;
	
	private static String encryptedFileName;	
	
	private static String fileType; // set in daughter class
	
	private final static String PATH_FILE_NAME = PeaSettings.getJarFileName() + ".path";
	
	private static PswDialogBase dialog;
	
	private char[] initializedPassword = null;
	
	private static String errorMessage = null;
	
	// i18n: 	
	private static String language; 
	private static Locale currentLocale;
	private static Locale defaultLocale = Locale.getDefault();
	private static ResourceBundle languagesBundle;
	
	public static final Charset UTF_8 = Charset.forName("UTF-8");
	
	
	private static String workingMode = ""; // rescue mode or test mode
	
	//============================================================
	// --- abstract Methods:
	//

	// pre computation with windowOpening
	// this is nowhere used yet, but maybe used later 
	// for example ROM-hard KDF schemes  
	public abstract void preComputeInThread();
	//
	// before exit: clear secret values
	public abstract void clearSecretValues();
	//
	public abstract void startDecryption();

	
	// currently only used in fileType:
	public abstract String[] getSelectedFileNames();

	//============================================================
	
	public final static void setLanguagesBundle(String _language) throws MalformedURLException {
		
		
/*		if(languagesBundle == null){
			try {
				//System.out.println("language: " + Locale.getDefault().getLanguage());
				if (Locale.getDefault().getLanguage().contains("de")) {
					setLanguagesBundle(Locale.getDefault().getLanguage());
				} else {
					setLanguagesBundle(null);
				}
			} catch (MalformedURLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}*/
		
		// i18n: 
		if (_language == null) {
			if (defaultLocale == null) { // openSuse
				defaultLocale = new Locale( System.getProperty("user.language") );
			}		
			language = defaultLocale.getLanguage(); // get default language
		} else {
			language = _language;
		}
	    currentLocale = new Locale(language); // set default language as language
	    
	    // First try to open Peafactory i18n, if PswDialogBase is not used inside of a pea 
	    // FilePanel uses this bundle
	    try {
	    	languagesBundle = ResourceBundle.getBundle("config.i18n.LanguagesBundle", currentLocale);
	    } catch (MissingResourceException mre) {// used inside of pea

	    	try {	    
	    	 /*   File file = new File("resources");  
	    	    URL[] urls = {file.toURI().toURL()};  
	    	    ClassLoader loader = new URLClassLoader(urls);  
	    	    languagesBundle = ResourceBundle.getBundle("LanguagesBundle", currentLocale, loader); */ 
	    		languagesBundle = ResourceBundle.getBundle("resources.PeaLanguagesBundle", currentLocale);
	    	} catch  (MissingResourceException mre1) {
	    		
	    		try{
	    			File file = new File("resources");  
		    	    URL[] urls = {file.toURI().toURL()};  
		    	    ClassLoader loader = new URLClassLoader(urls);  
		    	    languagesBundle = ResourceBundle.getBundle("LanguagesBundle", currentLocale, loader);  

	    		} catch (MissingResourceException mre2) {
	    		
		    		currentLocale = new Locale("en"); // set default language as language
		    		language = "en";

		    	    //File file = new File("src" + File.separator + "config" + File.separator + "i18n");
		    	    File file = new File("resources");  

		    	    URL[] urls = {file.toURI().toURL()};  
		    	    ClassLoader loader = new URLClassLoader(urls);  
		    	    languagesBundle = ResourceBundle.getBundle("LanguagesBundle", currentLocale, loader);
	    		}
	    	}
	    }
	}
	

	@SuppressWarnings("all")
	public final static void printInformations() {
		System.out.println("Cipher: " + PeaSettings.getCipherAlgo().getAlgorithmName() );
		System.out.println("Hash: " + PeaSettings.getHashAlgo().getAlgorithmName() );
	}
	
	public final static byte[] deriveKeyFromPsw(char[] pswInputChars) { 

		if (pswInputChars == null) {
			pswInputChars = "no password".toCharArray();
		} else if (pswInputChars.length == 0) {
			pswInputChars = "no password".toCharArray();
		}
		byte[] pswInputBytes = Converter.chars2bytes(pswInputChars); 
		if (pswInputBytes.length > 0)  {
			Zeroizer.zero(pswInputChars);
		}
		// prevent using a zero password:
		pswInputChars = null;

		//=============
		//  derive key: 
		//	
		// 1. initial hash: Bcrypt limited password length, password remains n RAM in Bcrypt and Scrypt
		byte[] pswHash = HashStuff.hashAndOverwrite(pswInputBytes);
		Zeroizer.zero(pswInputBytes);

		// 2. derive key from selected KDF: 
		byte[] keyMaterial = KeyDerivation.getKdf().deriveKey(pswHash);

		Zeroizer.zero(pswHash);		
	
		return keyMaterial;
	}


	protected final byte[] getKeyMaterial() {

		//-------------------------------------------------------------------------------
		// Check if t is alive, if so: wait...
		if (PswDialogView.getThread().isAlive()) { // this should never happen... 
			System.err.println("PswDialogBase: WhileTypingThread still alive!");
			try {
				PswDialogView.getThread().join();
			} catch (InterruptedException e) {
				System.err.println("PswDialogBase: Joining thread interrupted");
				e.printStackTrace();
			}
		}								
		
		//---------------------------------------------------------------------
		// get password 
		char[] pswInputChars = null;
		if (PswDialogView.isInitializing() == false){
			pswInputChars = PswDialogView.getPassword();	
		} else {
			pswInputChars = initializedPassword;
		}

		byte[] keyMaterial = deriveKeyFromPsw(pswInputChars);	
		
		Zeroizer.zero(initializedPassword);
		initializedPassword = null;

		printInformations();
	
		if (keyMaterial == null) {
			PswDialogView.getView().displayErrorMessages("Key derivation failed.\n"
					+ "Program bug.");
			PswDialogView.clearPassword();
			if (CipherStuff.isBound() == false) {
				// reset the salt:
				KeyDerivation.setSalt(Attachments.getProgramRandomBytes());
			}
		}		
		return keyMaterial;
	}

	public final static void initializeVariables() {	
		
		HashStuff.setHashAlgo(PeaSettings.getHashAlgo() );
		CipherStuff.setCipherAlgo(PeaSettings.getCipherAlgo() );
		CipherStuff.setBound(PeaSettings.getBound());
		KeyDerivation.setKdf( PeaSettings.getKdfScheme() );
		KeyDerivation.settCost(PeaSettings.getIterations() );
		//KDFScheme.setScryptCPUFactor(PeaSettings.getIterations() );
		KeyDerivation.setmCost(PeaSettings.getMemory() );
		KeyDerivation.setArg3(PeaSettings.getParallelization());
		KeyDerivation.setVersionString(PeaSettings.getVersionString() );
		Attachments.setProgramRandomBytes(PeaSettings.getProgramRandomBytes() );
		KeyDerivation.setSalt(Attachments.getProgramRandomBytes() );
		
		Attachments.setFileIdentifier(PeaSettings.getFileIdentifier() );
	}
	
	// If a blank PEA is started first: 
	public final byte[] initializeCiphertext(byte[] ciphertext){
		// salt must be set manually and will be attached to the file later
		// in encryption step
		KeyDerivation.setSalt(Attachments.getProgramRandomBytes());
		byte[] attachedSalt = new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize());
		// this will set attachedSalt and update the salt:
		KeyDerivation.setAttachedAndUpdateSalt(attachedSalt);
		
		// attach salt:
		ciphertext = Attachments.attachBytes(ciphertext, attachedSalt);
		// attach fileIdentifier
		ciphertext = Attachments.attachBytes(ciphertext, Attachments.getFileIdentifier());
		
		return ciphertext;
	}
	
	// if the PAE is not bonded to a content: the salt is attached
	public final void handleAttachedSalt(byte[] ciphertext) {
		// reset salt if password failed before:
		KeyDerivation.setSalt(Attachments.getProgramRandomBytes());

		byte[] tmp = new byte[KeyDerivation.getSaltSize()];
		System.arraycopy(ciphertext, 
				ciphertext.length 
				- Attachments.getFileIdentifierSize()
				- KeyDerivation.getSaltSize(), 
				tmp, 0, tmp.length);
		KeyDerivation.setAttachedAndUpdateSalt(tmp);
	}

	//== file functions: ===============================================================	
	
	// checks access and file identifier
	// sets errorMessages
	public final static boolean checkFile(String fileName) {
		File file = new File( fileName );		
		

		if (file.exists() && file.isFile() && file.canRead() && file.canWrite() ) {
			//byte[] content = ReadResources.readExternFile(PswDialogBase.getExternalFilePath());
			RandomAccessFile f;
			try {
				f = new RandomAccessFile(file, "rwd");
		    	if ( Attachments.checkFileIdentifier(f, false) == true) {
			    	f.close();
			    	//============================
			    	// external file name success:
			    	//============================
			    	return true;
				} else {					
					/*if (fileType.equals("passwordSafe") && f.length() == 0) { // just not initialized
						System.out.println("Program not initialized.");
						f.close();
						return true;
					}*/
					f.close();
					System.out.println("PswDialogView: External file path - file identifier failed : " + fileName );
					PswDialogBase.setErrorMessage(
							PswDialogBase.getErrorMessage()
							+ fileName + languagesBundle.getString("file_identifier_failed") + "\n");//": \n file identifier failed.\n");
				}
			} catch (FileNotFoundException e) {
				PswDialogBase.setErrorMessage(
						PswDialogBase.getErrorMessage()
						+ fileName + ": " + e +".\n");
				//e.printStackTrace();
			} catch (HeadlessException e) {
				PswDialogBase.setErrorMessage(
						PswDialogBase.getErrorMessage()
						+ fileName + ": " + e +".\n");
				//e.printStackTrace();
			} catch (IOException e) {
				PswDialogBase.setErrorMessage(
						PswDialogBase.getErrorMessage()
						+ fileName + ": " + e +".\n");
				//e.printStackTrace();
			} catch (Exception e) {
				PswDialogBase.setErrorMessage(
						PswDialogBase.getErrorMessage()
						+ fileName + ": " + e +".\n");
				//e.printStackTrace();
			}
		}
		return false;
	}
	

	
	//
	// 1. checks external file name
	// 2. checks path file
	// success-> sets fileLabel, PswDialogBase.setEncryptedFileName(...)
	// no success -> PswDialogBase.setErrorMessage(...)
	public final static String searchSingleFile() {
		
		PswDialogBase.setErrorMessage("");

		// 1. externalFilePath:
		if ( PeaSettings.getExternalFilePath() != null) {
			
			String externalFileName = PeaSettings.getExternalFilePath();
		/*	if (PswDialogBase.getFileType().equals("passwordSafe")) { 
				externalFileName = externalFileName 
						+ File.separator 
						+ PswDialogBase.getJarFileName() + File.separator
						+ "resources" + File.separator + "password_safe_1.lock";	
			}*/
			if ( checkFile(externalFileName) == true ) { // access and fileIdentifier
				PswDialogView.setFileName( PeaSettings.getExternalFilePath() );
				PswDialogBase.setEncryptedFileName( PeaSettings.getExternalFilePath() ); 
		    	return externalFileName;
			} else {
				System.out.println("PswDialogView: External file path invalid : " + externalFileName );
				PswDialogBase.setErrorMessage(
						PswDialogBase.getErrorMessage()
						//+ "Default file: " + EXTERNAL_FILE_PATH + "--- No access.\n");
						+ languagesBundle.getString("default_file") + " "
						+ PeaSettings.getExternalFilePath()
						+ "--- " +  languagesBundle.getString("no_access") + "\n");
			}

		} else { //if (PswDialogBase.getEncryptedFileName() == null) {
			if (fileType.equals("image") ) {
				//if ( PeaSettings.getBound() == false) {
				//	System.out.println("Base");
				//} else {
					// image was stored as text.lock inside jar file in directory resource
				return "insideJar";
				//}
			}
		}
			
		// check path file
		String[] pathNames = accessPathFile();
		
		if (pathNames == null) {
			PswDialogBase.setErrorMessage(
					PswDialogBase.getErrorMessage()
					+ languagesBundle.getString("no_path_file_result") + "\n");
					//+ "No result from previously saved file names in path file.\n");
		} else {
			
			for (int i = 0; i < pathNames.length; i++) {
				
				if (checkFile(pathNames[i]) == true ) {
					PswDialogView.setFileName(pathNames[i]);
					PswDialogBase.setEncryptedFileName(pathNames[i]);
					return pathNames[i];
					
				} else {
					PswDialogBase.setErrorMessage(
							PswDialogBase.getErrorMessage() +
							//"File from path file: " + pathNames[i] + "--- No access.\n");
					 languagesBundle.getString("file_from_path_file") + " "
					+ pathNames[i] 
					+ "--- " +  languagesBundle.getString("no_access") + "\n");
				}
			}						
		}

		// no file found from external file name and path file:
		if (PswDialogView.getFileName() == null) {
			PswDialogView.setFileName(languagesBundle.getString("no_valid_file_found"));//"no valid file found");
		}				
		return null;
	}
	
	// detects file names from path file
	protected final static String[] accessPathFile() {
		File file = new File(PATH_FILE_NAME);
		if (! file.exists() ) {
			System.err.println("PswDialogBase: no path file specified");
			return null;
		}
		if (! file.canRead() ) {
			System.err.println("PswDialogBase: can not read path file " + file.getName() );
			PswDialogView.setMessage(languagesBundle.getString("path_file_access_error")
				+ "\n" +  PATH_FILE_NAME);
/*			JOptionPane.showInternalMessageDialog(PswDialogView.getView(),
					languagesBundle.getString("path_file_access_error")
				//"There is are file containing names of potentially encrypted files,\n" +
				//" but no access to it: \n"
				+ PATH_FILE_NAME, 
				"Info",//title
				JOptionPane.INFORMATION_MESSAGE);*/
			return null;
		}
		
		byte[] pathBytes = ReadResources.readExternFile( file.getName() );
		if (pathBytes == null) {
			System.err.println("PswDialogBase: path file does not contain any file name: " + file.getPath() );
			return null;
		}
		String pathString = new String(pathBytes, UTF_8);

		String[] pathNames = pathString.split("\n");
	
		return pathNames;			
	}
	
	// checks if file names already exist in path file
	// if not: adds them	
	public final static void addFilesToPathFile(String[] selectedFileNames) {

		byte[] newPathBytes = null;
		StringBuilder newPathNames = null;
				
		File file = new File(PATH_FILE_NAME);
		
		if (! file.exists() ) { // WriteResources creates new file
			
			newPathNames = new StringBuilder();
			
			for (int i = 0; i < selectedFileNames.length; i++) {
				newPathNames.append(selectedFileNames[i]);
				newPathNames.append("\n");
			}
			
		} else {	// path file exists
		
			if (! file.canRead() || ! file.canWrite() ) {
				System.err.println("PswDialogBase: can not read and write path file " + file.getName() );
				
				PswDialogView.setMessage(languagesBundle.getString("path_file_access_error")
						+ "\n" 
						+ PATH_FILE_NAME);
			/*	JOptionPane.showInternalMessageDialog(PswDialogView.getView(),
						languagesBundle.getString("path_file_access_error")
					//"There is are file containing names of potentially encrypted files,\n" +
					//" but no access to it: \n" 
					+ PATH_FILE_NAME, 
					"Info",//title
					JOptionPane.INFORMATION_MESSAGE); */
				return;
			}
			
			// get file names from path file:
			String[] oldPathNames = accessPathFile();
			
			newPathNames = new StringBuilder();
			
			// append old file names:
			for (int i = 0; i < oldPathNames.length; i++) {
				newPathNames.append(oldPathNames[i]);
				newPathNames.append("\n");
			}
			// check if file name already exists, if not append
			for (int i = 0; i < selectedFileNames.length; i++) {
				boolean append = true;
				for (int j = 0; j < oldPathNames.length; j++) {
					if (selectedFileNames[i].equals(oldPathNames[j] )){
						append = false;
					}
				}
				if (append == true) {
					newPathNames.append(selectedFileNames[i]);
					newPathNames.append("\n");
				}
			}
		}

		newPathBytes = new String(newPathNames).getBytes(UTF_8);
			
		WriteResources.write(newPathBytes, PATH_FILE_NAME, null);		
	}
	
	// checks if current encryptedFileName is in path file
	// asks to remember if not
	protected final static void pathFileCheck() {
		//String encryptedFileName = getEncryptedFileName();
		if (encryptedFileName != null) {
			if (encryptedFileName.equals(PeaSettings.getExternalFilePath() )) {
				return; // no need to remember
			}			
		}
		String pathFileName = PeaSettings.getJarFileName() + ".path";
		byte[] pathFileContent = ReadResources.readExternFile(pathFileName);
		if (pathFileContent == null) {
			int n = JOptionPane.showConfirmDialog(null,
					languagesBundle.getString("remember_files")
			//		"Should the name of the file be saved to remember for the next program start? \n" 
					+ languagesBundle.getString("path_file_storage_info")
				//+ "The name is then saved in the same folder as this program in:"
			+ pathFileName , 
				"?", 
				JOptionPane.OK_CANCEL_OPTION, 
				JOptionPane.PLAIN_MESSAGE);
				if (n == 2) { // cancel
					return;
				} else if (n == 0){
					byte[] encryptedFileNameBytes = encryptedFileName.getBytes(UTF_8);

					WriteResources.write(encryptedFileNameBytes, pathFileName, null);
				}
			return;
		} else {
			String pathNamesString = new String(pathFileContent, UTF_8);

			String[] pathNames = pathNamesString.split("\n");
			// check if encryptedFileName is already included
			for (int i = 0; i < pathNames.length; i++) {
				if (pathNames[i].equals(encryptedFileName)) {
					System.out.println("Already included: " + encryptedFileName);
					return;
				}
			}
						
			// is not included:
			int n = JOptionPane.showConfirmDialog(null,
			//	"Should the name of the file be saved to remember for the next program start? \n" 
			//+ "(The name is then saved in " + pathFileName + " in the same folder as this program).", 
			languagesBundle.getString("remember_files")
			+ languagesBundle.getString("path_file_storage_info")
			+ pathFileName , 
			"?", 
			JOptionPane.OK_CANCEL_OPTION, 
			JOptionPane.PLAIN_MESSAGE);
			if (n == 2) { // cancel
				return;
			} else if (n == 0){
				byte[] encryptedFileNameBytes =  ("\n" + encryptedFileName).getBytes(UTF_8);

				byte[] newContent = new byte[pathFileContent.length + encryptedFileNameBytes.length];
				System.arraycopy(pathFileContent, 0, newContent, 0, pathFileContent.length);
				System.arraycopy(encryptedFileNameBytes, 0, newContent, pathFileContent.length, encryptedFileNameBytes.length);
				
				if (newContent != null) {
					WriteResources.write(newContent, pathFileName, null);
				}
				
			}
		}		
	} 


	//==========================================
	// Getter & Setter

	public final static Charset getCharset(){
		return UTF_8;
	}
	
	protected final static void setFileType(String _fileType) {
		fileType = _fileType;
	}
	public final static String getFileType() {
		return fileType;
	}
	public final static String getEncryptedFileName() {
		return encryptedFileName;
	}
	public final static void setEncryptedFileName(String _encryptedFileName) {
		encryptedFileName = _encryptedFileName;
	}
	protected final static void setDialog(PswDialogBase _dialog) {
		dialog = _dialog;
	}
	public final static PswDialogBase getDialog() {
		return dialog;
	}
	public final static String getPathFileName() {
		return PATH_FILE_NAME;
	}
	public final static JPanel getTypePanel() {
		return typePanel;
	}
	public final static void setTypePanel( JPanel panel){
		typePanel = panel;
	}
	public final static String getErrorMessage() {
		return errorMessage;
	}
	public final static void setErrorMessage(String newErrorMessage) {
		errorMessage = newErrorMessage;
	}
	public final static ResourceBundle getBundle(){
		if (languagesBundle == null) { // set default language
			try {
				setLanguagesBundle(null);
			} catch (MalformedURLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return languagesBundle;
	}
	
	public char[] getInitializedPasword(){
		return initializedPassword;
	}
	public void setInitializedPassword(char[] psw){
		initializedPassword = psw;
	}
	/**
	 * @return the workingMode
	 */
	public static String getWorkingMode() {
		return workingMode;
	}
	/**
	 * @param workingMode the workingMode to set
	 */
	public static void setWorkingMode(String workingMode) {
		PswDialogBase.workingMode = workingMode;
	}
} public class FileType extends DataType {
	
	private static byte[] fileNamesAsBytes = null;

	@Override
	public void processData( byte[] keyMaterial) {
		
		if (MainView.isBlankPea() == false) {
		
			String[] fileNames = MainView.getFilePanel().getSelectedFileNames();

			FileTypePanel filePanel = MainView.getFilePanel();
			
			filePanel.startProgressTask();// starts if execution > 1 second
			CipherStuff.getCipherMode().encryptFiles(fileNames, keyMaterial, false, filePanel);
			filePanel.closeProgressTask();// close automatically
			
		} else {
			
			byte[] cipherBytes = "uninitializedFile".getBytes(PeaFactory.getCharset());
			
			String dirName = JarStuff.getJarFileName().substring(0, JarStuff.getJarFileName().length() - 4); // extract ".jar"

			// store cipherBytes beside jar archive:
			WriteResources.write(cipherBytes, "text.lock", "peas" + File.separator + dirName + File.separator + "resources");
		}
	}

	@Override
	public String getDescription() {
		return PeaFactory.getLanguagesBundle().getString("file_description");
	}

	@Override
	public String getTypeName() {
		return "file";
	}

	@Override
	public byte[] getData() {
		return fileNamesAsBytes;
	}

	@Override
	public void setData(byte[] data) {
		fileNamesAsBytes = data;		
	}
} @SuppressWarnings("serial")
public class PswDialogView extends JDialog implements WindowListener,
		ActionListener, KeyListener {
	
	private static final Color peaColor = new Color(230, 249, 233);
	private static final Color messageColor = new Color(252, 194, 171);//(255, 216, 151)
	
	private static JLabel pswLabel;
	private static JPasswordField pswField;
	private JButton okButton;
	
	private JPanel filePanel;
	
	private static JLabel fileLabel;

	private static String fileType = "dingsda";
	
	private static JCheckBox rememberCheck;
	
	private static PswDialogView dialog;
	
	// display warnings and error messages
	private static JTextArea messageArea;
	
	private static WhileTypingThread t;
	
	private static Image image;
	
	private static ResourceBundle languagesBundle;
	
	private static boolean started = false;// to check if decryption was startet by ok-button
	
	private static boolean initializing = false; // if started first time (no password, no content)
	
	private PswDialogView() {
		
		setUI();
		
		languagesBundle = PswDialogBase.getBundle();
		
		fileType = PswDialogBase.getFileType();
	
		dialog = this;
		this.setTitle(PeaSettings.getJarFileName() );
				
		this.setBackground(peaColor);

		URL url = this.getClass().getResource("resources/pea-lock.png");
		if (url == null) {
			try{
			image = new ImageIcon(getClass().getClassLoader().getResource("resources/pea-lock.png")).getImage();
			} catch (Exception e) {
				System.out.println("image not found");
			}
		} else {			
			image = new ImageIcon(url).getImage();
		}


	    this.setIconImage(image);
	
		this.addWindowListener(this);
		
		this.addMouseMotionListener(new MouseRandomCollector() );

		JPanel topPanel = (JPanel) this.getContentPane();
		// more border for right and bottom to start
		// EntropyPool if there is no password
		topPanel.setBackground(peaColor);
		topPanel.setBorder(new EmptyBorder(5,5,15,15));
		
		topPanel.addMouseMotionListener(new MouseRandomCollector() );
		
		messageArea = new JTextArea();
		messageArea.setEditable(false);	
		messageArea.setLineWrap(true);
		messageArea.setWrapStyleWord(true);
		messageArea.setBackground(topPanel.getBackground());		
		
		okButton = new JButton("ok");
		okButton.setPreferredSize(new Dimension( 60, 30));
		okButton.setActionCommand("ok");
		okButton.addActionListener(this);
		// if there is no password, this might be the only 
		// chance to start EntropyPool
		okButton.addMouseMotionListener(new MouseRandomCollector() );
		
		topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));		
		
		topPanel.add(messageArea);
		
		if (initializing == false) {			
			
			if (PswDialogBase.getWorkingMode().equals("-r")) { // rescue mode
				JLabel rescueLabel = new JLabel("=== RESCUE MODE ===");
				rescueLabel.setForeground(Color.RED);
				topPanel.add(rescueLabel);
			}

			if (PeaSettings.getExternFile() == true) {	// display files and menu		
				
				if (fileType.equals("file") ) {
					filePanel = PswDialogBase.getTypePanel();
					filePanel.addMouseMotionListener(new MouseRandomCollector() );
					topPanel.add(filePanel);
					
				} else { // text, passwordSafe, image: one single file
					
					JPanel singleFilePanel = new JPanel();
					singleFilePanel.setLayout(new BoxLayout(singleFilePanel, BoxLayout.Y_AXIS));
					
					JPanel fileNamePanel = new JPanel();
					fileNamePanel.setLayout(new BoxLayout(fileNamePanel, BoxLayout.X_AXIS));
					singleFilePanel.add(fileNamePanel);
					
					JLabel noteLabel = new JLabel();
					noteLabel.setText(languagesBundle.getString("encrypted_file"));//"encryptedFile: ");				
					fileNamePanel.add(noteLabel);
					fileNamePanel.add(Box.createHorizontalStrut(10));
					
					fileLabel = new JLabel(languagesBundle.getString("no_valid_file_found") + languagesBundle.getString("select_new_file"));//"no valid file found - select a new file");
					fileNamePanel.add(fileLabel);
					fileNamePanel.add(Box.createHorizontalGlue() );
					
					JPanel openButtonPanel = new JPanel();
					openButtonPanel.setLayout(new BoxLayout(openButtonPanel, BoxLayout.X_AXIS));
					singleFilePanel.add(openButtonPanel);
					
					JButton openFileButton = new JButton();
					openFileButton.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 11));
					
					if (fileType.equals("passwordSafe")){
						openFileButton.setText("open directory");
					} else { // text, image
						openFileButton.setText(languagesBundle.getString("open_file"));//"open file");
					}
					openFileButton.addActionListener(this);
					openFileButton.setActionCommand("openFile");
					openButtonPanel.add(openFileButton);
					openButtonPanel.add(Box.createHorizontalGlue() );
					
					topPanel.add(singleFilePanel);
					
					if (PswDialogBase.searchSingleFile() == null) {
						JOptionPane.showMessageDialog(this,
								PswDialogBase.getErrorMessage(),
							    "Error",
							    JOptionPane.ERROR_MESSAGE);
						//reset errorMessage:
						PswDialogBase.setErrorMessage("");
					}
				}
			}//else
			topPanel.add(Box.createVerticalStrut(10));//Box.createVerticalStrut(10));
			
			JPanel pswLabelPanel = new JPanel();
			pswLabelPanel.addMouseMotionListener(new MouseRandomCollector() );
			pswLabelPanel.setLayout(new BoxLayout(pswLabelPanel, BoxLayout.X_AXIS));
			topPanel.add(pswLabelPanel);
			
			if (PeaSettings.getLabelText() == null) {
				pswLabel = new JLabel( languagesBundle.getString("enter_password") );
			} else {
				pswLabel = new JLabel( PeaSettings.getLabelText() );
				//pswLabel.setText(PeaSettings.getLabelText() );
			}
			pswLabel.setPreferredSize(new Dimension( 460, 20));
			pswLabelPanel.add(pswLabel);
			pswLabelPanel.add(Box.createHorizontalGlue());
			JButton charTableButton = new JButton(languagesBundle.getString("char_table"));
			charTableButton.addMouseMotionListener(new MouseRandomCollector() );
			charTableButton.addActionListener(this);
			charTableButton.setActionCommand("charTable1");
			pswLabelPanel.add(charTableButton);

			
			JPanel pswPanel = new JPanel();
			pswPanel.setLayout(new BoxLayout(pswPanel, BoxLayout.X_AXIS));
			
			pswField = new JPasswordField();
			pswField.setBackground(new Color(231, 231, 231) );
			pswField.setPreferredSize(new Dimension(400, 30));
			pswField.addKeyListener(this);
			pswField.addKeyListener(new KeyRandomCollector() );

			pswPanel.add(pswField);
			
			pswPanel.add(okButton);
			topPanel.add(pswPanel);
		
		} else {// initializing
			
			if (fileType.equals("file") ) {
				JPanel initPanel1 = new JPanel();
				initPanel1.setLayout(new BoxLayout(initPanel1, BoxLayout.X_AXIS));
				JLabel fileInitializationLabel = new JLabel("Select one file and type a password to initialize...");
				initPanel1.add(fileInitializationLabel);
				initPanel1.add(Box.createHorizontalGlue());
				topPanel.add(initPanel1);
				
				JPanel initPanel2 = new JPanel();
				initPanel2.setLayout(new BoxLayout(initPanel2, BoxLayout.X_AXIS));
				JLabel fileInitializationLabel2 = new JLabel("(you can add more files or directories later).");
				initPanel2.add(fileInitializationLabel2);
				initPanel2.add(Box.createHorizontalGlue());
				topPanel.add(initPanel2);				
			}
			
			JPanel panel = new JPanel();
			panel.setPreferredSize(new Dimension(400, 300));
			panel.addMouseMotionListener(new MouseRandomCollector() );
			
			JLabel infoLabel = new JLabel("Initialization...  ");
			infoLabel.setForeground(Color.RED);
			infoLabel.addMouseMotionListener(new MouseRandomCollector() );
			panel.add(infoLabel);
			panel.add(okButton);
			
			topPanel.add(panel);
		}		
		
		if (PeaSettings.getExternFile() == true){		
			
			JPanel checkRememberPanel = new JPanel();
			checkRememberPanel.setLayout(new BoxLayout(checkRememberPanel, BoxLayout.X_AXIS));
			
			rememberCheck = new JCheckBox();
		    rememberCheck = new JCheckBox();	
		    rememberCheck.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
		    rememberCheck.setText(languagesBundle.getString("remember_file_names"));//"remember selected file names");
		    rememberCheck.setToolTipText(languagesBundle.getString("file_storage_info")// "file names will be saved in the file " +
		    		+  "  "  + PeaSettings.getJarFileName() + ".path");// in current directory");
			
		    checkRememberPanel.add(Box.createVerticalStrut(10));
		    checkRememberPanel.add(rememberCheck);
		    checkRememberPanel.add(Box.createHorizontalGlue() );
		    
		    topPanel.add(checkRememberPanel);
		}
		if (PeaSettings.getKeyboard() != null) {
			JButton keyboardButton = new JButton("keyboard");
			keyboardButton.addActionListener(this);
			keyboardButton.setActionCommand("keyboard");
			topPanel.add(keyboardButton);			
		}

		this.setLocation(100,30);
		pack();
	}
	
	public final static PswDialogView getInstance() {
		if (dialog == null) {
			dialog = new PswDialogView();
		} else {
			//return null
		}
		return dialog;
	}
	
	public static void checkInitialization(){
		// check if this file was not yet initialized and if not,  
		// remove password field
		try {
			// the String "uninitializedFile" is stored in the file "text.lock"
			// if the pea was not yet initialized
			
			// try to read only the first line: 
			BufferedReader brTest = new BufferedReader(new FileReader("resources" + File.separator + "text.lock"));
		    String test = brTest .readLine();
		    brTest.close();
			if (test.startsWith("uninitializedFile")){
				// set variable initializing to indicate that there is no password or content 
				//PswDialogView.setInitialize(true);
				initializing = true;
				System.out.println("Initialization");
			}
		} catch (Exception e) {
			// ignore
		}
	}

	public final void displayErrorMessages(String topic) {
		
		setMessage(topic + ":\n" + CipherStuff.getErrorMessage());

		PswDialogView.clearPassword();
	}

	@Override
	public void keyPressed(KeyEvent kpe) {
		// EntropyPool must be started by mouse events if initializing
		if(kpe.getKeyChar() == KeyEvent.VK_ENTER && initializing == false){
			okButton.doClick();
		}
	}
	@Override
	public void keyReleased(KeyEvent arg0) {}
	@Override
	public void keyTyped(KeyEvent arg0) {}

	@Override
	public void actionPerformed(ActionEvent ape) {
		String command = ape.getActionCommand();
		//System.out.println("command: " + command);
		if(command.equals("keyboard")){
			PeaSettings.getKeyboard().setVisible(true);
			
		} else if (command.equals("charTable1")){
			CharTable table = new CharTable(this, pswField);
			table.setVisible(true);
			
		} else if (command.equals("ok")){

			if (PeaSettings.getKeyboard() != null) {
				PeaSettings.getKeyboard().dispose();
			}
			
			started = true;

			EntropyPool.getInstance().stopCollection();

			if (PeaSettings.getExternFile() == true && initializing == false) {

				// remember this file?
				if (rememberCheck.isSelected() ) {
					
						String[] newNames;
						if (PswDialogBase.getFileType().equals("file")) {
							// set encrypted file names:
							newNames = PswDialogBase.getDialog().getSelectedFileNames();

						} else {
							newNames = new String[1];
							newNames[0] = PswDialogBase.getEncryptedFileName();
						}
						PswDialogBase.addFilesToPathFile( newNames );
				}
				
				if ( ! fileType.equals("file")) { // text, image
					
					// check if one valid file is selected:
					if (PswDialogBase.getEncryptedFileName() == null) {
						
						setMessage(languagesBundle.getString("no_file_selected"));

						return;
					}
					
					PswDialogBase.getDialog().startDecryption();
				} else { // file
					PswDialogBase.getDialog().startDecryption();
				}
			} else { // internal File in directory "resources"

				// set resources/text.lock as encryptedFileName
				String newFileName = "resources" + File.separator + "text.lock";

				PswDialogBase.setEncryptedFileName(newFileName);

				PswDialogBase.getDialog().startDecryption();
		
				// remember this file?
				if (PeaSettings.getExternFile() == true && initializing == false) {
					if (rememberCheck.isSelected() ) {
						String[] newName = { PswDialogBase.getEncryptedFileName() };
						PswDialogBase.addFilesToPathFile( newName);
					}
				}
			}
			started = false;

			
		} else if (command.equals("openFile")) {

			JFileChooser chooser = new JFileChooser();

			if (fileType.equals("text")) {
				FileNameExtensionFilter filter = new FileNameExtensionFilter("TEXT FILES", "txt", "text", "rtf");
				chooser.setFileFilter( filter );
			} else if (fileType.equals("passwordSafe")) {
				chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			    chooser.setAcceptAllFileFilterUsed(false);
			} else if (fileType.equals("image")) {
				FileNameExtensionFilter filter = new FileNameExtensionFilter("IMAGE FILES", "png", "jpg", "jpeg", "bmp", "gif");
				chooser.setFileFilter( filter );
			}
		    int returnVal = chooser.showOpenDialog(this);
		    if(returnVal == JFileChooser.APPROVE_OPTION) {		    
		    	
		    	File file = chooser.getSelectedFile();
		    	String selectedFileName = file.getAbsolutePath();
		    	
		    	if ( PswDialogBase.checkFile(selectedFileName ) == true ) {
					fileLabel.setText( selectedFileName );
					PswDialogBase.setEncryptedFileName( selectedFileName ); 
		    	} else {
		    		setMessage(languagesBundle.getString("error") 
		    				+ "\n" + languagesBundle.getString("no_access_to_file")
		    				+ "\n" + file.getAbsolutePath());
					return;
		    	}
		    }
		}		
	}
	

	@Override
	public void windowActivated(WindowEvent arg0) {}
	@Override
	public void windowClosed(WindowEvent arg0) { // dispose

		// if attacker has access to memory but not to file
		if (Attachments.getNonce() != null) {
			Zeroizer.zero(Attachments.getNonce());
		}
		if (PswDialogBase.getDialog() != null) {
			PswDialogBase.getDialog().clearSecretValues();
		}	

		this.dispose();
		//System.exit(0);	// do not exit, otherwise files leave unencrypted	
	}
	@Override
	public void windowClosing(WindowEvent arg0) { // x
		
		if (started == true) {
			return;
		} else {
			// if attacker has access to memory but not to file
			if (Attachments.getNonce() != null) {
				Zeroizer.zero(Attachments.getNonce());
			}
			if (PswDialogBase.getDialog() != null) {
				PswDialogBase.getDialog().clearSecretValues();
			}
			System.exit(0);
		}	
	}
	@Override
	public void windowDeactivated(WindowEvent arg0) {}
	@Override
	public void windowDeiconified(WindowEvent arg0) {}
	@Override
	public void windowIconified(WindowEvent arg0) {}

	@Override
	public void windowOpened(WindowEvent arg0) {
		
		// do some expensive computations while user tries to remember the password
		t = this.new WhileTypingThread();
		t.setPriority( Thread.MAX_PRIORITY );// 10
		t.start();
		if (initializing == false) {
			pswField.requestFocus();
		}
	}

	
	//=======================================
	// Helper Functions
	
	public final static void setUI() {
//		UIManager.put("Button.background", peaColor );					
//		UIManager.put("MenuBar.background", peaColor ); // menubar
//		UIManager.put("MenuItem.background", color2 ); // menuitem
		UIManager.put("PopupMenu.background", peaColor );	//submenu									
		UIManager.put("OptionPane.background", peaColor );
		UIManager.put("Panel.background", peaColor );
		UIManager.put("RadioButton.background", peaColor );
		UIManager.put("ToolTip.background", peaColor );
		UIManager.put("CheckBox.background", peaColor );	
		
		UIManager.put("InternalFrame.background", peaColor );
		UIManager.put("ScrollPane.background", peaColor );
		UIManager.put("Viewport.background", peaColor );	
		// ScrollBar.background  Viewport.background
	}
	

	
	protected final static void updateView(){
		dialog.dispose();
		dialog = new PswDialogView();
		dialog.setVisible(true);
	}
	
	/**
	 * Perform he action for ok button. Used for initialization. 
	 */
	public void clickOkButton() {
		okButton.doClick();
	}
	

	
	//===============================================
	// Getter & Setter
	public final static char[] getPassword() {
		return pswField.getPassword();
	}
	protected final static void setPassword(char[] psw) { // for Keyboard
		pswField.setText( new String(psw) );
	}
	protected final static void setPassword(char pswChar) { // for charTable
		pswField.setText("" +  pswChar );
	}
	public final static void clearPassword() {
		if (pswField != null) {
			pswField.setText("");
		}
	}
	public final static void setMessage(String message) {
		if (message != null) {
			messageArea.setBackground(messageColor);
			messageArea.setText(message);
			dialog.pack();
		}
	}
	public final static void setLabelText(String labelText) {
		if (initializing == true){
			pswLabel.setText(labelText);
		}
	}
	public final static WhileTypingThread getThread() {
		return t;
	}
	public final static Image getImage() {
		if (image == null) {
			System.err.println("PswDialogView getImage: image null");
		}
	    return image;
	}
	public final static PswDialogView getView() {
		return dialog;
	}
	public final JPanel getFilePanel() {
		return filePanel;
	}
	public final static void setFileName(String fileName) {
		fileLabel.setText(fileName);
	}
	public final static String getFileName() {
		return fileLabel.getText();
	}
	public final static Color getPeaColor(){
		return peaColor;
	}
	
	/**
	 * @return the initializing
	 */
	public static boolean isInitializing() {
		return initializing;
	}

	/**
	 * @param initializing the initializing to set
	 */
	public static void setInitializing(boolean initialize) {
		PswDialogView.initializing = initialize;
	}

	//================================================
	// inner class
	public class WhileTypingThread extends Thread {
		// some pre-computations while typing the password
		@Override
		public void run() {		
			if (PswDialogBase.getDialog() != null) {
				PswDialogBase.getDialog().preComputeInThread();
			}
		}
	}
} public final class Attachments {
	
	// Random values:
	private static byte[] programRandomBytes  = null; // unique for every program/dialog
	private static byte[] nonce = null; // unique for every encrypted content 
	private static byte[] fileIdentifier = null; // unique for every program/dialog

	
	protected final static int PSW_IDENTIFIER_SIZE = 8; // byte
	protected final static int FILE_IDENTIFIER_SIZE = 8; // byte
	protected final static int PROGRAM_RANDOM_BYTES_SIZE = KeyDerivation.getSaltSize();// at most 129
	protected final static int NONCE_SIZE = 8; // byte

	
	//========== Helper Functions: ==============================================
	
	private final static void addBytes(RandomAccessFile f, byte[] supplementBytes) 
			throws IOException {
		f.seek( f.length() ); // set file pointer
		f.write(supplementBytes, 0, supplementBytes.length ); 
	}
	public final static byte[] getLastBytes(RandomAccessFile f, int resultSize, boolean truncate) 
			throws IOException {
		
		if (resultSize < 0 || f.length() < resultSize) {
			throw new IllegalArgumentException("invalid value to get last bytes");
		}
			
		byte[] result = new byte[resultSize];

		long fileSize = 0;

		fileSize = f.length();
		if (fileSize - resultSize < 0) {
			throw new IllegalArgumentException( "invalid file size, file:  " + f.toString() );
		}
		f.seek(fileSize - resultSize); // set file pointer
		f.read(result, 0, resultSize ); // read bytes
		if (truncate == true) {
			cutLastBytes(f,  resultSize); // truncate file
		}
		return result;
	}
	// more secure cut (overwrites truncated bytes)
	private final static void cutLastBytes(RandomAccessFile f, int truncateSize) 
			throws IOException {
		if (truncateSize < 0 || f.length() < truncateSize) {
			throw new IllegalArgumentException("invalid value to cut last bytes");
		}
		byte[] nullBytes = new byte[truncateSize];
		f.seek(f.length() - truncateSize); // set file pointer
		f.write(nullBytes, 0, nullBytes.length);
		f.setLength(f.length() - truncateSize);
	}

	
	//=== psw identifier ======================================================
	
	// add the inverted last bytes to the end to check later if password fails
	// (invert: if file contains all the same bytes, this method would fail)
	// short files: padding identifier with 0xFF (toInvertBytes with 0)
	// exits if failed
/*	public final static void addPswIdentifier(RandomAccessFile f) 
			throws IOException{ 
		
		byte[] pswIdentifier = new byte[PSW_IDENTIFIER_SIZE]; // return value		

		long fileSize = f.length();
		if (fileSize == 0) {
			throw new IllegalArgumentException("empty file");
		}			
		
		byte[] toInvertBytes = null;
		if (fileSize < PSW_IDENTIFIER_SIZE) {
			byte[] lastBytes = getLastBytes(f, (int)fileSize, false);
			toInvertBytes = new byte[PSW_IDENTIFIER_SIZE];
			System.arraycopy(lastBytes, 0, toInvertBytes, 0, lastBytes.length); // rest 0
		} else {
			toInvertBytes = getLastBytes(f, PSW_IDENTIFIER_SIZE, false); 
		}
		for (int i = 0; i < PSW_IDENTIFIER_SIZE; i++) {
			pswIdentifier[i] = (byte) ~( (int) toInvertBytes[i]);
		}	
		addBytes(f, pswIdentifier);
		Arrays.fill(toInvertBytes, (byte) 0);
		Arrays.fill(pswIdentifier, (byte) 0);
	}*/
	public final static byte[] addPswIdentifier(byte[] plainBytes) {
		
		byte[] pswIdentifier = new byte[PSW_IDENTIFIER_SIZE]; // return value		

		if (plainBytes.length == 0) { // if no text: set space as plainBytes
			plainBytes = " ".getBytes();
		}	
		int inputSize = plainBytes.length;
			
		byte[] toInvertBytes = new byte[PSW_IDENTIFIER_SIZE];
		if (inputSize < PSW_IDENTIFIER_SIZE) {
			System.arraycopy(plainBytes, 0, toInvertBytes, 0, inputSize); // rest 0
		} else {
			System.arraycopy(plainBytes, inputSize - PSW_IDENTIFIER_SIZE, toInvertBytes, 0, PSW_IDENTIFIER_SIZE);
		}
		for (int i = 0; i < PSW_IDENTIFIER_SIZE; i++) {
			pswIdentifier[i] = (byte) ~( (int) toInvertBytes[i]);
		}	
		byte[] tmp = new byte[inputSize + PSW_IDENTIFIER_SIZE];
		System.arraycopy(plainBytes, 0, tmp, 0, plainBytes.length);
		System.arraycopy(pswIdentifier, 0, tmp, plainBytes.length, PSW_IDENTIFIER_SIZE);
		Zeroizer.zero(plainBytes);
		plainBytes = tmp;
		Zeroizer.zero(toInvertBytes);
		Zeroizer.zero(pswIdentifier);
		
		return plainBytes;
	}
		
	// truncates only if true
	public final static byte[] checkAndCutPswIdentifier(byte[] block) { 
		
		if (block.length <= PSW_IDENTIFIER_SIZE) {
			System.err.println("block too short for pswIdentifier");
			return null;
		}
		byte[] result = new byte[block.length - PSW_IDENTIFIER_SIZE];

		byte[] bytesToCheck = new byte[PSW_IDENTIFIER_SIZE];
		byte[] pswIdentifier = new byte[PSW_IDENTIFIER_SIZE];
		System.arraycopy(block, block.length - PSW_IDENTIFIER_SIZE, pswIdentifier, 0, PSW_IDENTIFIER_SIZE);
		if (block.length > PSW_IDENTIFIER_SIZE + PSW_IDENTIFIER_SIZE) {
			System.arraycopy(block, block.length - PSW_IDENTIFIER_SIZE - PSW_IDENTIFIER_SIZE, bytesToCheck, 0, PSW_IDENTIFIER_SIZE);

		} else { // padded pswIdentifier
			System.arraycopy(block, 0, bytesToCheck, 0, block.length - PSW_IDENTIFIER_SIZE);		
		}
		
		for (int i = 0; i < PSW_IDENTIFIER_SIZE; i++) {
			if ( ~bytesToCheck[i] != pswIdentifier[i]) {
				System.err.println("pswIdentifier failed at position " + i);
				return null;
			}
		}
		Zeroizer.zero(bytesToCheck);

		System.arraycopy(block, 0, result, 0, result.length);
		Zeroizer.zero(block);
		return result;
	}


	
	//=== file identifier ======================================================
	
	public final static void addFileIdentifier( RandomAccessFile f) 
			throws IOException { 
		
		if( fileIdentifier == null ) {
			throw new IllegalArgumentException ("fileIdentifier null");
		}		
		addBytes(f, fileIdentifier);
	}

	public final static byte[] addFileIdentifier(byte[] cipherBytes) {
		
		if(cipherBytes == null || fileIdentifier == null) {
			System.err.println("Attachments: add fileIdentifier failed: "
					+ "cipherBytes or fileIdentifier null");
			System.exit(1);
		}
		
		byte[] result = new byte[cipherBytes.length + fileIdentifier.length];
		System.arraycopy(cipherBytes,  0,  result,  0,  cipherBytes.length);
		System.arraycopy(fileIdentifier,  0,  
				result, cipherBytes.length, fileIdentifier.length);
		
		Zeroizer.zero(cipherBytes);
		
		return result;		
	}

	public final static boolean checkFileIdentifier( RandomAccessFile f, boolean truncate) 
			throws IOException { 
		
		if(f.length() < FILE_IDENTIFIER_SIZE ) {
			System.err.println("file too short to contain fileIdentifier");
			return false;
		}	

		byte[] checkIdentifier = getLastBytes(f, FILE_IDENTIFIER_SIZE, false);
		
		if (Arrays.equals(checkIdentifier, fileIdentifier)) {
			if (truncate == true) {
				cutLastBytes(f, FILE_IDENTIFIER_SIZE);
			}
			return true;
		} else {
			return false;
		}
	}
	
	// return null if check failed
	public final static byte[] checkFileIdentifier( byte[] input, boolean truncate) {
		
		byte[] result = new byte[input.length - FILE_IDENTIFIER_SIZE];	
		//Help.printBytes("inpt", input);
		if(input.length < FILE_IDENTIFIER_SIZE ) {
			System.err.println("input too short to contain fileIdentifier");
			return null;
		}	
		byte[] identifierToCheck = new byte[FILE_IDENTIFIER_SIZE];
		System.arraycopy(input, input.length - FILE_IDENTIFIER_SIZE, identifierToCheck, 0, FILE_IDENTIFIER_SIZE);
		
		if (Arrays.equals(identifierToCheck, fileIdentifier)) {
			if (truncate == true) {
							
				System.arraycopy(input, 0, result, 0, result.length);
				Zeroizer.zero(input);
				//input = tmp;				
			}
			return result;
		} else {
			return null;
		}
	}

	public final static void generateAndSetFileIdentifier() {
		fileIdentifier = new RandomStuff().createRandomBytes( Attachments.getFileIdentifierSize() );		
	}
	public final static void setFileIdentifier(byte[] _fileIdentifier) {
		fileIdentifier = _fileIdentifier;
	}
	public final static byte[] getFileIdentifier() {
//		if (fileIdentifier == null) {
//			System.err.println("Attachments getFileIdentifier: fileIdentifier null");
//		}
		return fileIdentifier;
	}
	
	//=== NONCE ======================================================
	
	public final static void addNonce( RandomAccessFile f, byte[] nonce) 
			throws IOException { 
		
		if( nonce == null ) {
			throw new IllegalArgumentException ("Nonce null");
		}		
		addBytes(f, nonce);
	}
	public final static byte[] addNonce(byte[] cipherBytes, byte[] nonce) {
		
		if(cipherBytes == null || nonce == null) {
			System.err.println("Attachments: addNonce failed: "
					+ "cipherBytes or nonce null");
			System.exit(1);
		}
		
		byte[] result = new byte[cipherBytes.length + nonce.length];
		System.arraycopy(cipherBytes,  0,  result,  0,  cipherBytes.length);
		System.arraycopy(nonce,  0,  
				result, cipherBytes.length, nonce.length);
		
		Zeroizer.zero(cipherBytes);
		return result;		
	}
	
	public final static byte[] getAndCutNonce( RandomAccessFile f, boolean truncate) 
			throws IOException { 
		//int cryptIvLength = CipherStuff.getCipherAlgo().getBlockSize();
		if(f.length() < NONCE_SIZE ) {
			throw new IllegalArgumentException("file is too short: " + f.toString() );
		}
		return  getLastBytes(f, NONCE_SIZE, truncate);
	}
	
	
	public final static byte[] generateNonce() {
		return new RandomStuff().createRandomBytes( NONCE_SIZE);//CipherStuff.getCipherAlgo().getBlockSize() );

	}
	
	public final static void setNonce(byte[] _nonce
			) { // required in PswDialogBase
		if (_nonce == null) {
			throw new IllegalArgumentException("Nonce to set null");
		}
		nonce = _nonce;
	}	
	public final static byte[] getNonce() {
		if (nonce == null) {
			//System.err.println("Attachments getCryptIV: cryptIV null");
		}
		return nonce;
	}
	public final static byte[] calculateNonce( byte[] input ) {
		//int cryptIvLength = CipherStuff.getCipherAlgo().getBlockSize();
		
		byte[] result = new byte[NONCE_SIZE];
		
		if (input.length < NONCE_SIZE) {
			System.err.println("Attachments calculateNonce: input too short");
			return null;
		} else {
			System.arraycopy(input, input.length - NONCE_SIZE, result, 0, result.length);
			return result;
		}
	}
	
	public final static byte[] cutNonce(byte[] input) {	
				
		if(input.length < NONCE_SIZE ) {
			System.err.println("input is too short to contain Nonce. ");
			return null;
		} 
		byte[] result = new byte[input.length - NONCE_SIZE];
		System.arraycopy(input, 0, result, 0, result.length);
		Zeroizer.zero(input);
		
		return result;
	}
	
	//===================================================
	// Getter & Setter & Generators
	
	public final static void generateAndSetProgramRandomBytes() {
		programRandomBytes = new RandomStuff().createRandomBytes( Attachments.getProgramRandomBytesSize() );		
	}
	public final static void setProgramRandomBytes( byte[] _programRandomBytes) {
		programRandomBytes = _programRandomBytes;
	}
	public final static byte[] getProgramRandomBytes() {
		if (programRandomBytes == null) {
			System.err.println("Attachments getProgramRandomBytes: programRandomBytes null");
		}
		return programRandomBytes;
	}

	
	public final static int getProgramRandomBytesSize() {
		return PROGRAM_RANDOM_BYTES_SIZE;
	}
	public final static int getPswIdentifierSize() {
		return PSW_IDENTIFIER_SIZE;
	}
	public final static int getFileIdentifierSize() {
		return FILE_IDENTIFIER_SIZE;
	}
	public final static int getNonceSize() {
		return NONCE_SIZE;
	}
	public static byte[] attachBytes(byte[] sourceBytes, byte[] bytesToAttach) {
		
		if(sourceBytes == null || bytesToAttach == null) {
			System.err.println("Attachments: attachedBytes failed");
			System.exit(1);
		}
		
		byte[] result = new byte[sourceBytes.length + bytesToAttach.length];
		System.arraycopy(sourceBytes,  0,  result,  0,  sourceBytes.length);
		System.arraycopy(bytesToAttach,  0,  
				result, sourceBytes.length, bytesToAttach.length);
		
		Zeroizer.zero(sourceBytes);
		return result;	
	}	
	public final static void addSalt( RandomAccessFile f, byte[] attachedSalt) 
			throws IOException { 
		
		if( attachedSalt == null ) {
			throw new IllegalArgumentException ("Attached Salt null");
		}		
		addBytes(f, attachedSalt);
	}
	public final static void cutSalt( RandomAccessFile f) 
			throws IOException { 
		//int cryptIvLength = CipherStuff.getCipherAlgo().getBlockSize();
		
		if(f.length() < PROGRAM_RANDOM_BYTES_SIZE ) {
			throw new IllegalArgumentException("file is too short: " + f.toString() );
		}
		cutLastBytes(f, PROGRAM_RANDOM_BYTES_SIZE);
	}
	public final static byte[] cutSalt(byte[] input) {	
		
		if(input.length < PROGRAM_RANDOM_BYTES_SIZE ) {
			System.err.println("input is too short to contain the salt. ");
			return null;
		} 
		byte[] result = new byte[input.length - PROGRAM_RANDOM_BYTES_SIZE];
		System.arraycopy(input, 0, result, 0, result.length);
		Zeroizer.zero(input);
		
		return result;
	}
	public final static byte[] getSalt(byte[] cipherBytes) { 
		byte[] result = new byte[PROGRAM_RANDOM_BYTES_SIZE];
		System.arraycopy(cipherBytes,  0,  result,  0,  result.length);
		return result;
	}
	public final static byte[] getAndCutSalt( RandomAccessFile f, boolean truncate) 
			throws IOException { 
		if(f.length() < PROGRAM_RANDOM_BYTES_SIZE ) {
			throw new IllegalArgumentException("file is too short: " + f.toString() );
		}
		return  getLastBytes(f, PROGRAM_RANDOM_BYTES_SIZE, truncate);
	}
	public final static byte[] getEndBytesOfFile( String fileName, int resultLen) 
			throws IOException { 
		
		RandomAccessFile f = new RandomAccessFile( fileName, "r" );
		
		if(f.length() < resultLen) {
			f.close();
			throw new IllegalArgumentException("file is too short: " + f.toString() );
		}
		byte[] result = getLastBytes(f, resultLen, false);
		f.close();
		return result;
	}
}
import java.awt.EventQueue;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.*;
import javax.swing.JOptionPane;
import cologne.eck.peafactory.crypto.CipherStuff;
import cologne.eck.peafactory.crypto.HashStuff;
import cologne.eck.peafactory.crypto.KeyDerivation;
import cologne.eck.peafactory.crypto.RandomStuff;
import cologne.eck.peafactory.crypto.kdf.CatenaKDF;
import cologne.eck.peafactory.gui.MainView;
import cologne.eck.peafactory.gui.ProjectSelection;
import cologne.eck.peafactory.peagen.DataType;
import cologne.eck.peafactory.peagen.JarStuff;
import cologne.eck.peafactory.peas.PswDialogBase;
import cologne.eck.peafactory.peas.file_pea.FileType;
import cologne.eck.peafactory.peas.gui.PswDialogView;
import cologne.eck.peafactory.tools.Attachments;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SkeinDigest;
import org.bouncycastle.crypto.engines.TwofishEngine;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	public final static void createFile() {

		//=====================
		// initialize (order!)
		//
	    if (HashStuff.getHashAlgo() == null) {
	    	HashStuff.setHashAlgo( DEFAULT_HASH );// default
	    }
	    if (CipherStuff.isBound() == true){
	    	Attachments.generateAndSetProgramRandomBytes();
	    	Attachments.generateAndSetFileIdentifier();	
			KeyDerivation.setSalt(Attachments.getProgramRandomBytes() );
	    } else {
	    	// use default values for fileIdentifier and programRandomBytes
	    	// calculate default salt and update via setAttachedSalt: 
	    	// xor the salt with attachedSalt (will be attached to the content)
	    	Attachments.setFileIdentifier("PFACTORY".getBytes(UTF_8)); // 8 byte
	    	// 129 byte string to initialize the salt: 
	    	String fixedSaltString = "PEAFACTORY - PRODUCTION OF PASSWORD ENCRYPTED ARCHIVES - FIXED STRING USED TO INITIALIZE THE SALT FOR THE KEY DERIVATION FUNCTION";
	    	String saltString = fixedSaltString.substring(0, KeyDerivation.getSaltSize() );
	    	Attachments.setProgramRandomBytes(saltString.getBytes(UTF_8));
			KeyDerivation.setSalt(saltString.getBytes(UTF_8));

		    byte[] attachedSalt = new RandomStuff().createRandomBytes(Attachments.getProgramRandomBytesSize());
		    KeyDerivation.setAttachedAndUpdateSalt(attachedSalt);
	    }
	    
	    // DEFAULT CIPHER ALGO: 
	    if (CipherStuff.getCipherAlgo() == null) {
	    	CipherStuff.setCipherAlgo( DEFAULT_CIPHER );
	    }
	    // DEFAULT KEY DERIVATION FUNCTION
	    if (KeyDerivation.getKdf() == null) {
	    	KeyDerivation.setKdf( DEFAULT_KDF );
	    }

		//======================================
		// get password from pswField and check:
		//
		char[] pswInputChars = MainView.getPsw();	
		MainView.resetPsw();
		char[] pswInputChars2 = MainView.getPsw2();
		MainView.resetPsw2();
		
		if ( ( pswInputChars2.length == 0)  && ( pswInputChars.length != 0)) {
			JOptionPane.showMessageDialog(null, languageBundle.getString("password2_is_null"), null, JOptionPane.PLAIN_MESSAGE);
			return;
		} else if (pswInputChars.length != pswInputChars2.length){
			JOptionPane.showMessageDialog(null, languageBundle.getString("different_password"), null, JOptionPane.PLAIN_MESSAGE);
			return;
		}
		boolean pswEqual = true;
		// prevent timing attacks: 
		for (int i = 0; i < pswInputChars.length; i++) {
			if (pswInputChars[i] != pswInputChars2[i]) {
				pswEqual = false;
			}
		}		
		if (pswEqual == false) {
			JOptionPane.showMessageDialog(null, languageBundle.getString("different_password"), null, JOptionPane.PLAIN_MESSAGE);
			return;
		}

		
		int inputLength = pswInputChars.length;

		Arrays.fill(pswInputChars2, '\0');		
		// Warning for insecure password
		if ((inputLength == 0) && (MainView.isBlankPea() == false)) {
			// allow null-passwords with warning
			JOptionPane.showMessageDialog(frame,
					languageBundle.getString("no_password_warning"),
				"Warning",
				JOptionPane.WARNING_MESSAGE);
			pswInputChars = "no password".toCharArray();

		} else if (inputLength < 12 && MainView.isBlankPea() == false) {
			JOptionPane.showMessageDialog(null, languageBundle.getString("short_password_warning"), null, JOptionPane.PLAIN_MESSAGE);
		}

		//=============
		//  derive key: 
		//	
		byte[] keyMaterial = null;
		
		if (MainView.isBlankPea() == false) {// if true: no need to derive key
			keyMaterial = PswDialogBase.deriveKeyFromPsw(pswInputChars);
			
			if (keyMaterial == null) {
				JOptionPane.showMessageDialog(frame,
						languageBundle.getString("program_bug")
						+ ": \n  (keyMaterial is null)",
					"Error",
					JOptionPane.ERROR_MESSAGE);
				return;
			}		
		}
		MainView.progressBar.setValue(35);
		MainView.progressBar.paint(MainView.progressBar.getGraphics());
			
		//=================
		// generate jar file:
		//
		//long start = System.currentTimeMillis(); // Startpunkt

		new JarStuff().generateJarFile(keyMaterial);
		
		//System.out.println("generateJarFile - Zeit in ms: \n" + (System.currentTimeMillis() - start));//Messpunkt
		
		String fileOrDirectory = (MainView.getOpenedFileName() == null) ? languageBundle.getString("directory") : languageBundle.getString("file");
		if (MainView.isBlankPea() == true) {
			fileOrDirectory = languageBundle.getString("directory"); // for all peas
		}
		if (DataType.getCurrentType() instanceof FileType) {