package org.bouncycastle.crypto.examples;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.SecureRandom;

import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.KeyGenerationParameters;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.generators.DESedeKeyGenerator;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.DESedeParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;

/**
 * DESExample is a simple DES based encryptor/decryptor.
 *
 * The program is command line driven, with the input
 * and output files specified on the command line.
 *
 * java org.bouncycastle.crypto.examples.DESExample infile outfile [keyfile]
 *
 * A new key is generated for each encryption, if key is not specified,
 * then the example will assume encryption is required, and as output
 * create deskey.dat in the current directory.  This key is a hex
 * encoded byte-stream that is used for the decryption.  The output
 * file is Hex encoded, 60 characters wide text file.
 *
 * When encrypting;
 *	. the infile is expected to be a byte stream (text or binary)
 *  . there is no keyfile specified on the input line
 *
 * When decrypting;
 *  . the infile is expected to be the 60 character wide base64 
 *    encoded file
 *  . the keyfile is expected to be a base64 encoded file
 *
 * This example shows how to use the light-weight API, DES and
 * the filesystem for message encryption and decryption.
 *
 */
public class DESExample extends Object
{
	// Encrypting or decrypting ?
	private boolean encrypt = true;

	// To hold the initialised DESede cipher
	private PaddedBufferedBlockCipher cipher = null;

	// The input stream of bytes to be processed for encryption
	private BufferedInputStream in = null;

	// The output stream of bytes to be procssed
	private BufferedOutputStream out = null;

	// The key
	private byte[] key = null;

	/*
	 * start the application
	 */
	public static void main(String[] args)
	{
		boolean encrypt = true;
		String infile = null;
		String outfile = null;
		String keyfile = null;

		if (args.length < 2)
		{
			DESExample de = new DESExample();
			System.err.println("Usage: java "+de.getClass().getName()+
								" infile outfile [keyfile]");
			System.exit(1);
		}

		keyfile = "deskey.dat";
		infile = args[0];
		outfile = args[1];

		if (args.length > 2)
		{
			encrypt = false;
			keyfile = args[2];
		}

		DESExample de = new DESExample(infile, outfile, keyfile, encrypt);
		de.process();
	}

	// Default constructor, used for the usage message
	public DESExample() { }
	/*
	 * Constructor, that takes the arguments appropriate for
	 * processing the command line directives.
	 */
	public DESExample(
				String infile,
				String outfile,
				String keyfile,
				boolean encrypt
					)
	{
		/* 
		 * First, determine that infile & keyfile exist as appropriate.
		 *
		 * This will also create the BufferedInputStream as required
		 * for reading the input file.  All input files are treated
		 * as if they are binary, even if they contain text, it's the
		 * bytes that are encrypted.
		 */
		this.encrypt = encrypt;
		try
		{
			in = new BufferedInputStream(new FileInputStream(infile));
		}
		catch (FileNotFoundException fnf)
		{
			System.err.println("Input file not found ["+infile+"]");
			System.exit(1);
		}

		try
		{
			out = new BufferedOutputStream(new FileOutputStream(outfile));
		}
		catch (IOException fnf)
		{
			System.err.println("Output file not created ["+outfile+"]");
			System.exit(1);
		}

		if (encrypt)
		{
			try
			{
				/*
				 * The process of creating a new key requires a 
				 * number of steps.
				 *
				 * First, create the parameters for the key generator
				 * which are a secure random number generator, and
				 * the length of the key (in bits).
				 */
				SecureRandom sr = null;
				try
				{
					sr = new SecureRandom();
					/*
					 * This following call to setSeed() makes the
					 * initialisation of the SecureRandom object
					 * _very_ fast, but not secure AT ALL.  
					 *
					 * Remove the line, recreate the class file and 
					 * then run DESExample again to see the difference.
					 *
					 * The initialisation of a SecureRandom object
					 * can take 5 or more seconds depending on the
					 * CPU that the program is running on.  That can
					 * be annoying during unit testing.
					 * 	-- jon
					 */
					sr.setSeed("www.bouncycastle.org".getBytes());
				}
				catch (Exception nsa)
				{
					System.err.println("Hmmm, no SHA1PRNG, you need the "+
										"Sun implementation");
					System.exit(1);
				}
				KeyGenerationParameters kgp = new KeyGenerationParameters(
									sr, 
									DESedeParameters.DES_EDE_KEY_LENGTH*8);

				/*
				 * Second, initialise the key generator with the parameters
				 */
				DESedeKeyGenerator kg = new DESedeKeyGenerator();
				kg.init(kgp);

				/*
				 * Third, and finally, generate the key
				 */
				key = kg.generateKey();

				/*
				 * We can now output the key to the file, but first
				 * hex encode the key so that we can have a look
				 * at it with a text editor if we so desire
				 */
				BufferedOutputStream keystream = 
					new BufferedOutputStream(new FileOutputStream(keyfile));
				byte[] keyhex = Hex.encode(key);
				keystream.write(keyhex, 0, keyhex.length);
				keystream.flush();
				keystream.close();
			}
			catch (IOException createKey)
			{
				System.err.println("Could not decryption create key file "+
									"["+keyfile+"]");
				System.exit(1);
			}
		}
		else
		{
			try
			{
				// read the key, and decode from hex encoding
				BufferedInputStream keystream = 
					new BufferedInputStream(new FileInputStream(keyfile));
				int len = keystream.available();
				byte[] keyhex = new byte[len];
				keystream.read(keyhex, 0, len);
				key = Hex.decode(keyhex);
			}
			catch (IOException ioe)
			{
				System.err.println("Decryption key file not found, "+
									"or not valid ["+keyfile+"]");
				System.exit(1);
			}
		}
	}

	private final void process()
	{
		/* 
		 * Setup the DESede cipher engine, create a PaddedBufferedBlockCipher
		 * in CBC mode.
		 */
		cipher = new PaddedBufferedBlockCipher(
									new CBCBlockCipher(new DESedeEngine()));

		/*
		 * The input and output streams are currently set up
		 * appropriately, and the key bytes are ready to be
		 * used.
		 *
		 */

		if (encrypt)
		{
			performEncrypt(key);
		}
		else
		{
			performDecrypt(key);
		}

		// after processing clean up the files
		try
		{
			in.close();
			out.flush();
			out.close();
		}
		catch (IOException closing)
		{

		}
	}
		
	/*
	 * This method performs all the encryption and writes
	 * the cipher text to the buffered output stream created
	 * previously.
	 */
	private final void performEncrypt(byte[] key)
	{
		// initialise the cipher with the key bytes, for encryption
		cipher.init(true, new KeyParameter(key));

		/*
		 * Create some temporary byte arrays for use in
		 * encryption, make them a reasonable size so that
		 * we don't spend forever reading small chunks from
		 * a file.
		 *
		 * There is no particular reason for using getBlockSize()
		 * to determine the size of the input chunk.  It just
		 * was a convenient number for the example.  
		 */
		// int inBlockSize = cipher.getBlockSize() * 5;
		int inBlockSize = 47;
		int outBlockSize = cipher.getOutputSize(inBlockSize);

		byte[] inblock = new byte[inBlockSize];
		byte[] outblock = new byte[outBlockSize];

		/* 
		 * now, read the file, and output the chunks
		 */
		try
		{
			int inL;
			int outL;
			byte[] rv = null;
			while ((inL=in.read(inblock, 0, inBlockSize)) > 0)
			{
				outL = cipher.processBytes(inblock, 0, inL, outblock, 0);
				/*
				 * Before we write anything out, we need to make sure
				 * that we've got something to write out. 
				 */
				if (outL > 0)
				{
					rv = Hex.encode(outblock, 0, outL);
					out.write(rv, 0, rv.length);
					out.write('\n');
				}
			}

			try
			{
				/*
				 * Now, process the bytes that are still buffered
				 * within the cipher.
				 */
				outL = cipher.doFinal(outblock, 0);
				if (outL > 0)
				{
					rv = Hex.encode(outblock, 0, outL);
					out.write(rv, 0, rv.length);
					out.write('\n');
				}
			}
			catch (CryptoException ce)
			{

			}
		}
		catch (IOException ioeread)
		{
			ioeread.printStackTrace();
		}
	}

	/*
	 * This method performs all the decryption and writes
	 * the plain text to the buffered output stream created
	 * previously.
	 */
	private final void performDecrypt(byte[] key)
	{	
		// initialise the cipher for decryption
		cipher.init(false, new KeyParameter(key));

		/* 
		 * As the decryption is from our preformatted file,
		 * and we know that it's a hex encoded format, then
		 * we wrap the InputStream with a BufferedReader
		 * so that we can read it easily.
		 */
		BufferedReader br = new BufferedReader(new InputStreamReader(in));

		/* 
		 * now, read the file, and output the chunks
		 */
		try
		{
			int outL;
			byte[] inblock = null;
			byte[] outblock = null;
			String rv = null;
			while ((rv = br.readLine()) != null)
			{
				inblock = Hex.decode(rv);
				outblock = new byte[cipher.getOutputSize(inblock.length)];

				outL = cipher.processBytes(inblock, 0, inblock.length, 
											outblock, 0);
				/*
				 * Before we write anything out, we need to make sure
				 * that we've got something to write out. 
				 */
				if (outL > 0)
				{
					out.write(outblock, 0, outL);
				}
			}

			try
			{
				/*
				 * Now, process the bytes that are still buffered
				 * within the cipher.
				 */
				outL = cipher.doFinal(outblock, 0);
				if (outL > 0)
				{
					out.write(outblock, 0, outL);
				}
			}
			catch (CryptoException ce)
			{

			}
		}
		catch (IOException ioeread)
		{
			ioeread.printStackTrace();
		}
	}

}

