using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Diagnostics;

namespace EncryptDecryptFiles
{
    static class CryptoStuff
    {
        // Use the password to generate key bytes.
        private static void MakeKeyAndIV(string password, byte[] salt, int keySizeBits, int blockSizeBits, out byte[] key, out byte[] iv)
        {
            Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, salt, 1000);

            key = deriveBytes.GetBytes(keySizeBits / 8);
            iv = deriveBytes.GetBytes(blockSizeBits / 8);
        }

        #region "Encrypt Files and Streams"

        // Encrypt or decrypt a file, saving the results in another file.
        public static void EncryptFile(string password, string inFile, string outFile)
        {
            CryptFile(password, inFile, outFile, true);
        }
        public static void DecryptFile(string password, string inFile, string outFile)
        {
            CryptFile(password, inFile, outFile, false);
        }
        public static void CryptFile(string password, string inFile, string outFile, bool encrypt)
        {
            // Create input and output file streams.
            using (FileStream inStream = new FileStream(inFile, FileMode.Open, FileAccess.Read))
            {
                using (FileStream outStream = new FileStream(outFile, FileMode.Create, FileAccess.Write))
                {
                    // Encrypt/decrypt the input stream into the output stream.
                    CryptStream(password, inStream, outStream, encrypt);
                }
            }
        }

        // Encrypt the data in the input stream into the output stream.
        public static void CryptStream(string password, Stream inStream, Stream outStream, bool encrypt)
        {
            // Make an AES service provider.
            AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();

            // Find a valid key size for this provider.
            int keySizeBits = 0;
            for (int i = 1024; i > 1; i--)
            {
                if (aesProvider.ValidKeySize(i))
                {
                    keySizeBits = i;
                    break;
                }
            }
            Debug.Assert(keySizeBits > 0);
            Console.WriteLine("Key size: " + keySizeBits);

            // Get the block size for this provider.
            int blockSizeBits = aesProvider.BlockSize;

            // Generate the key and initialization vector.
            byte[] key = null;
            byte[] iv = null;
            byte[] salt = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
            MakeKeyAndIV(password, salt, keySizeBits, blockSizeBits, out key, out iv);

            // Make the encryptor or decryptor.
            ICryptoTransform cryptoTransform;
            if (encrypt)
                cryptoTransform = aesProvider.CreateEncryptor(key, iv);
            else
                cryptoTransform = aesProvider.CreateDecryptor(key, iv);

            // Attach a CryptoStream to the output stream.
            // Closing the CryptoStream sometimes throws an exception if the
            // decryption didn't work (e.g. if we use the wrong password).
            try
            {
                using (CryptoStream cryptoStream = new CryptoStream(outStream, cryptoTransform, CryptoStreamMode.Write))
                {
                    // Encrypt or decrypt the file.
                    const int blockSize = 2048;
                    byte[] buffer = new byte[blockSize];
                    int bytesRead;
                    while (true)
                    {
                        // Read some bytes.
                        bytesRead = inStream.Read(buffer, 0, blockSize);
                        if (bytesRead == 0) break;

                        // Write the bytes into the CryptoStream.
                        cryptoStream.Write(buffer, 0, bytesRead);
                    }
                } // using cryptoStream 
            }
            catch
            {
            }
            cryptoTransform.Dispose();
        }

        #endregion // Encrypt Files and Streams"

        #region "Encrypt Strings and Byte[]"
        // Note that extension methods must be defined in a non-generic static class.

        // Encrypt or decrypt the data in inBytes[] and return the result.
        public static byte[] CryptBytes(string password, byte[] inBytes, bool encrypt)
        {
            // Make an AES service provider.
            AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();

            // Find a valid key size for this provider.
            int keySizeBits = 0;
            for (int i = 1024; i > 1; i--)
            {
                if (aesProvider.ValidKeySize(i))
                {
                    keySizeBits = i;
                    break;
                }
            }
            Debug.Assert(keySizeBits > 0);
            Console.WriteLine("Key size: " + keySizeBits);

            // Get the block size for this provider.
            int blockSizeBits = aesProvider.BlockSize;

            // Generate the key and initialization vector.
            byte[] key = null;
            byte[] iv = null;
            byte[] salt = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
            MakeKeyAndIV(password, salt, keySizeBits, blockSizeBits, out key, out iv);

            // Make the encryptor or decryptor.
            ICryptoTransform cryptoTransform;
            if (encrypt)
            {
                cryptoTransform = aesProvider.CreateEncryptor(key, iv);
            }
            else
            {
                cryptoTransform = aesProvider.CreateDecryptor(key, iv);
            }

            // Create the output stream.
            using (MemoryStream outStream = new MemoryStream())
            {
                // Attach a crypto stream to the output stream.
                using (CryptoStream cryptoStream = new CryptoStream(outStream,
                    cryptoTransform, CryptoStreamMode.Write))
                {
                    // Write the bytes into the CryptoStream.
                    cryptoStream.Write(inBytes, 0, inBytes.Length);
                    try
                    {
                        cryptoStream.FlushFinalBlock();
                    }
                    catch (CryptographicException)
                    {
                        // Ignore this exception. The password is bad.
                    }
                    catch
                    {
                        // Re-throw this exception.
                        throw;
                    }

                    // return the result.
                    return outStream.ToArray();
                }
            }
        }

        // String extensions to encrypt and decrypt strings.
        public static byte[] Encrypt(this string theString, string password)
        {
            System.Text.ASCIIEncoding asciiEncoder = new System.Text.ASCIIEncoding();
            byte[] plainBytes = asciiEncoder.GetBytes(theString);
            return CryptBytes(password, plainBytes, true);
        }
        public static string Decrypt(this byte[] theBytes, string password)
        {
            byte[] decryptedBytes = CryptBytes(password, theBytes, false);
            System.Text.ASCIIEncoding asciiEncoder = new System.Text.ASCIIEncoding();
            return asciiEncoder.GetString(decryptedBytes);
        }

        // Encrypt a string and return a string version of the resulting bytes.
        public static string EncryptToHex(this string inString, string password, string separator)
        {
            byte[] bytes = inString.Encrypt(password);
            return bytes.ToHex(separator);
        }

        // Decrypt a string that represents a byte array.
        public static string DecryptFromHex(this string inString, string password, string separator)
        {
            byte[] bytes = inString.ToBytes(separator);
            return bytes.Decrypt(password);
        }

        // Convert a byte array into a readable string of hexadecimal values.
        public static string ToHex(this byte[] theBytes)
        {
            return ToHex(theBytes, "");
        }
        public static string ToHex(this byte[] theBytes, string separator)
        {
            string result = "";
            for (int i = 0; i < theBytes.Length; i++)
            {
                result += theBytes[i].ToString("x2") + separator;
            }
            return result;
        }

        // Convert a string containing 2-digit hexadecimal values into a byte array.
        public static byte[] ToBytes(this string theString, string separator)
        {
            List<byte> theBytes = new List<byte>();
            if ((separator != null) && (separator.Length > 0))
                theString = theString.Replace(separator, "");

            for (int i = 0; i < theString.Length; i += 2)
            {
                theBytes.Add(
                    byte.Parse(theString.Substring(i, 2),
                        System.Globalization.NumberStyles.HexNumber));
            }
            return theBytes.ToArray();
        }

        #endregion // Encrypt Strings and Byte[]

    }
}
