// cryptbig.java
// -------------------------------------
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.yacy.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.Provider;
import java.security.Security;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Random;
import java.util.Set;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import net.yacy.cora.document.encoding.UTF8;
import net.yacy.cora.order.Base64Order;
import net.yacy.cora.order.Digest;
import net.yacy.cora.util.CommonPattern;
import net.yacy.cora.util.ConcurrentLog;


public class cryptbig {

    // --------------------------------------------------------
    // Section: random salt generation
    // --------------------------------------------------------

    private static long saltcounter = 0;
    private static Random saltrandom = new Random(System.currentTimeMillis());

    public static String randomSalt() {
        // generate robust 48-bit random number
        final long salt = (saltrandom.nextLong() & 0XffffffffffffL) + (System.currentTimeMillis() & 0XffffffffffffL) + ((1001 * saltcounter) & 0XffffffffffffL);
        saltcounter++;
        // we generate 48-bit salt values, that are represented as 8-character
        // b64-encoded strings
        return Base64Order.standardCoder.encodeLongSB(salt & 0XffffffffffffL, 8).toString();
    }

    // --------------------------------------------------------
    // Section: PBE + PublicKey based on passwords encryption
    // --------------------------------------------------------

    public static final String vDATE = "20030925";
    public static final String copyright = "[ 'crypt' v" + vDATE + " by Michael Christen / www.anomic.de ]";
    public static final String magicString = "crypt|anomic.de|0"; // magic identifier inside every '.crypt' - file
    public static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.ENGLISH);

    String cryptMethod; // one of ["TripleDES", "Blowfish", "DESede", "DES"]
    private static final String defaultMethod = "PBEWithMD5AndDES"; //"DES";

    Cipher ecipher;
    Cipher dcipher;

    public cryptbig(final String pbe) {
	// this is possible, but not recommended
	this(pbe, (pbe + "XXXXXXXX").substring(0, 8));
    }

    public cryptbig(final String pbe, final String salt) {
	this(pbe, salt, defaultMethod);
    }


    private cryptbig(final String pbe, String salt, final String method) {
	// a Password-Based Encryption. The SecretKey is created on the fly
	final PBEKeySpec keySpec = new PBEKeySpec(pbe.toCharArray());
	try {
	    if (salt.length() > 8) salt = salt.substring(0,8);
	    if (salt.length() < 8) salt = (salt + "XXXXXXXX").substring(0,8);

	    // create the PBE key
	    final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(method);
	    final SecretKey key = keyFactory.generateSecret(keySpec);

	    // create parameter spec for PBE
	    final PBEParameterSpec paramSpec = new PBEParameterSpec(UTF8.getBytes(salt), 1000 /*ITERATIONS*/);

	    // Create a cipher and initialize it for encrypting end decrypting
	    this.cryptMethod = method;
	    this.ecipher = Cipher.getInstance(this.cryptMethod);
	    this.dcipher = Cipher.getInstance(this.cryptMethod);
	    this.ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); // paramSpec only for PBE!
	    this.dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
	} catch (final javax.crypto.NoSuchPaddingException e) {
	} catch (final java.security.InvalidKeyException e) {
	} catch (final java.security.NoSuchAlgorithmException e) {
	} catch (final java.security.spec.InvalidKeySpecException e) {
	} catch (final java.security.InvalidAlgorithmParameterException e) {
	}
    }

    // Encode a string into a new string using utf-8, crypt and b64
    public String encryptString(final String str) {
	try {
	    final byte[] utf = str.getBytes("UTF8");
	    final byte[] enc = encryptArray(utf);
	    if (enc == null) return null;
	    return Base64Order.standardCoder.encode(enc);
	} catch (final UnsupportedEncodingException e) {
	}
	return null;
    }

    // Decode a string into a new string using b64, crypt and utf-8
    public String decryptString(final String str) {
	final byte[] b64dec = Base64Order.standardCoder.decode(str);
    if (b64dec == null) return null; // error in input string (inconsistency)
    final byte[] dec = decryptArray(b64dec);
    if (dec == null) return null;
    return UTF8.String(dec);
    }

    // Encode a byte array into a new byte array
    public byte[] encryptArray(final byte[] b) {
	if (b == null) return null;
	try {
	    return this.ecipher.doFinal(b);
	} catch (final javax.crypto.BadPaddingException e) {
	} catch (final IllegalBlockSizeException e) {
	}
	return null;
    }

    // Decode a string into a new string using b64, crypt and utf-8
    public byte[] decryptArray(final byte[] b) {
	if (b == null) return null;
	try {
	    return this.dcipher.doFinal(b);
	} catch (final javax.crypto.BadPaddingException e) {
	} catch (final IllegalBlockSizeException e) {
	}
	return null;
    }


    // This method returns the available implementations for a service type
    public static Set<String> listCryptoMethods(final String serviceType) {
        final Set<String> result = new HashSet<String>();

        // All providers
        final Provider[] providers = Security.getProviders();
        for (Provider provider : providers) {
            // Get services provided by each provider
            final Set<?> keys = provider.keySet();
            for (Object name : keys) {
                String key = (String) name;
                key = CommonPattern.SPACE.split(key)[0];
                if (key.startsWith(serviceType + ".")) {
                    result.add(key.substring(serviceType.length() + 1));
                } else if (key.startsWith("Alg.Alias." + serviceType + ".")) {
                    // This is an alias
                    result.add(key.substring(serviceType.length() + 11));
                }
            }
        }
        return result;
    }

    public static void testCryptMethods(final Set<String> methods) {
	    String method;
	    final Iterator<String> i = methods.iterator();
	    while (i.hasNext()) {
		method = i.next();
		System.out.print(method + " : ");
		try {
		    final cryptbig crypter = new cryptbig("abrakadabra", method);
		    final String encrypted = crypter.encryptString("nicht verraten abc 1234567890");
		    System.out.print(encrypted + "/");
		    final String decrypted = crypter.decryptString(encrypted);
		    System.out.println(decrypted);
		} catch (final Exception e) {
		    System.out.println("Exception: " + e.getMessage());
		    ConcurrentLog.logException(e);
		}
	    }
    }

    public void encryptFile(final String inFileName, final String outFileName) {
	/*
	  File-Format of encrypted file:
	  Filename: b64-of-encryption-of-<encryption-date: YYYYMMddHHmmSSsss> plus extension ".crypt"
	  File Content:
	  <original file name>
	  <original file date>
	  <original file size>
	  <compressed-before-encryption-flag>
	  <compressed-after-encryption-flag>
	  <binary>
	*/
    InputStream  fin = null;
    OutputStream fout = null;
	try {
	    final File   inFile           = new File(inFileName);
	    final String inFileDate       = dateFormatter.format(new Date(inFile.lastModified())); // 17 byte
	    final String encryptionDate   = dateFormatter.format(new Date()); // 17 byte
	    final String inFileSize       = Base64Order.standardCoder.encodeLongSB(inFile.length(), 11).toString(); // 64 / 6 = 11; 11 byte
	    final String flag             = "1"; // 1 byte
	    //int    inFileNameLength = inFileName.length(); // 256
	    final String X                = inFileDate + encryptionDate + inFileSize + flag + inFileName;

	    System.out.println("TEST: preserving inFileDate    : " + dateFormatter.parse(inFileDate, new ParsePosition(0)));
	    System.out.println("TEST: preserving encryptionDate: " + dateFormatter.parse(encryptionDate, new ParsePosition(0)));
	    System.out.println("TEST: preserving inFileLength  : " + inFile.length());
	    System.out.println("TEST: preserving flag          : " + flag);
	    System.out.println("TEST: preserving inFileName    : " + inFileName);
	    System.out.println("TEST: preserving X-String      : " + X);

	    // start encryption
	    fin  = new CipherInputStream(new FileInputStream(inFile), this.ecipher);
	    fout = new FileOutputStream(outFileName);

	    // write magic and properties of original file
	    // - we encrypt the original date, the encryption date, the file size, the flag
	    //   and file name together to the string A and calculate the length AL of that string
	    // - the length of the file name is therefore equal to AL-(17+17+11+1) = AL-46
	    // - AL is then b64-ed and also encrypted which results into string B
	    // - the length of B is BL; BL is then b64-ed to a string C of fixed length 1
	    // - after the magic String we write C, B and A
	    try {
		final String A = UTF8.String(this.ecipher.doFinal(X.getBytes("UTF8")));
		final String B = UTF8.String(this.ecipher.doFinal(Base64Order.standardCoder.encodeLongSB(A.length(), 2).toString().getBytes("UTF8"))); // most probable not longer than 4
		final String C = Base64Order.standardCoder.encodeLongSB(B.length(), 1).toString(); // fixed length 1 (6 bits, that should be enough)
		fout.write(UTF8.getBytes(magicString)); // the magic string, used to identify a 'crypt'-file
		fout.write(UTF8.getBytes(C));
		fout.write(UTF8.getBytes(B));
		fout.write(UTF8.getBytes(A));

		// write content of file
		copy(fout, fin, 512);
	    }
	    catch (final javax.crypto.IllegalBlockSizeException e) {System.err.println("ERROR:" + e.getMessage());}
	    catch (final javax.crypto.BadPaddingException e) {System.err.println("ERROR:" + e.getMessage());}
	} catch (final FileNotFoundException e) {
	    System.err.println("ERROR: file '" + inFileName + "' not found");
	} catch (final IOException e) {
	    System.err.println("ERROR: IO trouble");
	} finally {
		try {
			if(fin != null) {
				fin.close();
			}
		} catch (IOException ignored) {
		}
		try {
			if(fout != null) {
				fout.close();
			}
		} catch(IOException ignored) {
		}
	}
    }

    public void decryptFile(final String inFileName, final String outFileName) {
	InputStream  fin = null;
	OutputStream fout = null;
	try {
	    // Start opening the files
	    fin  = new BufferedInputStream(new FileInputStream(inFileName), 4096);

	    // read the file properties
	    final byte[] thisMagic = new byte[magicString.length()];
	    fin.read(thisMagic);

	    if (!((UTF8.String(thisMagic)).equals(magicString))) {
		// this is not an crypt file, so dont do anything
		fin.close();
		return;
	    }
	    final byte[] C = new byte[1];
	    fin.read(C); // the length of the following String, encoded as b64
	    final byte[] B = new byte[(int) Base64Order.standardCoder.decodeLong(UTF8.String(C))];
	    fin.read(B); // this is again the length of the following string, as encrypted b64-ed integer
	    final byte[] A = new byte[(int) Base64Order.standardCoder.decodeLong(UTF8.String(this.dcipher.doFinal(B)))];
	    fin.read(A);
	    final String X = UTF8.String(this.dcipher.doFinal(A));

	    System.out.println("TEST: detecting X-String      : " + X);

	    // reconstruct the properties
	    final Date   inFileDate     = dateFormatter.parse(X.substring(0, 17), new ParsePosition(0));
	    final Date   encryptionDate = dateFormatter.parse(X.substring(17, 34),  new ParsePosition(0));
	    final long   inFileSize     = Base64Order.standardCoder.decodeLong(X.substring(34, 45));
	    final String flag           = X.substring(45, 46);
	    final String origFileName   = X.substring(46);

	    System.out.println("TEST: detecting inFileDate    : " + inFileDate);
	    System.out.println("TEST: detecting encryptionDate: " + encryptionDate);
	    System.out.println("TEST: detecting inFileLength  : " + inFileSize);
	    System.out.println("TEST: detecting flag          : " + flag);
	    System.out.println("TEST: detecting inFileName    : " + origFileName);

	    // open the output file
	    fout = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(outFileName), this.dcipher), 4096);

	    // read and decrypt the file
	    copy(fout, fin, 512);

	    // do postprocessing
	} catch (final BadPaddingException e) {
	    System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage());
	} catch (final IllegalBlockSizeException e) {
	    System.err.println("ERROR: decryption of '" + inFileName + "' not possible: " + e.getMessage());
	} catch (final FileNotFoundException e) {
	    System.err.println("ERROR: file '" + inFileName + "' not found");
	} catch (final IOException e) {
	    System.err.println("ERROR: IO trouble");
	} finally {
	    try { if(fin != null) {
	        fin.close();
	    }} catch (final Exception ee) {}
	    try { if(fout != null) {
	        fout.close();
	    }} catch (final Exception ee) {}
	}
    }

    private static void copy(final OutputStream out, final InputStream in, final int bufferSize) throws IOException {
	final InputStream  bIn  = new BufferedInputStream(in, bufferSize);
	final OutputStream bOut = new BufferedOutputStream(out, bufferSize);
	final byte[] buf = new byte[bufferSize];
	int n;
	while ((n = bIn.read(buf)) > 0) bOut.write(buf, 0, n);
	bIn.close();
	bOut.close();
    }


    public static String scrambleString(final String key, final String s) {
	// we perform several operations
	// - generate salt
	// - gzip string
	// - crypt string with key and salt
	// - base64-encode result
	// - attach salt and return
	final String salt = randomSalt();
	//System.out.println("Salt=" + salt);
	final cryptbig c = new cryptbig(key, salt);
	boolean gzFlag = true;
	byte[] gz = Gzip.gzipString(s);
	if (gz.length > s.length()) {
	    // revert compression
	    try {
		gz = s.getBytes("UTF8");
		gzFlag = false;
	    } catch (final UnsupportedEncodingException e) {
		return null;
	    }
	}
	//System.out.println("GZIP length=" + gz.length);
	if (gz == null) return null;
	final byte[] enc = c.encryptArray(gz);
	if (enc == null) return null;
	return salt + ((gzFlag) ? "1" : "0") + Base64Order.enhancedCoder.encode(enc);
    }

    public static String descrambleString(final String key, String s) {
	final String salt = s.substring(0, 8);
	final boolean gzFlag = (s.charAt(8) == '1');
	s = s.substring(9);
	final cryptbig c = new cryptbig(key, salt);
	final byte[] b64dec = Base64Order.enhancedCoder.decode(s);
	if (b64dec == null) return null; // error in input string (inconsistency)
	final byte[] dec = c.decryptArray(b64dec);
	if (dec == null) return null;
	if (gzFlag) return Gzip.gunzipString(dec);
	return UTF8.String(dec);

    }


    // --------------------------------------------------------
    // Section: simple Codings
    // --------------------------------------------------------


    public static String simpleEncode(final String content) {
	return simpleEncode(content, null, 'b');
    }

    public static String simpleEncode(final String content, final String key) {
	return simpleEncode(content, key, 'b');
    }

    public static String simpleEncode(final String content, String key, final char method) {
	if (key == null) key = "NULL";
	if (method == 'p') return "p|" + content;
	if (method == 'b') return "b|" + Base64Order.enhancedCoder.encodeString(content);
	if (method == 'z') return "z|" + Base64Order.enhancedCoder.encode(Gzip.gzipString(content));
	if (method == 'c') return "c|" + scrambleString(key, content);
	return null;
    }

    public static String simpleDecode(String encoded, final String key) {
	if ((encoded == null) || (encoded.length() < 3)) return null;
	if (encoded.charAt(1) != '|') return encoded; // not encoded
	final char method = encoded.charAt(0);
	encoded = encoded.substring(2);
	if (method == 'p') return encoded;
	if (method == 'b') return Base64Order.enhancedCoder.decodeString(encoded);
	if (method == 'z') return Gzip.gunzipString(Base64Order.enhancedCoder.decode(encoded));
	if (method == 'c') return descrambleString(key, encoded);
	return null;
    }


    // --------------------------------------------------------
    // Section: one-way encryption
    // --------------------------------------------------------

    public static String oneWayEncryption(final String key) {
	final cryptbig crypter = new cryptbig(key);
	String e = crypter.encryptString(key);
	if (e.isEmpty()) e = "0XXXX";
	if (e.length() % 2 != 0) e += "X";
	while (e.length() < 32) e = e + e;
	final char[] r = new char[16];
	for (int i = 0; i < 16; i++) r[i] = e.charAt(2 * i + 1);
	return new String(r);
    }

    // --------------------------------------------------------
    // Section: command interface
    // --------------------------------------------------------

    private static void help() {
	System.out.println("AnomicCrypt (2003) by Michael Christen");
	System.out.println("Password-based encryption using the " + defaultMethod + "-method in standard java");
	System.out.println("usage: crypt -h | -help");
	System.out.println("       crypt -1 <passwd>");
	System.out.println("       crypt -md5 <file>");
	System.out.println("       crypt ( -es64 | -ds64 | -ec64 | -dc64 ) <string>");
	System.out.println("       crypt ( -e | -d ) <key> <string>");
	System.out.println("       crypt -enc <key> <file>  \\");
	System.out.println("                   [-o <target-file> | -preserveFilename]  \\");
	System.out.println("                   [-d <YYYYMMddHHmmSSsss> | -preserveDate] [-noZip]");
	System.out.println("       crypt -dec <key> <file>  \\");
	System.out.println("                   [-o <target-file> | -preserveFilename]  \\");
	System.out.println("                   [-d <YYYYMMddHHmmSSsss> | -preserveDate]");
	System.out.println("       crypt ( -info | -name | -size | -date | -edate )  \\");
	System.out.println("                   <key> <encrypted-file>");
    }

    private static void longhelp() {
	// --line-help--   *---------------------------------------------------------------
	System.out.println("AnomicCrypt (2003) by Michael Christen");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -1 <passwd>");
	System.out.println("");
	System.out.println("   One-way encryption of the given password.");
	System.out.println("   The result is computed by encoding the word with the word as");
	System.out.println("   the password and repeating it until the length is greater");
	System.out.println("   than 32. Then every second character is taken to compose the");
	System.out.println("   result which has always the length of 16 characters.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -md5 <file>");
	System.out.println("");
	System.out.println("   MD5 digest according to RFC 1321. The resulting bytes are");
	System.out.println("   encoded as two-digit hex and concatenated to a single string.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -ec64 <cardinal>");
	System.out.println("");
	System.out.println("   Encoding of a cardianal (a positive long integer) with the");
	System.out.println("   built-in non-standard base-64 algorithm.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -dc64 <string>");
	System.out.println("");
	System.out.println("   Decoding of the given b64-coded string to a cardinal number.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -es64 <string>");
	System.out.println("");
	System.out.println("   Encoding of a given String to a b64 string.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -ds64 <string>");
	System.out.println("");
	System.out.println("   Decoding of a given b64-coded string to a normal string.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -e <key> <string>");
	System.out.println("");
	System.out.println("   Encryption of a given Unicode-String.");
	System.out.println("   The given string is first encoded to an UTF-8 byte stream, then");
	System.out.println("   encoded using a password based encryption and then finaly");
	System.out.println("   encoded to b64 to generate a printable form.");
	System.out.println("   The PBE method is " + defaultMethod + ".");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -d <key> <string>");
	System.out.println("");
	System.out.println("   Decryption of a string.");
	System.out.println("   The string is b64-decoded, " + defaultMethod + "-decrypted, ");
	System.out.println("   and then transformed to an unicode string.");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -enc <key> <file>  \\");
	System.out.println("            [-o <target-file> | -preserveFilename]  \\");
	System.out.println("            [-d <target-date YYYYMMddHHmmSSsss> | -preserveDate] [-noZip]");
	System.out.println("");
	System.out.println("");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt -dec <key> <file>  \\");
	System.out.println("            [-o <target-file> | -preserveFilename]  \\");
	System.out.println("            [-d <target-date YYYYMMddHHmmSSsss> | -preserveDate]");
	System.out.println("");
	System.out.println("");
	System.out.println("crypt ( -info | -name | -size | -date | -edate ) <key> <encrypted-file>");
	System.out.println("");
	System.out.println("");
    }

    public static void main(final String[] s) {
	if (s.length == 0) {
	    help();
	    System.exit(0);
	}
	if ((s[0].equals("-h")) || (s[0].equals("-help"))) {
	    longhelp();
	    System.exit(0);
	}
	if (s[0].equals("-tc")) {
	    // list all available crypt mehtods:
	    final Set<String> methods = listCryptoMethods("Cipher");
	    System.out.println(methods.size() + " crypt methods:" + methods.toString());
	    testCryptMethods(methods);
	    System.exit(0);
	}
	if (s[0].equals("-random")) {
	    final int count = ((s.length == 2) ? (Integer.parseInt(s[1])) : 1);
	    for (int i = 0; i < count; i++) System.out.println(randomSalt());
	    System.exit(0);
	}
	if (s[0].equals("-1")) {
	    if (s.length != 2) {help(); System.exit(-1);}
	    System.out.println(oneWayEncryption(s[1]));
	    System.exit(0);
	}
	if (s[0].equals("-ec64")) {
	    // generate a b64 encoding from a given cardinal
	    if (s.length != 2) {help(); System.exit(-1);}
	    System.out.println(Base64Order.standardCoder.encodeLongSB(Long.parseLong(s[1]), 0).toString());
	    System.exit(0);
	}
	if (s[0].equals("-dc64")) {
	    // generate a b64 decoding from a given cardinal
	    if (s.length != 2) {help(); System.exit(-1);}
	    System.out.println(Base64Order.standardCoder.decodeLong(s[1]));
	    System.exit(0);
	}
	if (s[0].equals("-es64")) {
	    // generate a b64 encoding from a given string
	    if (s.length != 2) {help(); System.exit(-1);}
	    System.out.println(Base64Order.standardCoder.encodeString(s[1]));
	    System.exit(0);
	}
	if (s[0].equals("-ds64")) {
	    // generate a b64 decoding from a given string
	    if (s.length != 2) {help(); System.exit(-1);}
	    System.out.println(Base64Order.standardCoder.decodeString(s[1]));
	    System.exit(0);
	}
	if (s[0].equals("-ess")) {
	    // 'scramble' string
	    if (s.length != 3) {help(); System.exit(-1);}
	    final long t = System.currentTimeMillis();
	    System.out.println(scrambleString(s[1], s[2]));
	    System.out.println("Calculation time: " + (System.currentTimeMillis() - t) + " milliseconds");
	    System.exit(0);
	}
	if (s[0].equals("-dss")) {
	    // 'descramble' string
	    if (s.length != 3) {help(); System.exit(-1);}
	    final long t = System.currentTimeMillis();
	    System.out.println(descrambleString(s[1], s[2]));
	    System.out.println("Calculation time: " + (System.currentTimeMillis() - t) + " milliseconds");
	    System.exit(0);
	}
	if (s[0].equals("-e")) {
	    if (s.length != 3) {help(); System.exit(-1);}
	    System.out.println((new cryptbig(s[1])).encryptString(s[2]));
	    System.exit(0);
	}
	if (s[0].equals("-d")) {
	    if (s.length != 3) {help(); System.exit(-1);}
	    System.out.println((new cryptbig(s[1])).decryptString(s[2]));
	    System.exit(0);
	}
	if (s[0].equals("-md5")) {
	    // generate a public key from a password that can be used for encryption
	    if (s.length != 2) {help(); System.exit(-1);}
	    String md5s = "";
        try {
            md5s = Digest.encodeMD5Hex(new File(s[1]));
        } catch (final IOException e) {
            e.printStackTrace();
        }
	    System.out.println(md5s);
	    System.exit(0);
	}
	if (s[0].equals("-enc")) {
	    if ((s.length < 3) || (s.length > 4)) {help(); System.exit(-1);}
	    String target;
	    if (s.length == 3) target = s[2] + ".crypt"; else target = s[3];
	    (new cryptbig(s[1])).encryptFile(s[2], target);
	    System.exit(0);
	}
	if (s[0].equals("-dec")) {
	    if ((s.length < 3) || (s.length > 4)) {help(); System.exit(-1);}
	    String target;
	    if (s.length == 3) {
		if (s[2].endsWith(".crypt"))
		    target = s[2].substring(0, s[2].length() - 7);
		else
		    target = s[2] + ".decoded";
	    } else {
		target = s[3];
	    }
	    (new cryptbig(s[1])).decryptFile(s[2], target);
	    System.exit(0);
	}
	help(); System.exit(-1);
    }

}
