@SuppressWarnings("rawtypes")
public class CrococryptFile implements CbIEncrypt, CbIDecrypt, SimpleCallback{
	static{
		UICenter.INIT();
		InitCrypto.INIT();
	}
	
	public static void main(String[] args) throws Exception{
		if(args == null || args.length == 0){
			launch(null);
		}
		else{
			launch(ConsoleOptions.parse(args));
		}
	}
	
	public static final void launch(CrococryptParameters params){
		boolean jceok = true;
		try {
			if(Cipher.getMaxAllowedKeyLength("AES") < 256) jceok = false;
		} catch (Exception e) {
			jceok = false;
			e.printStackTrace();
		}
		if(!jceok){
			CPrint.error(_T.Launcher_jcepolicyerr);
			if(UICenter.isGUI()){
				PageLauncher.launch(new JCEPolicyError());
			}
			return;
		}
		
		if(System.console() != null && params == null)
			System.out.println("You can use the commandline interface by starting with -c");
		
		try{
			new CrococryptFile(params).main();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	

	public static String LASTERROR = null;
	
	private boolean check_forcedec = true;
	private boolean check_forcedeccloak = true;
	private boolean check_forceparams = true;
	private CrococryptParameters inputparams = null;
	private HashMap<SuitePARAM, Object> operationParams = new HashMap<>();
	private SuitePARAM[] reqparams = null;
	private int reqparams_index = 0;
	private Object currentSource;
	
	public CrococryptFile(CrococryptParameters params){
		this.inputparams = params;
		
		if(params != null && params.forceconsole)
			UICenter.setCUI();
	}
	
	private enum STATE{
		none,

		loadparams,
		cloak4dec,
		dec4enc,
		operationparam,
		decrypt,
		decryptfinishoperation,
		encrypt,
		encryptfinishoperation,
	}
	
	private STATE cur = STATE.none;
	private SuccessCallback successcb = new SuccessCallback() {
		@Override public void callbackValue(Object source, SUCCESS ret) {
			CrococryptFile.this.callbackValue(source, ret);
		}
	};

	@SuppressWarnings("unchecked")
	public void main(){
		if(UICenter.isCUI() && (inputparams == null || inputparams.destination == null || inputparams.filesanddirs == null || inputparams.filesanddirs.size() == 0)){
			CPrint.line("Invalid or insufficient options. Possible commandline options are:\n");
			CPrint.line("java -jar crococryptfile.jar [OPTIONS] [files & directories]");
			for(ConsoleOptions_Params param: ConsoleOptions_Params.values())
				CPrint.line(" -" + param.name() + ConsoleOptions.description.get(param));
			CPrint.line(" files & dirs\tSingle .croco-archive (decrypt)|files and directories (encrypt)");
			CPrint.line("\nReleased under the GPLv3 by Frank Hissen (www.frankhissen.de)");
			return;
		}
		
		if(check_forceparams && (inputparams == null || inputparams.filesanddirs == null || inputparams.filesanddirs.size() == 0)){
			check_forceparams = false;

			if(UICenter.isGUI()){
				cur = STATE.loadparams;
				if(inputparams != null && inputparams.cloakedfile)
					CrocoparamsDialog.requestParams(this, inputparams, _T.FileSelection_title4cloak.val());
				else
					CrocoparamsDialog.requestParams(this, inputparams);
			}
			else{
				UICenter.message("Invalid or insufficient options");
			}
		}
		else if(check_forcedec && !inputparams.decmode && inputparams.filesanddirs.size() == 1 && inputparams.filesanddirs.get(0).isFile()
				&& inputparams.filesanddirs.get(0).getName().toLowerCase().endsWith(SUITES.FILEEXTENSIONwDOT)){
			check_forcedec = false;

			if(UICenter.isGUI()){
				cur = STATE.dec4enc;
				SimpleDialogs.questionYesNo(_T.FileSelection_dec4enc.val(), successcb);
			}
			else{
				main();
			}
		}
		else if(check_forcedeccloak && !inputparams.cloakedfile && !inputparams.decmode && inputparams.filesanddirs.size() == 1 && inputparams.filesanddirs.get(0).isFile()
				&& inputparams.filesanddirs.get(0).getName().indexOf(".") < 0){
			check_forcedeccloak = false;

			if(UICenter.isGUI()){
				cur = STATE.cloak4dec;
				SimpleDialogs.questionYesNo(_T.FileSelection_dec4cloak.val(), successcb);
			}
			else{
				main();
			}
		}
		else{
			if(inputparams.decmode){
				if(UICenter.isGUI()){
					File startDest = inputparams.destination;
					if(startDest == null){
						File file = inputparams.filesanddirs.get(0);
						String tmp = file.getName();
						if(tmp.indexOf(".") > 0) tmp = tmp.substring(0, tmp.lastIndexOf("."));
						else tmp = tmp + " - archive";
						startDest = new File(file.getParentFile(), tmp);
						if(startDest.exists() && startDest.isFile()) startDest = new File(file.getParentFile(), tmp + " - archive");
					}

					Decrypt dec = new Decrypt(this);
					dec.setFolder(startDest);
					PageLauncher.launch(dec);
				}
				else{
					try {
						if(inputparams.cloakedfile){
							inputparams.suite = SUITES.PBECLOAKED_AESTWO;
						}
						else{
							File croco = inputparams.filesanddirs.get(0);
							inputparams.suite = SUITES.read(croco);
						}
						if(inputparams.suite == null){
							UICenter.message("Input parameters are invalid (no valid provider or file)");
						}
						else{
							prepareDecryptParams(inputparams.suite);
							setRawParams(inputparams.rawcreds);
							retrieveOperationParams();
						}
					} catch (Exception e) {
						e.printStackTrace();
						UICenter.message("Input parameters are invalid");
					}
				}
			}
			else{
				if(UICenter.isGUI()){
					File startDest = inputparams.destination;
					if(startDest == null && inputparams.singlesource){
						startDest = inputparams.filesanddirs.get(0);
						if(OSFolders.isRoot(startDest)) startDest = new File(startDest, _T.EncryptWindow_EncryptedFile + BasicFileinfo.FILEEXTENSIONwDOT);
						else if(OSFolders.isRoot(startDest.getParentFile())) startDest = new File(startDest.getParentFile(), _T.EncryptWindow_EncryptedFile + BasicFileinfo.FILEEXTENSIONwDOT);
						else startDest = new File(startDest.getParentFile(), startDest.getParentFile().getName() + BasicFileinfo.FILEEXTENSIONwDOT);
					}
					Encrypt enc = new Encrypt(this, inputparams, startDest);
					PageLauncher.launch(enc);
				}
				else{
					if(inputparams.suite != null){
						prepareEncryptParams(inputparams.suite);
						setRawParams(inputparams.rawcreds);
						retrieveOperationParams();
					}
					else{
						UICenter.message("No provider specified, valid providers are: " + SUITES.providerListAsLine());
					}
				}
			}
		}
	}
	
	
	@Override
	public void callbackValue(Object source, Object ret) {
		try {
			switch (cur) {
			case loadparams:
				inputparams = (CrococryptParameters) ret;
				cur = STATE.none;
				main();
				break;
				
			case cloak4dec:
				if(ret == SUCCESS.TRUE) inputparams.cloakedfile = true;
			case dec4enc:
				if(ret == SUCCESS.TRUE)
					inputparams.decmode = true;
				else
					inputparams.decmode = false;
				cur = STATE.none;
				main();
				break;
				
			case decrypt:
				cur = STATE.none;
				SUCCESS decsucc = (SUCCESS) ret;
				
				switch (decsucc) {
				case TRUE:
					if(UICenter.isGUI()){
						cur = STATE.decryptfinishoperation;
						SimpleDialogs.message(_T.DecryptWindow_success, successcb);
					}
					else{
						UICenter.message(_T.DecryptWindow_success);
					}
					Page.exitPage(currentSource);

					break;

				case FALSE:
					if(LASTERROR != null){
						UICenter.message(_T.DecryptWindow_failedspecific + "\n" + LASTERROR);
						LASTERROR = null;
					}
					else{
						UICenter.message(_T.DecryptWindow_failedgeneral);
					}
					break;

				case CANCEL:
					UICenter.message(_T.DecryptWindow_cancel);
					Page.exitPage(currentSource);

					break;
				}
				
				break;
				
			case decryptfinishoperation:
				try {
					Desktop.getDesktop().browse(inputparams.destination.toURI());
				} catch (Exception e2) {}
				break;
				
			case encrypt:
				cur = STATE.none;
				SUCCESS encsucc = (SUCCESS) ret;
				
				switch (encsucc) {
				case TRUE:
					UICenter.message(_T.EncryptWindow_success);
					break;

				case FALSE:
					if(LASTERROR != null){
						UICenter.message(_T.EncryptWindow_failedspecific + "\n" + LASTERROR);
						LASTERROR = null;
					}
					else{
						UICenter.message(_T.EncryptWindow_failedgeneral);
					}
					break;

				case CANCEL:
					UICenter.message(_T.EncryptWindow_cancel);
					break;
				}
				
				Page.exitPage(currentSource);

				break;
				
			case encryptfinishoperation:
				break;
				
			case operationparam:
				setOperationParam(source, ret);
				retrieveOperationParams();
				break;

			default:
				UICenter.message("Internal error: unknown callback response - " + cur);
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-98);
		}
	}

	
	@Override
	public void callbackDecrypt(Object source, File destinationFolder) {
		currentSource = source;
		inputparams.destination = destinationFolder;
		File croco = inputparams.filesanddirs.get(0);
		
		if(!inputparams.cloakedfile){
			inputparams.suite = SUITES.read(croco);
			if(inputparams.suite == null){
				UICenter.message(_T.DecryptWindow_unknownfile);
				return;
			}
		}
		else{
			inputparams.suite = SUITES.PBECLOAKED_AESTWO;
		}
		
		prepareDecryptParams(inputparams.suite);
		retrieveOperationParams();
	}

	@Override
	public void callbackEncrypt(Object source, File destination, SUITES provider) {
		if(provider == null){
			UICenter.message("Unknown Operation");
			return;
		}
		
		currentSource = source;
		inputparams.destination = destination;
		inputparams.suite = provider;
		
		prepareEncryptParams(provider);
		retrieveOperationParams();
	}
	
	@Override
	public boolean isDecryptRunning(Object source) {
		if(source == currentSource && cur == STATE.decrypt)
			return true;
		return false;
	}
	
	@Override
	public boolean isEncryptRunning(Object source) {
		if(source == currentSource && cur == STATE.encrypt)
			return true;
		return false;
	}
	
	private void prepareEncryptParams(SUITES suite){
		reqparams = suite.getEncryptParameters();
		reqparams_index = 0;
		operationParams.clear();
	}
	
	private void prepareDecryptParams(SUITES suite){
		reqparams = suite.getDecryptParameters();
		reqparams_index = 0;
		operationParams.clear();
	}
	
	
	@SuppressWarnings("unchecked")
	private void retrieveOperationParams(){
		if(reqparams == null || reqparams.length <= reqparams_index){
			if(inputparams.decmode){
				cur = STATE.decrypt;
				doDecrypt();
			}
			else{
				cur = STATE.encrypt;
				doEncrypt();
			}
		}
		else{
			cur = STATE.operationparam;
			SuitePARAM theparam = reqparams[reqparams_index];
			reqparams_index++;
			
			if(inputparams.decmode){
				switch (theparam) {
				case password:
					if(UICenter.isGUI())
						new PasswordInputdialog(currentSource).main(this);
					else
						new PasswordInputConsole().main(this);
					break;
					
				case pgp_dec:
					if(UICenter.isGUI())
						PGP_PrivatekeyDialog.requestParams(currentSource, this);
					break;

				default:
					UICenter.message("Internal error: unknown parameter");
				}
			}
			else{
				switch (theparam) {
				case password:
					if(UICenter.isGUI())
						
						new PasswordCreatedialogWithOptions(currentSource).main(this);
					else
						new PasswordCreateConsole().main(this);
					break;
					
				case capi_alias:
					if(UICenter.isGUI()){
						CAPI_DNList dnlist = new CAPI_DNList(this);
						PageLauncher.launch(new Options(currentSource), dnlist);
					}
					break;
					
				case pgp_enc:
					if(UICenter.isGUI()){
						PageLauncher.launch(new Options(currentSource), new PGP_Keylist(this));
					}
					break;

				default:
					UICenter.message("Internal error: unknown parameter");
				}
			}
		}
	}
	
	
	@SuppressWarnings("unchecked")
	private void setOperationParam(Object source, Object value){
		if(source == null || value == null) return;
		
		Class c = null;
		if(source instanceof Class) c = (Class) source;
		else c = source.getClass();
		
		if(c == PasswordCreatedialog.class || c == PasswordCreatedialogWithOptions.class || c == PasswordCreateConsole.class
				|| c == PasswordInputdialog.class || c == PasswordInputConsole.class){
			if(value instanceof HashMap){
				HashMap hm = (HashMap) value;
				if(hm.isEmpty()) return;
				
				if(hm.keySet().iterator().next() instanceof SuitePARAM){
					operationParams.putAll(hm);
				}
			}
			else{
				operationParams.put(SuitePARAM.password, value);
			}
		}
		else if(c == CAPI_DNList.class){
			operationParams.put(SuitePARAM.capi_alias, value);
		}
		else if(value instanceof HashMap){
			HashMap hm = (HashMap) value;
			if(hm.isEmpty()) return;
			
			if(hm.keySet().iterator().next() instanceof SuitePARAM){
				operationParams.putAll(hm);
			}
		}
	}
	
	private void setRawParams(ArrayList<String> raw){
		if(reqparams == null || reqparams.length == 0 || raw == null || raw.size() < reqparams.length) return;

		for(reqparams_index = 0; reqparams_index < reqparams.length; reqparams_index++){
			SuitePARAM tmp = reqparams[reqparams_index];
			switch (tmp) {
			case capi_alias:
				operationParams.put(tmp, raw.get(reqparams_index));
				continue;

			case password:
				UICenter.message("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
						"You are providing a password as commandline option, this is NOT RECOMMEND" +
						"\nsince your password might be easily gathered." +
						"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
				char[] pw = raw.get(reqparams_index).toCharArray();
				if(pw.length < CryptoCodes.STANDARD_PBKDF2_PWLEN){
					CPrint.line(_T.PasswordEncrypt_len.msg(CryptoCodes.STANDARD_PBKDF2_PWLEN));
					continue;
				}
				operationParams.put(tmp, pw);
				continue;
				
			case pgp_enc:
				if(raw.size() < 2) {
					System.err.println("Not enough parameters specified");
					return;
				}
				operationParams.put(SuitePARAM.pgp_pubkeyfile, raw.get(0));
				
				ArrayList<Long> kids = new ArrayList<>();
				for(int i=1; i<raw.size(); i++){
					String rawid = raw.get(i).trim();
					if(rawid.length() < 16){
						System.err.println("Only long keyid format supported");
						continue;
					}

					Long l = 0L;
					if(rawid.length() == 16){
						try {
							l = ByteUtils.bytesToLong(Hex.decode(rawid));
						} catch (Exception e) {}
					}
					else{
						try {
							l = Long.parseLong(rawid);
						} catch (Exception e) {}
					}

					if(l != 0) {
						kids.add(l);
					}
					else{
						System.err.println("keyid " + rawid + " could not be read");
					}
				}
				if(kids.size() == 0) return;
				
				operationParams.put(SuitePARAM.pgp_keyidlist, kids);
				continue;

			case pgp_dec:
				if(raw.size() < 2) {
					System.err.println("Not enough parameters specified");
					return;
				}
				
				UICenter.message("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
						"You are providing a password as commandline option, this is NOT RECOMMEND" +
						"\nsince your password might be easily gathered." +
						"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
				char[] pwpgp = raw.get(1).toCharArray();

				operationParams.put(SuitePARAM.pgp_privkeyfile, raw.get(0));
				operationParams.put(SuitePARAM.password, pwpgp);
				continue;

			default:
				UICenter.message("unknown credential");
				break;
			}
		}
	}
	
	private void doEncrypt(){
		final StatusUpdate stat;
		if(UICenter.isGUI()){
			stat = new ProgressDoubleWindow(((Page)currentSource).getPageLauncher().getWindow()).prepare();
		}
		else if(inputparams.verboseconsole){
			stat = new StatusConsole();
		}
		else{
			stat = null;
		}

		if(stat != null){
			stat.start();
			stat.receiveMessageSummary(_T.Password_PBEinProgress.val());
		}
		
		Suite.getInitializedInstanceAsync(inputparams.suite, SuiteMODE.ENCRYPT, operationParams, stat, new SuiteReceiver() {
			@Override
			public void receiveInitializedInstance(Suite suite) {
				if(suite == null) {
					UICenter.message("Fatal error while initializing ENCRYPTION.");
					System.exit(-12);
				}
				
				StatusUpdate status = suite.getStatus();
				if(status == null || status.isActive())
					new CrocoFilewriter(successcb, suite, inputparams.filesanddirs.toArray(new File[]{}), inputparams.destination).execute(stat);
				else{
					try {
						if(successcb != null) successcb.callbackValue(this, SUCCESS.CANCEL);
						if(stat != null) stat.finished();
						if(suite != null) suite.deinit();
					} catch (Exception e) {}
				}
			}
		});
	}
	
	private void doDecrypt(){
		final StatusUpdate stat;
		if(UICenter.isGUI()){
			stat = new ProgressWindow(((Page)currentSource).getPageLauncher().getWindow()).prepare();
		}
		else if(inputparams.verboseconsole){
			stat = new StatusConsole();
		}
		else{
			stat = null;
		}

		if(stat != null){
			stat.start();
			stat.receiveMessageSummary(_T.Password_PBEinProgress.val());
		}

		Suite.getInitializedInstanceAsync(inputparams.suite, SuiteMODE.DECRYPT, operationParams, stat, new SuiteReceiver() {
			@Override
			public void receiveInitializedInstance(Suite suite) {
				if(suite == null) {
					UICenter.message("Fatal error while initializing DECRYPTION.");
					System.exit(-12);
				}

				StatusUpdate status = suite.getStatus();
				if(status == null || status.isActive())
					new CrocoFilereader(successcb, suite, inputparams.filesanddirs.get(0), inputparams.destination).execute(stat);
				else{
					try {
						if(successcb != null) successcb.callbackValue(this, SUCCESS.CANCEL);
						if(stat != null) stat.finished();
						if(suite != null) suite.deinit();
					} catch (Exception e) {}
				}
			}
		});
	}
}
 public abstract class Suite {
	private SuiteMODE mode;
	private StatusUpdate status;
	private boolean initialized = false;
	
	protected Suite(){}
	
	public final void init(SuiteMODE mode, HashMap<SuitePARAM, Object> params, StatusUpdate status) throws IllegalArgumentException{
		if(mode == null) throw new IllegalArgumentException("no mode specified");
		
		this.status = status;
		_init(mode, params);
		
		this.mode = mode;
		initialized = true;
	}
	
	public SuiteMODE getMode(){
		return mode;
	}
	
	public final void writeTo(OutputStream out) throws IllegalStateException{
		if(!initialized) throw new IllegalStateException("not initialized");
		if(getMode() != SuiteMODE.ENCRYPT) throw new IllegalStateException("wrong mode");
		
		_writeTo(out);
	}
	
	public final void readFrom(InputStream is) throws IllegalStateException{
		if(!initialized) throw new IllegalStateException("not initialized");
		if(getMode() != SuiteMODE.DECRYPT) throw new IllegalStateException("wrong mode");
		
		_readFrom(is);
	}

	abstract protected void _init(SuiteMODE mode, HashMap<SuitePARAM, Object> params) throws IllegalArgumentException;
	abstract protected void _writeTo(OutputStream out) throws IllegalStateException;
	abstract protected void _readFrom(InputStream is) throws IllegalStateException;
	abstract public CipherMain getCipher();
	abstract public byte[] getAttributeIV();
	abstract public int headerLength();
	abstract public void deinit();
	
	public final byte[] getAlteredIV(int add){
		add = add % CryptoCodes.STANDARD_IVSIZE;
		byte[] buf = Arrays.copyOf(getAttributeIV(), CryptoCodes.STANDARD_IVSIZE);
		buf[add]++;
		return buf;
	}
	
	private int len = -1;
	public final int suiteLength(){
		if(len < 0){
			len = headerLength();
			if(!(this instanceof PBECloaked_AES2F_Main)) len += SUITES.MAGICNUMBER_LENGTH;
		}
		return len;
	}
	
	public final StatusUpdate getStatus(){
		return status;
	}
	
	
	public static final Suite getInstance(SUITES suite){
		try {
			return (Suite)(SUITES.classFromSuites(suite).newInstance());
		} catch (Exception e) {
			System.err.println("getInstance: " + e.getLocalizedMessage());
			return null;	
		}
	}

	public static final Suite getInitializedInstance(SUITES suite, SuiteMODE mode, HashMap<SuitePARAM, Object> params, StatusUpdate status){
		try {
			Suite instance = (Suite)(SUITES.classFromSuites(suite).newInstance());
			instance.init(mode, params, status);
			return instance;
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("getInitializedInstance: " + e.getLocalizedMessage());
			return null;	
		}
	}
	
	
	public static final void getInitializedInstanceAsync(final SUITES suite, final SuiteMODE mode,
			final HashMap<SuitePARAM, Object> params, final StatusUpdate status, final SuiteReceiver rec){
		if(rec == null){
			System.err.println("SuiteReceiver == null");
			return;
		}

		if(suite == null){
			System.err.println("Requested SUITES == null");
			rec.receiveInitializedInstance(null);
			return;
		}
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Suite instance = (Suite)(SUITES.classFromSuites(suite).newInstance());
					instance.init(mode, params, status);
					rec.receiveInitializedInstance(instance);
				} catch (Exception e) {
					e.printStackTrace();
					System.err.println("getInitializedInstanceAsync: " + e.getLocalizedMessage());
					rec.receiveInitializedInstance(null);
				}
			}
		}).start();
	}
	
	
	public static final Suite getInitializedInstance(SUITES suite, SuiteMODE mode, SuitePARAM aparam, Object avalue){
		HashMap<SuitePARAM, Object> params = new HashMap<>();
		params.put(aparam, avalue);
		return getInitializedInstance(suite, mode, params, null);
	}
	

	public interface SuiteReceiver{
		public void receiveInitializedInstance(Suite suite);
	}
}
 public enum SuiteMODE {
	ENCRYPT,
	DECRYPT,
}
 public enum SuitePARAM {
	password,
	itcount,
	
	capi_alias,
	
	
	pgp_enc,
	pgp_dec,
	
	pgp_keyidlist,
	pgp_pubkeyfile,
	pgp_privkeyfile,
	
	
	;
}
 public enum _T {
	CAPIDNListWindow_nokeys,
	CAPIDNListWindow_title,
	CAPI_DNnotfound,
	CrocoFilereader_already,
	CrocoFilereader_decrypting,
	CrocoFilewriter_encrypting,
	DecryptWindow_already,
	DecryptWindow_cancel,
	DecryptWindow_destination,
	DecryptWindow_failedgeneral,
	DecryptWindow_failedspecific,
	DecryptWindow_folderisfile,
	DecryptWindow_folderreadonly,
	DecryptWindow_invalid,
	DecryptWindow_nofile,
	DecryptWindow_success,
	DecryptWindow_text,
	DecryptWindow_title,
	DecryptWindow_unknownfile,
	DecryptWindow_wrongversion,
	Decrypt_Start,
	EncryptListChoose,
	EncryptListChooseMulti,
	EncryptWindow_EncryptedFile,
	EncryptWindow_Multidir,
	EncryptWindow_Multifile,
	EncryptWindow_MultifileMultidir,
	EncryptWindow_MultifileSingledir,
	EncryptWindow_Singledir,
	EncryptWindow_Singlefile,
	EncryptWindow_SinglefileMultidir,
	EncryptWindow_SinglefileSingledir,
	EncryptWindow_already,
	EncryptWindow_alreadydir,
	EncryptWindow_cancel,
	EncryptWindow_createdir,
	EncryptWindow_destination,
	EncryptWindow_failedgeneral,
	EncryptWindow_failedspecific,
	EncryptWindow_folderreadonly,
	EncryptWindow_multisources,
	EncryptWindow_success,
	EncryptWindow_title,
	FileSelection_dec4cloak,
	FileSelection_dec4enc,
	FileSelection_selectbutton,
	FileSelection_title,
	FileSelection_title4cloak,
	FilepathFailure,
	General_cancel,
	General_done,
	General_empty,
	General_none,
	General_open,
	General_password,
	General_quit,
	General_synchronize,
	JCEPolicyError_title,
	JCEPolicyError_text,
	Launcher_jcepolicyerr,
	Launcher_reallyquit,
	PasswordDecrypt_title,
	PasswordEncrypt_label,
	PasswordEncrypt_len,
	PasswordEncrypt_nomatch,
	PasswordEncrypt_retype,
	PasswordEncrypt_itcount,
	PasswordEncrypt_itcountdescr,
	PasswordEncrypt_title,
	Password_caps,
	Password_PBEinProgress,
	PGP_errorNokey,
	PGP_errorSeckeyfailed,
	PGP_nokeyfile,
	PGP_novalidprivkey,
	PGP_privkey_wrongfile,
	PGP_privkeypasstitle,
	PGP_selprivkey,
	PGP_selprivkeyTitle,
	PGP_selpubkey,
	PBKDF2Interstep_start,
	PBKDF2Interstep_found,
	PBKDF2Interstep_steplong,
	Suite_CAPI_RSAAES,
	Suite_PBE1_AES,
	Suite_PBE1_TWOFISH,
	Suite_PBE1_SERPENT,
	Suite_PBE1_CAMELLIA,
	Suite_PGP_AES,
	Suite_PBECLOAKED_AESTWO,
	Suite_PBECLOAKED1MB_AESTWO,

	;
	
	
	private static ResourceBundle rb;
	static{
		try {
			rb = ResourceBundle.getBundle(ResourceCenter.getTexts() + "text", ResourceCenter.getLocale());
		} catch (Exception e) {
			rb = ResourceBundle.getBundle(ResourceCenter.getTexts() + "text", Locale.ENGLISH);
		}
	}
	
	
	public String val(){
		return rb.getString(name());
	}
	
	public String msg(Object... obj){
		return MessageFormat.format(toString(), obj);
	}
	
	@Override
	public String toString() {
		return val();
	};
}
 public class CipherMain {
	private FSecretKeySpec[] keys;
	private BASECIPHER[] ciphers;
	
	public CipherMain(BASECIPHER cipher, FSecretKeySpec key) throws IllegalArgumentException {
		this(new BASECIPHER[]{cipher}, new FSecretKeySpec[]{key});
	}
	
	public CipherMain(BASECIPHER[] ciphers, FSecretKeySpec[] keys) throws IllegalArgumentException {
		if(ciphers == null || ciphers.length == 0 || keys == null || keys.length == 0 || keys.length != ciphers.length)
			throw new IllegalArgumentException("Key options not valid");

		this.keys = keys;
		this.ciphers = ciphers;
	}


	public OutputStream createOS_CBC_Pad(OutputStream os, byte[] iv){
		try {
			Cipher[] ciphs = generateCBCPadCiphers(false, iv);
			CipherOutputStream cos = new CipherOutputStream(os, ciphs[0]);
			for(int i=1; i<ciphs.length; i++){
				cos = new CipherOutputStream(cos, ciphs[i]);
			}
			
			return cos;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}
	
	
	public InputStream createIS_CBC_Pad(InputStream is, byte[] iv){
		try {
			Cipher[] ciphs = generateCBCPadCiphers(true, iv);
			CipherInputStream cis = new CipherInputStream(is, ciphs[0]);
			for(int i=1; i<ciphs.length; i++){
				cis = new CipherInputStream(cis, ciphs[i]);
			}
			
			return cis;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}
	

	public byte[] doEnc_ECB(byte[] in){
		try {
			Cipher[] ciphs = generateECBCiphers(false);
			byte[] ret = ciphs[0].doFinal(in);
			for(int i=1; i<ciphs.length; i++){
				ret = ciphs[i].doFinal(ret);
			}
			
			return ret;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}
	
	public byte[] doDec_ECB(byte[] in){
		try {
			Cipher[] ciphs = generateECBCiphers(true);
			
			if(ciphs.length == 1) return ciphs[0].doFinal(in);
			
			byte[] ret = ciphs[ciphs.length - 1].doFinal(in);
			for(int i=ciphs.length-2; i>=0; i--){
				ret = ciphs[i].doFinal(ret);
			}
			
			return ret;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}
	
	
	public void deinint(){
		for(FSecretKeySpec fs: keys)
			fs.wipe(true);
		
		keys = null;
		ciphers = null;
	}

	private Cipher[] generateECBCiphers(boolean decrypt) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException{
		Cipher[] ciphs = new Cipher[keys.length];
		for(int i=0; i<keys.length; i++){
			ciphs[i] = Cipher.getInstance(ciphers[i].getECB());
			if(decrypt) ciphs[i].init(Cipher.DECRYPT_MODE, keys[i]);
			else ciphs[i].init(Cipher.ENCRYPT_MODE, keys[i]);
		}
		return ciphs;
	}


	private Cipher[] generateCBCPadCiphers(boolean decrypt, byte[] iv)
			throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException{
		Cipher[] ciphs = new Cipher[keys.length];
		for(int i=0; i<keys.length; i++){
			ciphs[i] = Cipher.getInstance(ciphers[i].getCBCPad());
			if(decrypt) ciphs[i].init(Cipher.DECRYPT_MODE, keys[i], new IvParameterSpec(iv));
			else ciphs[i].init(Cipher.ENCRYPT_MODE, keys[i], new IvParameterSpec(iv));
		}
		return ciphs;
	}

	@Override
	protected void finalize() throws Throwable {
		deinint();
		super.finalize();
	}
	
	
	public static final CipherMain instance(BASECIPHER cipher, byte[] key){
		try {
			return new CipherMain(CryptoUtils.toArray(cipher), CryptoUtils.toArray(key));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}

	public static final CipherMain instance(BASECIPHER[] ciphers, byte[]... keys){
		try {
			return new CipherMain(ciphers, CryptoUtils.toArray(keys));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
}
 public class CryptoCodes {
	public static final String KEY_AES = "AES";
	public static final String KEY_RSA = "RSA";
	
	public static final String HMAC_SHA512 = "HmacSHA512";
	public static final String HMAC_WHIRL = "HmacWhirlpool";
	
	public static final String HASH_SHA512 = "SHA512";
	
	public static final int AES_KEYSIZE_BITS = 256;
	public static final int AES_KEYSIZE = AES_KEYSIZE_BITS / 8;

	public static final int STANDARD_BLOCKSIZE = 16;
	public static final int STANDARD_IVSIZE = STANDARD_BLOCKSIZE;
	public static final int STANDARD_SALTSIZE = 64;
	public static final int STANDARD_PBKDF2_ITERATIONS = 100000; 
	public static final int STANDARD_PBKDF2_PWLEN = 8; 


	private static HashMap<BASECIPHER, String> ecbCodes = new HashMap<>();
	private static HashMap<BASECIPHER, String> cbcpadCodes = new HashMap<>();
	private static HashMap<BASECIPHER, String> cbcnopadCodes = new HashMap<>();

	static{
		for(BASECIPHER b: BASECIPHER.values()){
			ecbCodes.put(b, b.getCipherCode() + "/ECB/NoPadding");
			cbcpadCodes.put(b, b.getCipherCode() + "/CBC/PKCS5Padding");
			cbcnopadCodes.put(b, b.getCipherCode() + "/CBC/NoPadding");
		}
	}

	public enum BASECIPHER{
		AES,
		TWOFISH ("Twofish"),
		SERPENT ("Serpent"),
		CAMELLIA ("Camellia"),
		
		;
		
		private String cipherCode = null;
		private BASECIPHER(){}
		private BASECIPHER(String cipherCode){
			this.cipherCode = cipherCode;
		}
		
		private String getCipherCode(){
			if(cipherCode != null)
				return cipherCode;
			
			return name();
		}
		
		public String getECB(){
			return ecbCodes.get(this);
		}
		
		public String getCBCPad(){
			return cbcpadCodes.get(this);
		}
		
		public String getCBCNopad(){
			return cbcnopadCodes.get(this);
		}
	}
}
 public enum BASECIPHER{
	AES,
	TWOFISH ("Twofish"),
	SERPENT ("Serpent"),
	CAMELLIA ("Camellia"),
		
	;
		
	private String cipherCode = null;
	private BASECIPHER(){}
	private BASECIPHER(String cipherCode){
		this.cipherCode = cipherCode;
	}
		
	private String getCipherCode(){
		if(cipherCode != null)
			return cipherCode;
			
		return name();
	}
		
	public String getECB(){
		return ecbCodes.get(this);
	}
		
	public String getCBCPad(){
		return cbcpadCodes.get(this);
	}
		
	public String getCBCNopad(){
		return cbcnopadCodes.get(this);
	}
}
 public class CryptoUtils {
	static{
		InitCrypto.INIT();
	}
	
	private static Random randio = new Random();
	static{
		randio.nextBoolean();
		long l = randio.nextLong();
		randio.setSeed(System.currentTimeMillis() ^ l);
	}

	public static final void kill(byte[] b){
		if(b == null) return;
		randio.nextBytes(b);
	}

	public static final void kill(byte[]... bs){
		if(bs == null) return;
		
		for(byte[]b: bs)
			kill(b);
	}

	public static final void kill(char[] b){
		if(b == null) return;
		for(int i=0; i<b.length; i++)
			b[i] = (char)randio.nextInt();
	}
	

	private static final SecureRandom sr = new KeygenUtils().makeSR();
	public static final byte[] randIv16(){
		byte[] ret = new byte[16];
		sr.nextBytes(ret);
		return ret;
	}

	public static final byte[] randIv(int size){
		byte[] ret = new byte[size];
		sr.nextBytes(ret);
		return ret;
	}
	
	
	public static final long makeSimpleId(){
		return UUID.randomUUID().getMostSignificantBits();
	}
	
	
	public static final String[] toArray(String... s){
		return s;
	}

	public static final BASECIPHER[] toArray(BASECIPHER... bc){
		return bc;
	}

	public static final FSecretKeySpec[] toArray(FSecretKeySpec... fs){
		return fs;
	}

	public static final FSecretKeySpec[] toArray(byte[]... rawkeys){
		if(rawkeys == null || rawkeys.length == 0) return null;
		
		FSecretKeySpec[] fs = new FSecretKeySpec[rawkeys.length];
		
		for(int i=0; i<rawkeys.length; i++){
			fs[i] = new FSecretKeySpec(rawkeys[i]);
		}
		
		return fs;
	}
}
 public class FSecretKeySpec extends SecretKeySpec{
	private static final long serialVersionUID = 1L;

	private byte[] k;
	private String alg;
	private ArrayList<byte[]> all = new ArrayList<>();
	
	public FSecretKeySpec(byte[] key, String algorithm) {
		super(CryptoUtils.randIv(key.length), algorithm);
		
		k = key;
		alg = algorithm;
	}
	
	public FSecretKeySpec(byte[] key) {
		this(key, CryptoCodes.KEY_AES);
	}
	
	@Override
	public boolean equals(Object obj) {
		return this == obj;
	}

	@Override
	public String getAlgorithm() {
		return alg;
	}

	@Override
	public byte[] getEncoded() {
		byte[] next = Arrays.copyOf(k, k.length);
		all.add(next);
		return next;
	}

	@Override
	public String getFormat() {
		return "RAW";
	}
	
	public void wipe(boolean key_inclusive){
		for(byte[] b: all)
			CryptoUtils.kill(b);
		all.clear();
		
		if(key_inclusive) CryptoUtils.kill(k);
	}

	public static final FSecretKeySpec make(byte[] key){
		return new FSecretKeySpec(key, CryptoCodes.KEY_AES);
	}
}
 public class KeygenUtils {
	private MessageDigest md;
	private boolean hasgui = false;

	{
		try {
			md = MessageDigest.getInstance(CryptoCodes.HASH_SHA512);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		try {
			hasgui = !GraphicsEnvironment.isHeadless();
		} catch (Throwable t) {
			System.err.println(t.getLocalizedMessage());
		}
	}
	
	public SecureRandom makeSR(){
		SecureRandom sr = new SecureRandom();
		
		sr.nextBoolean();
		byte[] b = additionalSeed();
		
		sr.setSeed(b);
		
		CryptoUtils.kill(b);
		
		return sr;
	}
	
	public KeyGenerator make(int sizeinbits) {
		try {
			KeyGenerator key_gen = KeyGenerator.getInstance(CryptoCodes.KEY_AES);
			key_gen.init(sizeinbits, makeSR());
			long max = (System.currentTimeMillis() % 11) + 1;
			
			
			for(int i=0; i < max; i++){
				key_gen.generateKey();
			}

			return key_gen;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public byte[] raw(int sizeinbits) {
		return make(sizeinbits).generateKey().getEncoded();
	}
	
	private final byte[] additionalSeed(){
		byte[] addpseudorandomness;
		addpseudorandomness = newdigestNwipe(Calendar.getInstance().toString().getBytes());
		
		ArrayList<Object> collector = new ArrayList<>();
		try {
			collector.add(System.nanoTime());
			collector.add((long)new Object().hashCode() * (long)new Object().hashCode());
			try {collector.add(new File(".").getFreeSpace());} catch (Exception e) {}
			if(hasgui) collector.add(MouseInfo.getPointerInfo().getLocation().x);
			if(hasgui) collector.add(MouseInfo.getPointerInfo().getLocation().y);
			try {collector.add(new File(".").getUsableSpace());} catch (Exception e) {}
			if(hasgui) {
				Color tmp = new Robot().getPixelColor(10 + ((int)System.nanoTime() % 2000), 10 + ((int)System.nanoTime() % 1000));
				collector.add(tmp.getRGB());
				collector.add(tmp.getTransparency());
				collector.add(tmp.hashCode());
				tmp = null;
			}
			collector.add((long)new Object().hashCode() * (long)new Object().hashCode());
			if(hasgui){
				Color tmp = new Robot().getPixelColor(MouseInfo.getPointerInfo().getLocation().x, MouseInfo.getPointerInfo().getLocation().y);
				collector.add(tmp.getRGB());
				collector.add(tmp.getTransparency());
				collector.add(tmp.hashCode());
				tmp = null;
			}
			collector.add(System.currentTimeMillis());
			try {collector.add(Runtime.getRuntime().availableProcessors());} catch (Exception e) {}
			collector.add((long)new Object().hashCode() * (long)new Object().hashCode());
			collector.add(Runtime.getRuntime().hashCode());
			collector.add((long)new Object().hashCode() * (long)new Object().hashCode());
		} catch (Exception e) {}
		addpseudorandomness = newdigestNwipe(Xor.xorWipe(addpseudorandomness, ByteUtils.copyTogetherNwipeO(collector.toArray(new Object[collector.size()]))));
		collector.clear();
		System.gc();
		
		addpseudorandomness = newdigestNwipe(Xor.xorWipe(addpseudorandomness, ByteUtils.copyTogetherNwipeB(pseudoheap(),
				pseudorun(101), pseudorun(102), pseudorun(103), pseudorun((int)(System.nanoTime() % 111)))));
		System.gc();
		
		return addpseudorandomness;
	}
	
	private final byte[] newdigestNwipe(byte[] inarr){
		byte[] arr = md.digest(inarr);
		
		CryptoUtils.kill(inarr);
		
		return arr;
	}
	
	private static final byte[] pseudoheap(){
		long curheap = Runtime.getRuntime().totalMemory(); 
		
		long maxheap = Runtime.getRuntime().maxMemory();
		
		long freeheap = Runtime.getRuntime().freeMemory(); 
		
		return ByteUtils.copyTogetherNwipeO(curheap, maxheap, freeheap);
	}

	private static int winner;
	private static final class MyRun implements Runnable{
		private int no;
		
		public MyRun(int no){
			this.no = no;
		}
		
		@Override
		public void run() {
			no += (System.currentTimeMillis() % Integer.MAX_VALUE);
			winner = no + hashCode();
		}
	}
	
	private static final byte[] pseudorun(int len){
		try {
			Thread[] ts = new Thread[len];
			for(int i=0; i<len; i++){
				ts[i] = new Thread(new MyRun(i));
			}

			for(int i=0; i<len; i++){
				ts[i].start();
			}

			for(int i=0; i<len; i++){
				ts[i].join();
				ts[i] = null;
			}
			
			byte[] ret = ByteUtils.intToBytes(winner);
			winner = Integer.MIN_VALUE;
			return ret;
		} catch (Exception e) {
			System.err.println(e.getLocalizedMessage());
			return new byte[]{-1};
		}
	}
}
 public class ByteUtils {
	public static final byte[] stringToBytes(String str) {
		byte[] b = new byte[str.length() << 1];
		for (int i = 0; i < str.length(); i++) {
			char strChar = str.charAt(i);
			int bpos = i << 1;
			b[bpos] = (byte) ((strChar & 0xFF00) >> 8);
			b[bpos + 1] = (byte) (strChar & 0x00FF);
		}
		return b;
	}
	
	public static final byte[] charsToBytes(char[] chars) {
		byte[] b = new byte[chars.length << 1];
		for (int i = 0; i < chars.length; i++) {
			char strChar = chars[i];
			int bpos = i << 1;
			b[bpos] = (byte) ((strChar & 0xFF00) >> 8);
			b[bpos + 1] = (byte) (strChar & 0x00FF);
		}
		return b;
	}
	
	public static final char[] bytesToChars(byte[] bytes) {
		char[] buffer = new char[bytes.length >> 1];
		for (int i = 0; i < buffer.length; i++) {
			int bpos = i << 1;
			char c = (char) (((bytes[bpos] & 0x00FF) << 8) + (bytes[bpos + 1] & 0x00FF));
			buffer[i] = c;
		}
		
		return buffer;
	}

	public static final String bytesToString(byte[] bytes) {
		return new String(bytesToChars(bytes));
	}
	
	
	public static final byte[] longToBytes(long x) {
	    ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / 8);
	    buffer.putLong(x);
	    return buffer.array();
	}

	public static final long bytesToLong(byte[] bytes) {
	    ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / 8);
	    buffer.put(bytes);
	    buffer.flip(); 
	    return buffer.getLong();
	}

	
	public static final byte[] intToBytes(int x) {
	    ByteBuffer buffer = ByteBuffer.allocate(Integer.SIZE / 8);
	    buffer.putInt(x);
	    return buffer.array();
	}

	public static final int bytesToInt(byte[] bytes) {
	    ByteBuffer buffer = ByteBuffer.allocate(Integer.SIZE / 8);
	    buffer.put(bytes);
	    buffer.flip(); 
	    return buffer.getInt();
	}
	
	public static final byte[] objToBytes(Object o){
		JTYPE type = null;
		try {
			type = typeFromObj(o);
		} catch (Exception e) {
			System.err.println("OBJ unknown");
			return null;
		}
		
	    ByteBuffer buffer = ByteBuffer.allocate(type.size());
		switch (type) {
		case Long:
			buffer.putLong((Long)o);
			break;
		case Integer:
			buffer.putInt((Integer)o);
			break;
		case Byte:
			buffer.put((Byte)o);
			break;
		case Short:
			buffer.putShort((Short)o);
			break;
		case Double:
			buffer.putDouble((Double)o);
			break;
		case Float:
			buffer.putFloat((Float)o);
			break;
		case Character:
			buffer.putChar((char)o);
			break;
		default:
			System.err.println("OBJ unknown");
		}

	    return buffer.array();
	}
	
	@SuppressWarnings("unchecked")
	public static final <T>T bytesToObject(byte[] bytes, T o){
		JTYPE type = null;
		try {
			type = typeFromObj(o);
		} catch (Exception e) {
			System.err.println("OBJ unknown");
			return null;
		}
		
		if(bytes.length > type.size) bytes = cut(bytes, type.size);
		
	    ByteBuffer buffer = ByteBuffer.allocate(type.size());
	    buffer.put(bytes);
	    buffer.flip(); 
	    
		switch (type) {
		case Long:
			return (T) (Long)buffer.getLong();
		case Integer:
			return (T) (Integer)buffer.getInt();
		case Byte:
			return (T) (Byte)buffer.get();
		case Short:
			return (T) (Short)buffer.getShort();
		case Double:
			return (T) (Double)buffer.getDouble();
		case Float:
			return (T) (Float)buffer.getFloat();
		case Character:
			return (T) (Character)buffer.getChar();
		default:
			System.err.println("OBJ unknown");
			return null;
		}
	}
	
	public enum JTYPE{
		Long(8),
		Integer(4),
		Byte(1),
		Short(2),
		Double(64),
		Float(32),
		Character(2),
		
		;
		
		private int size;
		private JTYPE(int size){
			this.size = size;
		}
		
		public int size(){
			return size;
		}
	}
	
	public static final JTYPE typeFromObj(Object obj){
		JTYPE type = null;
		try {
			type = JTYPE.valueOf(obj.getClass().getSimpleName());
		} catch (Exception e) {
		}
		return type;
	}
	
	public static final int sizeObject(Object o){
		return typeFromObj(o).size();
	}
	
	public static final int sizeInBytes(Object o){
		if(o instanceof byte[]) return ((byte[]) o).length;
		return typeFromObj(o).size();
	}
	
	public static final int sizeInBytes(Object... o){
		int ret = 0;
		for(Object ox: o)
			ret += sizeInBytes(ox);
		return ret;
	}
	
	public static final int b2i(byte b){
		if(b >= 0) return b;
		return b + 256;
	}
	
	public static final byte i2b(int i){
		if(i > 128) return (byte)(i - 256);
		return (byte) i;
	}
	
	public static final byte[] copyTogetherNwipeO(Object... arr){
		byte[][] b = new byte[arr.length][];
		
		for(int i=0; i<arr.length; i++){
			if(arr[i] != null)
				b[i] = objToBytes(arr[i]);
			else
				b[i] = new byte[]{1};
		}
		
		return copyTogetherNwipeB(b);
	}
	
	public static final byte[] copyTogetherNwipeB(byte[]... arr){
		int len = 0;
		for(byte[] b: arr){
			if(b == null) continue;
			
			len += b.length;
		}
		
		byte[] ret = new byte[len];
		int start = 0;
		for(byte[] b: arr){
			if(b == null) continue;
			
			System.arraycopy(b, 0, ret, start, b.length);
			start += b.length;
			CryptoUtils.kill(b);
		}
		
		return ret;
	}

	public static final byte[] cut(byte[] bytes, int newlen){
		byte[] ret = new byte[newlen];
		System.arraycopy(bytes, 0, ret, 0, newlen);
		return ret;
	}
	
	public static final byte[] fill(byte[] bytes, int newlen){
		byte[] ret = new byte[newlen];
		System.arraycopy(bytes, 0, ret, 0, bytes.length);
		return ret;
	}
	
	public static final byte[] fill(Object o, int newlen){
		byte[] o_bytes = objToBytes(o);
		byte[] ret = new byte[newlen];
		System.arraycopy(o_bytes, 0, ret, 0, o_bytes.length);
		return ret;
	}
}
 public class Codes {
	public static final String UTF8 = "UTF-8";
	
	public static final int ZIP_BUFFERSIZE = 1024*1024*4;
}
 public class StreamMachine {
	public enum StreamResult{
		OK,
		EndOfStream,
		ReadException,
	}
	
	private InputStream is;
	
	public StreamMachine(InputStream is){
		this.is = is;
	}
	
	public byte[] read(int numofbytes){
		return read(is, numofbytes);
	}
	
	public StreamResult read(byte[] buffer){
		return read(is, buffer);
	}
	
	public <T>T readO(T o){
		return readO(is, o);
	}
	
	public static final void write(OutputStream os, Object... arr) throws IOException{
		for(Object o: arr){
			if(o instanceof byte[]){
				os.write((byte[]) o);
			}
			else{
				os.write(ByteUtils.objToBytes(o));
			}
		}
	}

	
	public static final byte[] read(InputStream in, int numofbytes){
		byte[] ret = new byte[numofbytes];
		read(in, ret);
		return ret;
	}
	
	public static final StreamResult read(InputStream in, byte[] buffer){
		if(buffer.length > 0){
			try {
				int received = 0, total = 0;
				while(total < buffer.length){
					received = in.read(buffer, total, buffer.length - total);
					if(received < 0) return StreamResult.EndOfStream;
					total += received;
				}
				return StreamResult.OK;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return StreamResult.ReadException;
	}
	
	public static final <T>T readO(InputStream in, T o){
		return ByteUtils.bytesToObject(read(in, ByteUtils.sizeObject(o)), o);
	}
}

import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.HashMap;
import javax.crypto.Cipher;
import org.crococryptfile.CrococryptFile;
import org.crococryptfile.suites.Suite;
import org.crococryptfile.suites.SuiteMODE;
import org.crococryptfile.suites.SuitePARAM;
import org.crococryptfile.ui.resources._T;
import org.fhissen.crypto.CipherMain;
import org.fhissen.crypto.CryptoCodes;
import org.fhissen.crypto.CryptoCodes.BASECIPHER;
import org.fhissen.crypto.CryptoUtils;
import org.fhissen.crypto.FSecretKeySpec;
import org.fhissen.crypto.KeygenUtils;
import org.fhissen.utils.ByteUtils;
import org.fhissen.utils.Codes;
import org.fhissen.utils.StreamMachine;

package org.crococryptfile.suites.capirsaaes;





public class CAPIRSAAESMain extends Suite {
	private static KeyStore capi = CAPIRSAUtils.getCAPI();

	private int ATTRIBUTE_DNLEN; 
	private byte[] ATTRIBUTE_DN; 
	private int ATTRIBUTE_KEYLEN; 
	private byte[] ATTRIBUTE_KEY; 
	private byte[] ATTRIBUTE_IV = new byte[CryptoCodes.STANDARD_IVSIZE]; 
	
	private String alias;
	private byte[] aesplain;
	private CipherMain ciph;

	
	@Override
	protected void _init(SuiteMODE mode, HashMap<SuitePARAM, Object> params) throws IllegalArgumentException{
		if(mode == SuiteMODE.ENCRYPT){
			if(params == null || params.size() == 0)  throw new IllegalArgumentException("CAPI: no params specified");
			alias = (String) params.get(SuitePARAM.capi_alias);
			if(alias == null) throw new IllegalArgumentException("CAPIEncrypt mode must specify an alias");

			aesplain = new KeygenUtils().raw(CryptoCodes.AES_KEYSIZE_BITS);
			ciph = new CipherMain(BASECIPHER.AES, new FSecretKeySpec(aesplain));
			ATTRIBUTE_IV = CryptoUtils.randIv16();
			
			try {

				ATTRIBUTE_DN = CAPIRSAUtils.getDNFromAlias(alias).getBytes(Codes.UTF8);
