using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace SuperCryptEX
{
    public partial class MainForm : Form
    {
        /* [#] ElGamal Key Details :
         * 
         * [A] Client :
         * Client - Public Key  = p, g, y, k    (For Encryption)
         * Client - Private Key = p, x, a       (For Decryption)
         * 
         * [B] Host :
         * Host - Private Key   = p, g, y, k    (For Encryption) -> Inverted, Basically Public Key
         * Host - Public Key    = p, x, a       (For Decryption) -> Inverted, Basically Private Key
         */

        // ------------------------------------------------------------
        // Parameters - Semua Variabel yang Digunakan dalam Program
        // ------------------------------------------------------------
        #region Parameters

        // Parameter - Global Variables
        private Class_General g = new Class_General();
        private Stopwatch sw = new Stopwatch();
        private string separatorSymbol = "|";
        private int progressBarIterator = 10;

        // Parameter - Relative Variables
        private bool setMainNotBackground = false;  // Set ke True Jika Tidak Ingin Menggunakan Background Worker
        private string inputPath, hostKeyPath, clientKeyPath;
        private long encryptFileLength = 0;
        private long decryptFileLength = 0;
        private string decryptFileExtension = string.Empty;
        private string decryptHashValue = string.Empty;
        private string debugMessage = string.Empty;
        private Options optEnDe;

        // Parameter - SHA3
        private const BlockSize SHA3BlockSize = BlockSize.Bit_512;   // Bit -> Block Size   : { 512, 384, 256, 224 }

        // Parameter - AES
        private BlockSize AESBlockSize = BlockSize.Bit_128;          // Bit -> Block Size   : { 128 }
        private BlockSize AESKeySize = BlockSize.Bit_256;            // Bit -> Key Size     : { 128, 192, 256 }
        private const int AES_BLOCK_SIZE = (128 / 8);                // Byte
        private const int AES_KEY_SIZE = (256 / 8);                  // Byte

        // Parameter - ElGamal
        private Class_ElGamal egHost = new Class_ElGamal();
        private Class_ElGamal egClient = new Class_ElGamal();

        // Parameter - Options (Encrypt/Decrypt)
        private enum Options
        {
            Encrypt = 0,
            Decrypt,
        };

        // Parameter - ID untuk Error Checking
        private enum ErrorID
        {
            // Check TextBox
            NullInputPath = 0,
            NullOutputPath,
            NullPassword,
            NullHostKey,
            NullClientKey,

            // Check File Existence
            NullInputFile,
            NullOutputFile,
            NullHostKeyFile,
            NullClientKeyFile,
            NullBothKeyFile,

            // Check Key Retrieval
            FailedHostKey,
            FailedClientKey,
            FailedBothKey,

            // Check Hash
            FailedSHA3,

            // Check Encryption Algorithm
            EncryptFailedAES,
            EncryptFailedElGamalHash,
            EncryptFailedElGamalPassword,

            // Check Decryption Algorithm
            DecryptFailedAES,
            DecryptFailedElGamalHash,
            DecryptFailedElGamalPassword,

            // Others
            HashMismatch,
            OtherError,
            Success,
        };

        // Parameter - Error Messages
        private string[] errorMessages = new string[]
        {
            // Check TextBox
            "Please Select a File!",
            "Please Select a Location to Save File!",
            "Please Enter a Password!",
            "Please Select a Host Key",
            "Please Select a Client Key",

            // Check File Existence
            "Input File Doesn't Exist!",
            "Output File Doesn't Exist!",
            "Host Key File Doesn't Exist!",
            "Client Key File Doesn't Exist!",
            "Host Key or Client Key File Doesn't Exist!",

            // Check Key Retrieval
            "Failed to Retrieve Host Key!",
            "Failed to Retrieve Client Key!",
            "Failed to Retrieve Host Key or Client Key!",

            // Check Hash
            "[SHA3] Hashing Failed!",

            // Check Encryption Algorithm
            "[AES] File Encryption Failed!",
            "[ElGamal] Hash Encryption Failed!\nCheck if You're Using the Right Key!",
            "[ElGamal] Password Encryption Failed!\nCheck if You're Using the Right Key!",

            // Check Decryption Algorithm
            "[AES] File Decryption Failed!",
            "[ElGamal] Hash Decryption Failed!\nCheck if You're Using the Right Key!",
            "[ElGamal] Password Decryption Failed!\nCheck if You're Using the Right Key!",

            // Others
            "Hash Mismatch, File has been Modified!",
            "Unknown Error!",
            "Success!"
        };

        #endregion

        // ------------------------------------------------------------
        // General Functions - Fungsi-Fungsi Umum yang Digunakan pada Program
        // ------------------------------------------------------------
        #region General Functions

        // Function - Update Log
        public void General_UpdateLogText(string log)
        {
            string finalLog = Regex.Replace(log, "(?<!\r)\n", "\r\n");
            g.printDebug(log);
            //debugMessage = log;
            if (text_log.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                BeginInvoke(new MethodInvoker(() => General_InvokeUpdateLogText(finalLog)));
            }
            else
            {
                // It's on the same thread, no need for Invoke
                text_log.Text += "\r\n" + finalLog;
            }
        }

        // Function - Update Log for Different Thread
        private void General_InvokeUpdateLogText(string log)
        {
            text_log.Text += "\r\n" + log;
        }

        // Function - Update Extension
        public void General_UpdateOutText(string ext)
        {
            if (text_output.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                BeginInvoke(new MethodInvoker(() => General_InvokeUpdateOutText(ext)));
            }
            else
            {
                // It's on the same thread, no need for Invoke
                text_output.Text = ext;
            }
        }

        // Function - Update Extension for Different Thread
        private void General_InvokeUpdateOutText(string ext)
        {
            text_output.Text = ext;
        }

        // Function - Enable/Disable Button
        private void General_ToggleButton(bool enable)
        {
            if (enable)
            {
                button_browse.Enabled =
                    button_save.Enabled =
                    button_host_key.Enabled =
                    button_client_key.Enabled =
                    button_start.Enabled =
                    button_check_key.Enabled =
                    true;
            }
            else
            {
                button_browse.Enabled =
                    button_save.Enabled =
                    button_host_key.Enabled =
                    button_client_key.Enabled =
                    button_start.Enabled =
                    button_check_key.Enabled =
                    false;
            }
        }

        // Function - Tampilkan Error Message
        private string General_GetErrorMessage(ErrorID id)
        {
            return errorMessages[(int)id];
        }

        // Function - Update Progress pada Background Worker
        private void General_UpdateProgress(string process)
        {
            status_process.Text = process;
            background_worker_program.ReportProgress(0);
        }

        // Function - Mengganti Tulisan Parameter Host Menjadi Client dan Sebaliknya
        private void General_ChangeParameterInfo(Options mode)
        {
            if (mode == Options.Encrypt)
            {
                // Enable Password Field
                text_password.Enabled = true;

                // Host
                label_host_key.Text = "Host Private Key :";
                text_host_key.PlaceholderText = "Host Private Key Location";
                button_host_key.Text = "Browse Private Key (H)";

                // Client
                label_client_key.Text = "Client Public Key :";
                text_client_key.PlaceholderText = "Client Public Key Location";
                button_client_key.Text = "Browse Public Key (C)";
            }
            else
            {
                // Disable Password Field
                text_password.Enabled = false;

                // Host
                label_host_key.Text = "Host Public Key :";
                text_host_key.PlaceholderText = "Host Public Key Location";
                button_host_key.Text = "Browse Public Key (H)";

                // Client
                label_client_key.Text = "Client Private Key :";
                text_client_key.PlaceholderText = "Client Private Key Location";
                button_client_key.Text = "Browse Private Key (C)";
            }
        }

        // Function - Reset TextBox & Variabel pada Program
        private void General_ResetTextBoxAndVariables(TextBox tb = null)
        {
            if (tb != null)
            {
                tb.Clear();
            }
            else
            {
                text_input.Clear();
                text_output.Clear();
                text_password.Clear();
                text_host_key.Clear();
                text_client_key.Clear();

                inputPath = hostKeyPath = clientKeyPath = string.Empty;
                encryptFileLength = decryptFileLength = 0;
            }
        }

        // Function - Inisialisasi Awal untuk Komponen-Komponen
        private void General_InitializeAllComponents()
        {
            //sw = new Stopwatch();
            sw.Restart();
            sw.Stop();
            status_progress_bar.Value = 0;
            status_progress_bar.Maximum = 100;
            //progressBarIterator = 10;
            General_ResetTextBoxAndVariables();
        }

        // Function - Cek Text Box pada Program Apakah Sudah Terisi Seluruhnya & Cek Apakah FIle Ada atau Tidak
        private ErrorID General_CheckFields()
        {
            // Cek Text Box Input Apakah Sudah Terisi atau Belum
            if (text_input.Text == string.Empty)
            {
                return ErrorID.NullInputPath;
            }

            // Cek Text Box Output Apakah Sudah Terisi atau Belum
            if (text_output.Text == string.Empty)
            {
                return ErrorID.NullOutputPath;
            }

            // Cek Text Box Host Key Apakah Sudah Terisi atau Belum
            if (text_host_key.Text == string.Empty)
            {
                return ErrorID.NullHostKey;
            }

            // Cek Text Box Client Key Apakah Sudah Terisi atau Belum
            if (text_client_key.Text == string.Empty)
            {
                return ErrorID.NullClientKey;
            }

            // Cek Text Box Password Apakah Sudah Terisi atau Belum (Hanya Jika Mode Enkripsi)
            if (optEnDe == Options.Encrypt)
            {
                if (text_password.Text == string.Empty)
                {
                    return ErrorID.NullPassword;
                }
            }

            // Cek File Input Apakah Ada atau Tidak
            if (g.fileIsExist(text_input.Text) == false)
            {
                return ErrorID.NullInputFile;
            }

            // Cek File Host Key Apakah Ada atau Tidak
            if (g.fileIsExist(text_host_key.Text) == false)
            {
                return ErrorID.NullHostKeyFile;
            }

            // Cek File Client Key Apakah Ada atau Tidak
            if (g.fileIsExist(text_client_key.Text) == false)
            {
                return ErrorID.NullClientKeyFile;
            }

            return ErrorID.Success;
        }

        // Function - Cek Validitas Host Key dan Client Key, Kemudian Masukkan ke Variabel Key ElGamal
        private ErrorID General_RetrieveHostAndClientKey(Class_ElGamal egHost, string hostKey, Class_ElGamal egClient, string clientKey, Options mode)
        {
            General_UpdateLogText(
                "\n==================================================\n" +
                "[#] Checking Key Validity\n" +
                "==================================================");

            // [#] Cek Apakah Host Key dan Client Key Ada
            if (g.fileIsExist(hostKey) && g.fileIsExist(clientKey))
            {
                string hostString, clientString;

                // [#] Get Host Key dari File
                try
                {
                    byte[] hostByte = File.ReadAllBytes(hostKey);
                    hostString = g.byteToString(Convert.FromBase64String(g.byteToString(hostByte)));
                    General_UpdateLogText("Host Key \t\t: " + hostString);
                }
                catch
                {
                    return ErrorID.FailedHostKey;
                }

                // [#] Get Client Key dari File
                try
                {
                    byte[] clientByte = File.ReadAllBytes(clientKey);
                    clientString = g.byteToString(Convert.FromBase64String(g.byteToString(clientByte)));
                    General_UpdateLogText("Client Key \t\t: " + clientString);
                }
                catch
                {
                    return ErrorID.FailedClientKey;
                }

                // [#] Split String Setiap Separator "|" pada Host Key dan Client Key, Kemudian Masukkan Ke Variabel Key pada ElGamal
                try
                {
                    int currentKeyCursor = 0;
                    int separatorFound = 0;

                    int[] hostTemp = new int[5] { 0, 0, 0, 0, 0 };
                    int hostSeparatorCount = hostString.Count(x => x == '|');
                    General_UpdateLogText("Host Separator Count \t: " + hostSeparatorCount);

                    int[] clientTemp = new int[5] { 0, 0, 0, 0, 0 };
                    int clientSeparatorCount = clientString.Count(x => x == '|');
                    General_UpdateLogText("Client Separator Count \t: " + clientSeparatorCount);

                    // Split Host String dan Masukkan ke Variabel "hostTemp"
                    for (int i = 0; i < hostString.Length; i++)
                    {
                        // Jika Value pada Posisi Byte Sekarang = "|"
                        if (hostString[i] == '|')
                        {
                            // Masukkan Value Key Sebelum "|" ke "hostTemp"
                            hostTemp[separatorFound] = Convert.ToInt32(hostString.Substring(currentKeyCursor, i - currentKeyCursor));
                            General_UpdateLogText("Found Host Key [" + separatorFound + "] \t: " + hostTemp[separatorFound]);
                            currentKeyCursor = i + 1;
                            separatorFound++;
                        }

                        // Jika Jumlah Separator Sudah Sama dengan Total Key Separator
                        if (separatorFound == hostSeparatorCount)
                        {
                            // Masukkan Value Key Terakhir Sesudah "|" ke "hostTemp"
                            hostTemp[separatorFound] = Convert.ToInt32(hostString.Substring(currentKeyCursor, hostString.Length - currentKeyCursor));
                            General_UpdateLogText("Found Host Key [" + separatorFound + "] \t: " + hostTemp[separatorFound]);
                            separatorFound = currentKeyCursor = 0;
                            break;
                        }
                    }

                    // Split Client String dan Masukkan ke Variabel "clientTtemp"
                    for (int i = 0; i < clientString.Length; i++)
                    {
                        // Jika Value pada Posisi Byte Sekarang = "|"
                        if (clientString[i] == '|')
                        {
                            clientTemp[separatorFound] = Convert.ToInt32(clientString.Substring(currentKeyCursor, i - currentKeyCursor));
                            General_UpdateLogText("Found Client Key [" + separatorFound + "] \t: " + clientTemp[separatorFound]);
                            currentKeyCursor = i + 1;
                            separatorFound++;
                        }

                        // Jika Jumlah Separator Sudah Sama dengan Total Key Separator
                        if (separatorFound == clientSeparatorCount)
                        {
                            // Masukkan Value Key Terakhir Sesudah "|" ke "clientTemp"
                            clientTemp[separatorFound] = Convert.ToInt32(clientString.Substring(currentKeyCursor, clientString.Length - currentKeyCursor));
                            General_UpdateLogText("Found Client Key [" + separatorFound + "] \t: " + clientTemp[separatorFound]);
                            separatorFound = currentKeyCursor = 0;
                            break;
                        }
                    }

                    // Assign Host dan Client Key Parameter ke Variabel Key pada ElGamal
                    if (mode == Options.Encrypt)
                    {
                        egHost.General_SetPublicKey(hostTemp[0], hostTemp[1], hostTemp[2], hostTemp[3]);
                        egClient.General_SetPublicKey(clientTemp[0], clientTemp[1], clientTemp[2], clientTemp[3]);
                    }
                    else
                    {
                        egHost.General_SetPrivateKey(hostTemp[0], hostTemp[1], hostTemp[2]);
                        egClient.General_SetPrivateKey(clientTemp[0], clientTemp[1], clientTemp[2]);
                    }

                }
                catch
                {
                    return ErrorID.FailedBothKey;
                }
            }
            else
            {
                return ErrorID.NullBothKeyFile;
            }

            return ErrorID.Success;
        }

        #endregion

        // ------------------------------------------------------------
        // Algorithm Functions - Fungsi-Fungsi Untuk Menjalankan Algoritma
        // ------------------------------------------------------------
        #region Algorithm Functions

        // Function - SHA-3
        private static HEX SHA3_Hash(FileStream fs)
        {
            return Class_SHA3.SHA3(fs, SHA3BlockSize);
        }

        // Function - AES (Inisialisasi Block (Termasuk Proses Padding))
        private void AES_InitializeByte(byte[] pByte, int iLength)
        {
            int iIndex = 0;
            for (iIndex = 0; iIndex < iLength; iIndex++)
            {
                pByte[iIndex] = 0;
            }
        }

        // Core Function - File Encrypt
        private ErrorID Core_Encrypt(string inputFile, string outputFile, string password)
        {
            try
            {
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //              [1] Inisialisasi Variabel & File Output Check
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 10/100 ===
                General_UpdateProgress("[10/100] Reading Input File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[10/100] Reading Input File\n" +
                    "==================================================");

                // Baca File dan Masukkan ke Byte Array
                byte[] fileByte = File.ReadAllBytes(inputFile);

                // Buka File Input
                FileStream inputStream = new FileStream(inputFile, FileMode.Open);
                FileInfo fileInfo = new FileInfo(inputFile);

                // Buat File Output
                //FileStream Unused_outputStream = new FileStream(outputFile, FileMode.Append);

                General_UpdateLogText("[Encrypt] [Read File] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //              [2] Masukkan Info Extension & File Length ke Output
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 20/100 ===
                General_UpdateProgress("[20/100] Saving File Info");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[20/100] Saving File Info\n" +
                    "==================================================");

                // Get File Extension
                string encryptFileExt = Path.GetExtension(inputFile);

                string fileExtStr = encryptFileExt.ToString();
                General_UpdateLogText("[Encrypt] [File Info] File Extension \t: " + fileExtStr);

                // Tambah Simbol "|" untuk Ditulis pada File Sebagai Separator
                fileExtStr += separatorSymbol;

                // ---> [FILE] VARIABEL 1A - FILE BYTE <---
                // Write --> Masukkan ke Output File [Size : <Relative> Bytes] (Tidak Termasuk Separator "|")
                byte[] fileExtByte = g.stringToByte(fileExtStr);
                //Unused_outputStream.Write(fileExtByte);

                // Get File Length
                // Method 1 (Dari File Stream) :
                encryptFileLength = inputStream.Length;
                // Method 2 (Dari File Info) :
                encryptFileLength = fileInfo.Length;

                string fileLengthStr = encryptFileLength.ToString();
                General_UpdateLogText("[Encrypt] [File Info] File Length \t: " + fileLengthStr + " Bytes");

                // Tambah Simbol "|" untuk Ditulis pada File Sebagai Separator
                fileLengthStr += separatorSymbol;

                // ---> [FILE] VARIABEL 1B - FILE LENGTH <---
                // Write --> Masukkan ke Output File [Size : <Relative> Bytes] (Tidak Termasuk Separator "|")
                byte[] fileLengthByte = g.stringToByte(fileLengthStr);
                //Unused_outputStream.Write(fileLengthByte);

                General_UpdateLogText("[Encrypt] [File Info] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                      [3] Hash File Byte dengan SHA3
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 30/100 ===
                General_UpdateProgress("[30/100] Hashing File with SHA3");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[30/100] Hashing File with SHA3\n" +
                    "==================================================");

                /* Info :
                 * Output dari SHA3 Menghasilkan Hash dengan Karakter Berukuran 128 Bytes
                 * SHA3-512 = 512 Bit = 512 / 8 = 64 Bytes
                 * 1 Karakter ASCII = 2 Bytes
                 * 64 x 2 = 128 Bytes
                 */

                // Hash File
                string shaString = string.Empty;
                try
                {
                    shaString = SHA3_Hash(inputStream);
                    General_UpdateLogText("[Encrypt] [SHA3 Hash] Result \t: \n\"" + shaString + "\"");
                }
                catch
                {
                    return ErrorID.FailedSHA3;
                }

                General_UpdateLogText("[Encrypt] [SHA3 Hash] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [4] Encrypt Hasil Hash dengan ElGamal
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 40/100 ===
                General_UpdateProgress("[40/100] Encrypting Hash with ElGamal");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[40/100] Encrypting Hash with ElGamal\n" +
                    "==================================================");

                // Encrypt Hash dengan ElGamal -> Host Private Key [p, g, y, k]
                string encryptedSHAString = string.Empty;
                try
                {
                    encryptedSHAString = egHost.Go_Encrypt(shaString, egHost.var_p, egHost.var_g, egHost.var_y, egHost.var_k);
                    General_UpdateLogText("[Encrypt] [Encrypted SHA3 Hash] Result \t: \n\"" + encryptedSHAString + "\"");
                }
                catch
                {
                    return ErrorID.EncryptFailedElGamalHash;
                }

                // Tambah Simbol "|" untuk Ditulis pada File Sebagai Separator
                encryptedSHAString += separatorSymbol;

                /* Info :
                 * Size = 460 Bytes Karena Besar Hash = 128 Bytes (String), Di Enkripsi dengan ElGamal (Base64) Menjadi 460 Bytes
                 * Dimana 1 Karakter Base64 = 6-Bit, Sedangkan 1 Karakter String = 8-Bit
                 * Sehingga Setiap 3 Karakter String (8 x 3 = 24-Bit) Terdapat 4 Karakter Base64 (6 x 4 = 24-Bit)
                 * 1. Input = 128 Bytes (String) = (128 / 3) x 4 = 43 x 4 = 172 Bytes (Base64)         *) 128 / 3 = 42.6 Pembulatan Ke Atas = 43
                 * 2. Enkripsi Setiap Char pada Base64 Sehingga Menjadi ASCII
                 * 3. 172 Bytes (ASCII) = 172 x 2 = 344 Bytes (Unicode)
                 * 4. Input = 344 Bytes (Unicode) = (344 / 3) x 4 = 115 x 4 = 460 Bytes (Base64)       *) 344 / 3 = 114.6 Pembulatan Ke Atas = 115
                 */

                // ---> VARIABEL 2A - ENCRYPTED HASH <---
                // Write --> Masukkan ke Variabel "encryptedHashByte" [Size : 460 Bytes] (Tidak Termasuk Separator "|")
                byte[] encryptedHashByte = g.stringToByte(encryptedSHAString);

                // Masukkan ke Sebuah File Debug
                g.createDebugFile(inputFile, encryptedHashByte, "[E]_ENCRYPTED_HASH");

                General_UpdateLogText("[Encrypt] [Encrypted SHA3 Hash] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //          [5] Pad Hasil Enkripsi Hash dan File -> E(Hash) + File
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 50/100 ===
                General_UpdateProgress("[50/100] Generating X1 Byte Array");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[50/100] Generating X1 Byte Array\n" +
                    "==================================================");

                // ---> VARIABEL 2B - X1 (ENCRYPTED HASH + FILE) <---
                // Write --> Masukkan ke Variabel "X1" [Size : (461 + File Length) Bytes] (Sudah Termasuk Separator "|")

                // 1. Inisialisasi Byte Array Baru yang Akan Menampung Gabungan dari Encrypted Hash + File (X1)
                byte[] X1 = new byte[encryptedHashByte.Length + inputStream.Length];

                // 2. Copy "encryptedHashByte" ke "X1"
                Buffer.BlockCopy(encryptedHashByte, 0, X1, 0, encryptedHashByte.Length);

                // 3. Copy "fileByte" ke "X1"
                Buffer.BlockCopy(fileByte, 0, X1, encryptedHashByte.Length, fileByte.Length);

                // Masukkan ke Sebuah File Debug
                g.createDebugFile(inputFile, X1, "[E]_X1");

                General_UpdateLogText("[Encrypt] [X1 Byte Array] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //  [6] Encrypt Variabel "X1" dengan AES, Encrypt Password AES dengan ElGamal
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // Inisialisasi AES Parameter
                byte[] plainByte = new byte[AES_BLOCK_SIZE];
                byte[] cipherByte = new byte[AES_BLOCK_SIZE];
                byte[] keyByte = new byte[AES_KEY_SIZE];

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [6.1] Encrypt AES Password dengan ElGamal
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 60/100 ===
                General_UpdateProgress("[60/100] Encrypting Password with ElGamal");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[60/100] Encrypting Password with ElGamal\n" +
                    "==================================================");

                // Inisialisasi Awal "keyByte" dengan Null Byte (0) dan dengan Ukuran 16 Byte, Digunakan untuk Menampung Password
                AES_InitializeByte(keyByte, AES_KEY_SIZE);

                // Konversi Password Text Menjadi Byte Array Berukuran 16 Byte
                byte[] keyByteTemp = g.stringToByte(password);
                for (int i = 0; i < keyByteTemp.Length; i++)
                {
                    keyByte[i] = keyByteTemp[i];
                }
                General_UpdateLogText("[Encrypt] [AES Password] Retrieved \t\t: \n\"" + g.byteToString(keyByte) + "\"");

                // Encrypt Password dengan ElGamal -> Client Public Key [p, g, y, k]
                string encryptedKeyString = string.Empty;
                try
                {
                    encryptedKeyString = egClient.Go_Encrypt(g.byteToString(keyByte), egClient.var_p, egClient.var_g, egClient.var_y, egClient.var_k);
                    General_UpdateLogText("[Encrypt] [Encrypted AES Password] Result \t: \n\"" + encryptedKeyString + "\"");
                }
                catch
                {
                    return ErrorID.EncryptFailedElGamalPassword;
                }

                // Tambah Simbol "|" untuk Ditulis pada File Sebagai Separator
                encryptedKeyString += separatorSymbol;

                // ---> [FILE] VARIABEL 3 - ENCRYPTED KEY <---
                // Write --> Masukkan ke Output File [Size : 64 Bytes] (Tidak Termasuk Separator "|")
                byte[] encryptedKeyByte = g.stringToByte(encryptedKeyString);
                //Unused_outputStream.Write(encryptedKeyByte);

                // Masukkan ke Sebuah File Debug
                g.createDebugFile(inputFile, encryptedKeyByte, "[E]_ENCRYPTED_PASSWORD");

                General_UpdateLogText("[Encrypt] [Encrypted AES Password] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [6.2] Encrypt Variabel "X1" dengan AES
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 70/100 ===
                General_UpdateProgress("[70/100] Encrypting File with AES (X1)");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[70/100] Encrypting File with AES (X1)\n" +
                    "==================================================");

                // Cari Block Size Encrypted X1 -> Karena AES Meng-Enkripsi Setiap Kelipatan 16 Bytes
                General_UpdateLogText("[Encrypt] [X1] Byte Size \t\t\t: " + X1.Length + " Bytes");
                long encryptedX1_BlockSize = 0;
                if (X1.Length % 16 != 0)
                {
                    encryptedX1_BlockSize = ((X1.Length / 16) + 1) * 16;
                }
                else
                {
                    encryptedX1_BlockSize = X1.Length;
                }

                General_UpdateLogText("[Encrypt] [Encrypted X1] Byte Size + Padding \t: " + encryptedX1_BlockSize + " Bytes");

                // Encrypt Variabel "X1" dengan AES -> Password
                byte[] encryptedX1 = new byte[encryptedX1_BlockSize];
                try
                {
                    // Set Posisi Awal Kursor Sebagai Penunjuk Posisi Baca pada Byte "X1"
                    long cursorPosition = 0;

                    // Baca Semua Byte pada "X1", Kemudian Lakukan Enkripsi
                    while (cursorPosition < X1.Length)
                    {
                        // Inisialisasi Awal "plainByte" dengan Null Byte (0) dan dengan Ukuran 16 Byte, Digunakan untuk Menampung Input
                        AES_InitializeByte(plainByte, AES_BLOCK_SIZE);

                        // Inisialisasi Awal "cipherByte" dengan Null Byte (0) dan dengan Ukuran 16 Byte, Digunakan untuk Menampung Output
                        AES_InitializeByte(cipherByte, AES_BLOCK_SIZE);

                        // Baca Byte Pada File Sebanyak 16 Byte, Masukkan ke Variabel "plainByte"
                        long hasRead = 0;
                        for (int i = 0; i < AES_BLOCK_SIZE; i++)
                        {
                            // Jika Posisi Kursor Sudah mencapai Ukuran Variabel "X1", Maka Break!
                            if (i + cursorPosition == X1.Length) { break; }

                            plainByte[i] = X1[i + cursorPosition];
                            hasRead = i;
                        }

                        // Lakukan Proses Enkripsi "plainByte" dengan AES
                        Class_AES aes = new Class_AES(AESKeySize, keyByte, AESBlockSize);
                        aes.Go_Encrypt(plainByte, cipherByte);
                        //g.printDebug("[Encrypt] Byte Value [" + cursorPosition + "] \t: \"" + g.byteToString(plainByte) + "\"", text_log);
                        //g.printDebug("[Encrypt] Encrypted Byte Value [" + cursorPosition + "] \t: \"" + g.byteToString(cipherByte) + "\"", text_log);

                        // Copy Hasil Enkripsi ke Variabel "encryptedX1"
                        Buffer.BlockCopy(cipherByte, 0, encryptedX1, (int)cursorPosition, cipherByte.Length);

                        // Update Posisi Kursor pada File
                        cursorPosition += AES_BLOCK_SIZE;
                        //g.printDebug("[Encrypt] Encryption Progress \t: [" + cursorPosition + "] of [" + X1.Length + "]", text_log);
                        //status_process.Text = "Encrypting X1 Byte Array with AES [" + cursorPosition + "] / [" + X1.Length + "]";

                        // ---> [FILE] VARIABEL 4 - ENCRYPTED X1 <---
                        // Write --> Masukkan ke Output File [Size : Kelipatan 16 Bytes]
                        //Unused_outputStream.Write(cipherByte, 0, AES_BLOCK_SIZE);

                        // Jika pada File Sudah Tidak Ada yang Bisa Dibaca (EOF), Maka Break dari Loop!
                        if (0 >= hasRead)
                        {
                            General_UpdateLogText("[Encrypt] [Encrypted X1] Has Read (EOF) \t: " + hasRead);
                            break;
                        }
                    }

                    // Masukkan ke Sebuah File Debug
                    g.createDebugFile(inputFile, encryptedX1, "[E]_ENCRYPTED_X1");
                }
                catch
                {
                    return ErrorID.EncryptFailedAES;
                }

                General_UpdateLogText("[Encrypt] [Encrypted X1] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [6] Masukkan Hasil Enkripsi ke Output File
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 80/100 ===
                General_UpdateProgress("[80/100] Writing Output File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[80/100] Writing Output File\n" +
                    "==================================================");

                // Buat File Output
                FileStream outputStream = new FileStream(outputFile, FileMode.Append);
                outputStream.Write(fileExtByte);
                outputStream.Write(fileLengthByte);
                outputStream.Write(encryptedKeyByte);
                outputStream.Write(encryptedX1);
                outputStream.Close();

                General_UpdateLogText("[Encrypt] [Output File] Success!");

                // === Update 90/100 ===
                General_UpdateProgress("[90/100] Closing File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[90/100] Closing File\n" +
                    "==================================================");

                //Unused_outputStream.Close();
                inputStream.Close();

                General_UpdateLogText("[Encrypt] [Close File] Success!");

                // === Update 100 / 100 ===
                General_UpdateProgress("[100/100] Process Completed!");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[100/100] Process Completed!\n" +
                    "==================================================");
                General_UpdateLogText("[Encrypt] [Process] Success!");

                return ErrorID.Success;
            }
            catch (Exception exc)
            {
                MessageBox.Show("System Error :\n" + exc.Message, "System Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return ErrorID.OtherError;
            }
        }

        // Core Function - File Decrypt
        private ErrorID Core_Decrypt(string inputFile, string outputFile, string password)
        {
            try
            {
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [1] Inisialisasi Variabel & File Input Check (RE-CHECK!)
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 10/100 ===
                General_UpdateProgress("[10/100] Checking Input File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[10/100] Checking Input File\n" +
                    "==================================================");

                // Cek Apakah Ekstensi Adalah ".mx_encrypted"
                if (0 > inputFile.LastIndexOf(".mx_encrypted"))
                {
                    return ErrorID.OtherError;
                }

                // Baca File dan Masukkan ke Byte Array
                byte[] fileByte = File.ReadAllBytes(inputFile);

                // Buka File Input
                FileStream inputStream = new FileStream(inputFile, FileMode.Open);

                // Variabel Sebagai Penanda Sampai Mana File Telah Dibaca oleh Stream
                long cursorCheckpoint = 0;

                General_UpdateLogText("[Decrypt] [Input File] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                          [2] Baca File Length Info
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 20/100 ===
                General_UpdateProgress("[20/100] Reading File Info");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[20/100] Reading File Info\n" +
                    "==================================================");

                General_UpdateLogText("[Decrypt] [File Extension] Cursor Checkpoint \t: " + cursorCheckpoint);

                for (int i = (int)cursorCheckpoint; i < inputStream.Length; i++)
                {
                    byte[] temp = new byte[1];
                    temp[0] = (byte)inputStream.ReadByte();

                    // Jika Value pada Posisi Byte Sekarang = "|"
                    if (temp[0] == g.stringToByte("|")[0])
                    {
                        General_UpdateLogText("[Decrypt] [File Extension] Separator Position \t: [" + inputStream.Position + "] : " + g.byteToString(temp));

                        // Tentukan Read dari File Sebanyak Berapa Bytes dengan Rumus : [Posisi Kursor File Sekarang - Posisi Kursor File Awal - 1 (Byte Separator)]
                        long readCount = inputStream.Position - cursorCheckpoint - 1;
                        General_UpdateLogText("[Decrypt] [File Extension] Read Count \t\t: " + readCount);

                        // Inisialisasi Byte yang Akan Menampung File Extension
                        byte[] fileExtByte = new byte[readCount];
                        General_UpdateLogText("[Decrypt] [File Extension] Byte Size \t\t: " + fileExtByte.Length + " Bytes");

                        // Seek Stream Kembali ke Checkpoint Awal
                        inputStream.Seek(cursorCheckpoint, SeekOrigin.Begin);

                        // Read dari File Sebanyak "readCount", Kemudian Masukkan Hasil ke Variabel "fileExtByte"
                        inputStream.Read(fileExtByte, 0, (int)readCount);

                        // Konversi Byte ke String
                        string fileExtStr = g.byteToString(fileExtByte);
                        General_UpdateLogText("[Decrypt] [File Extension] Retrieved \t\t: " + fileExtStr);

                        // Seek 1x Lagi untuk Meng-Skip Separator "|" Agar Tidak Terbaca Stream Reader & Save Checkpoint
                        inputStream.Seek(1, SeekOrigin.Current);
                        cursorCheckpoint = inputStream.Position;

                        // Masukkan Data pada Variabel "fileExtStr" ke Variabel Global "decryptFileExtension"
                        decryptFileExtension = fileExtStr;
                        outputFile += decryptFileExtension;

                        // Cek File Output Sudah Ada atau Belum, Jika Sudah Ada -> Rename dengan Nama Baru
                        while (File.Exists(outputFile))
                        {
                            outputFile = g.renameIfNameAlreadyExists(outputFile);
                        }

                        General_UpdateOutText(outputFile);

                        break;
                    }
                }

                General_UpdateLogText("[Decrypt] [File Extension] Success!");

                // ---------------------------------------------------------------------------

                General_UpdateLogText("[Decrypt] [File Length] Cursor Checkpoint \t: " + cursorCheckpoint);

                for (int i = (int)cursorCheckpoint; i < inputStream.Length; i++)
                {
                    byte[] temp = new byte[1];
                    temp[0] = (byte)inputStream.ReadByte();

                    // Jika Value pada Posisi Byte Sekarang = "|"
                    if (temp[0] == g.stringToByte("|")[0])
                    {
                        General_UpdateLogText("[Decrypt] [File Length] Separator Position \t: [" + inputStream.Position + "] : " + g.byteToString(temp));

                        // Tentukan Read dari File Sebanyak Berapa Bytes dengan Rumus : [Posisi Kursor File Sekarang - Posisi Kursor File Awal - 1 (Byte Separator)]
                        long readCount = inputStream.Position - cursorCheckpoint - 1;
                        General_UpdateLogText("[Decrypt] [File Length] Read Count \t\t: " + readCount);

                        // Inisialisasi Byte yang Akan Menampung File Length
                        byte[] fileLengthByte = new byte[readCount];
                        General_UpdateLogText("[Decrypt] [File Length] Byte Size \t\t: " + fileLengthByte.Length + " Bytes");

                        // Seek Stream Kembali ke Checkpoint Awal
                        inputStream.Seek(cursorCheckpoint, SeekOrigin.Begin);

                        // Read dari File Sebanyak "readCount", Kemudian Masukkan Hasil ke Variabel "fileLengthByte"
                        inputStream.Read(fileLengthByte, 0, (int)readCount);

                        // Konversi Byte ke String
                        string fileLengthStr = g.byteToString(fileLengthByte);
                        General_UpdateLogText("[Decrypt] [File Length] Retrieved \t\t: " + fileLengthStr + " Bytes");

                        // Seek 1x Lagi untuk Meng-Skip Separator "|" Agar Tidak Terbaca Stream Reader & Save Checkpoint
                        inputStream.Seek(1, SeekOrigin.Current);
                        cursorCheckpoint = inputStream.Position;

                        // Masukkan Data pada Variabel "fileLengthByte" ke Variabel Global "decryptFileLength"
                        decryptFileLength = Convert.ToInt64(fileLengthStr);

                        break;
                    }
                }

                General_UpdateLogText("[Decrypt] [File Length] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [3] Decrypt AES Password dengan ElGamal
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 30/100 ===
                General_UpdateProgress("[30/100] Decrypting Password with ElGamal");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[30/100] Decrypting Password with ElGamal\n" +
                    "==================================================");

                General_UpdateLogText("[Decrypt] [Encrypted AES Password] Cursor Checkpoint \t: " + cursorCheckpoint);

                // Inisialisasi AES Parameter
                byte[] encryptedByte = new byte[AES_BLOCK_SIZE];
                byte[] decryptedByte = new byte[AES_BLOCK_SIZE];
                byte[] keyByte = new byte[AES_KEY_SIZE];

                // Get Password (Unused, Password Didapat dari Dekripsi ElGamal)
                keyByte = Encoding.ASCII.GetBytes(password);

                for (int i = (int)cursorCheckpoint; i < inputStream.Length; i++)
                {
                    byte[] temp = new byte[1];
                    temp[0] = (byte)inputStream.ReadByte();

                    // Jika Value pada Posisi Byte Sekarang = "|"
                    if (temp[0] == g.stringToByte("|")[0])
                    {
                        General_UpdateLogText("[Decrypt] [Encrypted AES Password] Separator Position \t: [" + inputStream.Position + "] : " + g.byteToString(temp));

                        // Tentukan Read dari File Sebanyak Berapa Bytes dengan Rumus : [Posisi Kursor File Sekarang - Posisi Kursor File Awal - 1 (Byte Separator)]
                        long readCount = inputStream.Position - cursorCheckpoint - 1;
                        General_UpdateLogText("[Decrypt] [Encrypted AES Password] Read Count \t\t: " + readCount);

                        // Inisialisasi Byte yang Akan Menampung Encrypted AES Password
                        byte[] encryptedPasswordByte = new byte[readCount];
                        General_UpdateLogText("[Decrypt] [Encrypted AES Password] Byte Size \t\t: " + encryptedPasswordByte.Length + " Bytes");

                        // Seek Stream Kembali ke Checkpoint Awal
                        inputStream.Seek(cursorCheckpoint, SeekOrigin.Begin);

                        // Read dari File Sebanyak "readCount", Kemudian Masukkan Hasil ke Variabel "encryptedPasswordByte"
                        inputStream.Read(encryptedPasswordByte, 0, (int)readCount);

                        // Konversi Byte ke String
                        string encryptedPasswordString = g.byteToString(encryptedPasswordByte);
                        General_UpdateLogText("[Decrypt] [Encrypted AES Password] Retrieved \t\t: \n\"" + encryptedPasswordString + "\"");

                        // Seek 1x Lagi untuk Meng-Skip Separator "|" Agar Tidak Terbaca Stream Reader & Save Checkpoint
                        inputStream.Seek(1, SeekOrigin.Current);
                        cursorCheckpoint = inputStream.Position;

                        // Masukkan ke Sebuah File Debug
                        g.createDebugFile(inputFile, encryptedPasswordByte, "[D]_ENCRYPTED_PASSWORD");

                        // Decrypt AES Password dengan ElGamal -> Client Private Key [p, x, a]
                        try
                        {
                            keyByte = g.stringToByte(egClient.Go_Decrypt(encryptedPasswordString, egClient.var_p, egClient.var_x, egClient.var_a));
                            General_UpdateLogText("[Decrypt] [AES Password] Result \t\t\t: \n\"" + g.byteToString(keyByte) + "\"");
                        }
                        catch
                        {
                            inputStream.Close();
                            return ErrorID.DecryptFailedElGamalPassword;
                        }

                        break;
                    }
                }

                General_UpdateLogText("[Decrypt] [AES Password] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //              [4] Decrypt Variabel "encryptedX1" dengan AES
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 40/100 ===
                General_UpdateProgress("[40/100] Decrypting File with AES (EncryptedX1)");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[40/100] Decrypting File with AES (EncryptedX1)\n" +
                    "==================================================");

                General_UpdateLogText("[Decrypt] [Encrypted X1] Cursor Checkpoint \t: " + cursorCheckpoint);

                // Variabel untuk Menampung Byte yang Telah Di Dekripsi
                long X1_BlockSize = inputStream.Length - cursorCheckpoint;
                byte[] encryptedX1 = new byte[X1_BlockSize];
                byte[] X1 = new byte[X1_BlockSize];
                General_UpdateLogText("[Decrypt] [Encrypted X1] Byte Size \t\t: " + encryptedX1.Length + " Bytes");

                // Copy Byte Array dari File ke Variabel "encryptedX1"
                Buffer.BlockCopy(fileByte, (int)cursorCheckpoint, encryptedX1, 0, (int)X1_BlockSize);

                // Decrypt Variabel "encryptedX1" dengan AES -> Password
                try
                {
                    // Set Posisi Awal Kursor Sebagai Penunjuk Posisi Baca pada Byte "encryptedX1"
                    long cursorPositionForEncryptedX1 = 0;

                    // Baca Semua Byte pada "encryptedX1", Kemudian Lakukan Enkripsi
                    while (cursorPositionForEncryptedX1 <= encryptedX1.Length)
                    {
                        // Inisialisasi Awal "encryptedByte" dengan Null Byte (0) dan dengan Ukuran 16 Byte, Digunakan untuk Menampung Input
                        AES_InitializeByte(encryptedByte, AES_BLOCK_SIZE);

                        // Inisialisasi Awal "decryptedByte" dengan Null Byte (0) dan dengan Ukuran 16 Byte, Digunakan untuk Menampung Output
                        AES_InitializeByte(decryptedByte, AES_BLOCK_SIZE);

                        // Baca Byte Pada File Sebanyak 16 Byte, Masukkan ke Variabel "encryptedByte"
                        long hasRead = 0;
                        for (int i = 0; i < AES_BLOCK_SIZE; i++)
                        {
                            // Jika Posisi Kursor Sudah mencapai Ukuran Variabel "encryptedX1", Maka Break!
                            if (i + cursorPositionForEncryptedX1 == encryptedX1.Length) { break; }

                            encryptedByte[i] = encryptedX1[i + cursorPositionForEncryptedX1];
                            hasRead = i;
                        }

                        // Jika pada File Sudah Tidak Ada yang Bisa Dibaca (EOF), Maka Break dari Loop!
                        if (0 >= hasRead)
                        {
                            General_UpdateLogText("[Decrypt] [Encrypted X1] Has Read (EOF) \t: " + hasRead);
                            break;
                        }

                        // Lakukan Proses Dekripsi "encryptedByte" dengan AES
                        Class_AES aes = new Class_AES(AESKeySize, keyByte, AESBlockSize);
                        aes.Go_Decrypt(encryptedByte, decryptedByte);
                        //g.printDebug("[Encrypt] [Encrypted X1] Byte Value [" + cursorPositionForEncryptedX1 + "] \t: \"" + g.byteToString(encryptedByte) + "\"", text_log);
                        //g.printDebug("[Encrypt] [X1] Byte Value [" + cursorPositionForEncryptedX1 + "] \t: \"" + g.byteToString(decryptedByte) + "\"", text_log);

                        // Copy Hasil Dekripsi ke Variabel "X1"
                        Buffer.BlockCopy(decryptedByte, 0, X1, (int)cursorPositionForEncryptedX1, decryptedByte.Length);

                        // Update Posisi Kursor pada File
                        cursorPositionForEncryptedX1 += AES_BLOCK_SIZE;
                        //g.printDebug("[Decrypt] Encryption Progress \t: [" + cursorPositionForX1 + "] of [" + encryptedX1.Length + "]");

                    }
                }
                catch
                {
                    inputStream.Close();
                    return ErrorID.DecryptFailedAES;
                }

                // Masukkan ke Sebuah File Debug
                g.createDebugFile(inputFile, X1, "[D]_DECRYPTED_X1");

                General_UpdateLogText("[Decrypt] [X1] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                      [5] Decrypt Hash dengan ElGamal
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 50/100 ===
                General_UpdateProgress("[50/100] Decrypting Hash with ElGamal");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[50/100] Decrypting Hash with ElGamal\n" +
                    "==================================================");

                // Set Posisi Awal Kursor Sebagai Penunjuk Posisi Baca pada Byte "X1"
                long cursorPositionForX1 = 0;

                for (int i = (int)cursorPositionForX1; i < X1.Length; i++)
                {
                    // Jika Value pada Posisi Byte Sekarang = "|"
                    if (X1[i] == g.stringToByte("|")[0])
                    {
                        General_UpdateLogText("[Decrypt] [Encrypted SHA3 Hash] Separator Position \t: [" + i + 1 + "] : " + g.byteToString(X1)[i]);

                        // Tentukan Read dari Variabel "X1" Sebanyak Berapa Bytes dengan Rumus : [Posisi Kursor Sekarang (i) - Posisi Kursor File Awal]
                        long readCount = i - cursorPositionForX1;
                        General_UpdateLogText("[Decrypt] [Encrypted SHA3 Hash] Read Count \t\t: " + readCount);

                        // Inisialisasi Byte yang Akan Menampung Encrypted Hash
                        byte[] encryptedHashByte = new byte[readCount];
                        General_UpdateLogText("[Decrypt] [Encrypted SHA3 Hash] Byte Size \t\t: " + encryptedHashByte.Length + " Bytes");

                        // Read dari File Sebanyak "readCount", Kemudian Masukkan Hasil ke Variabel "encryptedHashByte"
                        Buffer.BlockCopy(X1, (int)cursorPositionForX1, encryptedHashByte, 0, (int)readCount);

                        // Konversi Byte ke String
                        string encryptedHashString = g.byteToString(encryptedHashByte);
                        General_UpdateLogText("[Decrypt] [Encrypted Hash] Retrieved \t\t\t: \n\"" + encryptedHashString + "\"");

                        // Seek 1x Lagi untuk Meng-Skip Separator "|" Agar Tidak Terbaca Stream Reader & Save Checkpoint
                        //inputStream.Seek(1, SeekOrigin.Current);
                        cursorPositionForX1 = i + 1;

                        // Masukkan ke Sebuah File Debug
                        g.createDebugFile(inputFile, encryptedHashByte, "[D]_ENCRYPTED_HASH");

                        // Decrypt Encrypted Hash dengan ElGamal -> Host Public Key [p, x, a]
                        byte[] decryptedHashByte;
                        try
                        {
                            decryptedHashByte = g.stringToByte(egHost.Go_Decrypt(encryptedHashString, egHost.var_p, egHost.var_x, egHost.var_a));
                            General_UpdateLogText("[Decrypt] [SHA3 Hash] Retrieved \t\t\t: \n\"" + g.byteToString(decryptedHashByte) + "\"");
                        }
                        catch
                        {
                            inputStream.Close();
                            return ErrorID.DecryptFailedElGamalHash;
                        }

                        // Masukkan Data pada Variabel "decryptedHashByte" ke Variabel Global "decryptHashValue"
                        decryptHashValue = g.byteToString(decryptedHashByte);

                        break;
                    }
                }

                General_UpdateLogText("[Decrypt] [SHA3 Hash] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                  [6] Masukkan Hasil Dekripsi ke Output File
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 60/100 ===
                General_UpdateProgress("[60/100] Writing Output File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[60/100] Writing Output File\n" +
                    "==================================================");

                // Buat File Output
                FileStream outputStream = new FileStream(outputFile, FileMode.Append);

                // Masukkan Sisa Byte pada Variabel "X1" ke Variabel "finalFile"
                byte[] finalFile = new byte[decryptFileLength];
                Buffer.BlockCopy(X1, (int)cursorPositionForX1, finalFile, 0, (int)decryptFileLength);

                // ---> [FILE] VARIABEL 2 - FINAL FILE <---
                // Write --> Masukkan ke Output File [Size : <Kelipatan 16 Bytes - Padding> = decryptFileLength]
                outputStream.Write(finalFile);
                outputStream.Close();

                General_UpdateLogText("[Decrypt] [Output File] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //                      [7] Hash Output File Byte dengan SHA3
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 70/100 ===
                General_UpdateProgress("[70/100] Hashing Output File with SHA3");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[70/100] Hashing Output File with SHA3\n" +
                    "==================================================");

                // Buka File Output untuk di-Hash
                FileStream hashStream = new FileStream(outputFile, FileMode.Open);

                // Hash Output File
                string shaString = string.Empty;
                try
                {
                    shaString = SHA3_Hash(hashStream);
                    General_UpdateLogText("[Decrypt] [New SHA3 Hash] Retrieved \t: \n\"" + shaString + "\"");
                }
                catch
                {
                    inputStream.Close();
                    hashStream.Close();
                    return ErrorID.FailedSHA3;
                }

                General_UpdateLogText("[Decrypt] [New SHA3 Hash] Success!");

                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                //          [8] Bandingkan Hash Output File dengan Hash yang Didekripsi
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                // === Update 80/100 ===
                General_UpdateProgress("[80/100] Comparing Hash Value");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[80/100] Comparing Hash Value\n" +
                    "==================================================");

                General_UpdateLogText("[Decrypt] [Compare Hash] Decrypted \t: \n\"" + decryptHashValue + "\"");
                General_UpdateLogText("[Decrypt] [Compare Hash] New \t\t: \n\"" + shaString + "\"");

                // Bandingkan Variabel "shaString" dengan Variabel "decryptHashValue"
                sw.Stop();
                if (string.Equals(shaString, decryptHashValue))
                {
                    MessageBox.Show("Hash Value Matched, File is Authentic!", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else
                {
                    //MessageBox.Show("Hash Value Mismatched, File is NOT Authentic!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return ErrorID.HashMismatch;
                }
                sw.Start();

                General_UpdateLogText("[Decrypt] [Compare Hash] Success!");

                // === Update 90/100 ===
                General_UpdateProgress("[90/100] Closing File");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[90/100] Closing File\n" +
                    "==================================================");

                hashStream.Close();
                inputStream.Close();

                General_UpdateLogText("[Decrypt] [Close File] Success!");

                // === Update 100 / 100 ===
                General_UpdateProgress("[100/100] Process Completed!");
                General_UpdateLogText(
                    "\n==================================================\n" +
                    "[100/100] Process Completed!\n" +
                    "==================================================");
                General_UpdateLogText("[Decrypt] [Process] Success!");

                return ErrorID.Success;
            }
            catch (Exception exc)
            {
                MessageBox.Show("System Error :\n" + exc.Message, "System Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return ErrorID.OtherError;
            }
        }

        // Core Function - Run Main Process
        private void MainProcess_DoWork()
        {
            // Reset Progress Bar & Stopwatch
            status_progress_bar.Value = 0;

            sw.Restart();
            sw.Stop();

            // Start Stopwatch
            sw.Start();

            // ~ Get Parameters
            ErrorID programErrorID;
            string strOpenFile = text_input.Text;
            string strSaveFile = text_output.Text;
            string strPassword = text_password.Text;
            string strSender = text_host_key.Text;
            string strReceiver = text_client_key.Text;
            string errorString = string.Empty;

            //panel_form_container.Enabled = false;
            // If Mode = Encrypt
            if (Options.Encrypt == optEnDe)
            {
                errorString = "Encryption";
                programErrorID = Core_Encrypt(strOpenFile, strSaveFile, strPassword);
                General_UpdateLogText("[Encrypt] Error ID \t: " + programErrorID);
            }
            // If Mode = Decrypt
            else
            {
                errorString = "Decryption";
                programErrorID = Core_Decrypt(strOpenFile, strSaveFile, strPassword);
                General_UpdateLogText("[Decrypt] Error ID \t: " + programErrorID);
            }
            //panel_form_container.Enabled = true;

            // Stop Stopwatch
            sw.Stop();

            // Cek Apakah Enkripsi/Dekripsi Berhasil, Throw Error Jika Tidak Berhasil
            if (programErrorID != ErrorID.Success)
            {
                // Get Error Message
                errorString += " Process Failed :\n" + General_GetErrorMessage(programErrorID);
                MessageBox.Show(errorString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                status_progress_bar.Value = 0;
                General_InitializeAllComponents();
                return;
            }
            else
            {
                // Get Error Message
                errorString += " Process " + General_GetErrorMessage(programErrorID) + "\nTotal Time : " + status_timer.Text;
                General_UpdateLogText("Total Time \t: " + status_timer.Text);
                MessageBox.Show(errorString, "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

        }

        #endregion

        // ------------------------------------------------------------
        // Components - Form, Click Event, dan Komponen Lainnya
        // ------------------------------------------------------------
        #region Form Components

        // Initialize Form - Main Form
        public MainForm()
        {
            InitializeComponent();

            // Console
            AllocConsole();
        }

        // Enable Console
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        private static extern bool AllocConsole();

        // Load Event - Main Form
        private void MainForm_Load(object sender, EventArgs e)
        {
            General_InitializeAllComponents();
            optEnDe = Options.Encrypt;
        }

        // Button - Start [button_start]
        private void button_start_Click(object sender, EventArgs e)
        {
            ErrorID errorID;

            // 1. Check Error - Cek Apakah Semua Parameter Sudah Terisi & Semua File Ada
            errorID = General_CheckFields();
            if (errorID != ErrorID.Success)
            {
                string errorString = General_GetErrorMessage(errorID);
                MessageBox.Show(errorString, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            // 2. Cek File Host Key dan Client Key, Jika Ada Masukkan ke Variabel Key ElGamal
            errorID = General_RetrieveHostAndClientKey(egHost, text_host_key.Text, egClient, text_client_key.Text, optEnDe);
            if (errorID != ErrorID.Success)
            {
                string errorString = General_GetErrorMessage(errorID);
                MessageBox.Show(errorString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            /*
            if (optEnDe == Options.Encrypt)
            {
                MessageBox.Show("=== Host ===\np = " + egHost.var_p + "\ng = " + egHost.var_g + "\ny = " + egHost.var_y + "\nk = " + egHost.var_k);
                MessageBox.Show("=== Client ===\np = " + egClient.var_p + "\ng = " + egClient.var_g + "\ny = " + egClient.var_y + "\nk = " + egClient.var_k);
            }
            else
            {
                MessageBox.Show("=== Host ===\np = " + egHost.var_p + "\nx = " + egHost.var_x + "\na = " + egHost.var_a);
                MessageBox.Show("=== Client ===\np = " + egClient.var_p + "\nx = " + egClient.var_x + "\na = " + egClient.var_a);
            }
            */

            // 3. Cek File Output Sudah Ada atau Belum, Jika Sudah Ada -> Rename dengan Nama Baru
            while (File.Exists(text_output.Text))
            {
                text_output.Text = g.renameIfNameAlreadyExists(text_output.Text);
            }

            // 4. Algorithm - Diproses Melalui Background Worker atau Main Process
            // -----------------------------------------------------------
            if (setMainNotBackground == true)
            {
                MainProcess_DoWork();
            }
            else
            {
                background_worker_program.RunWorkerAsync();
            }
            // -----------------------------------------------------------

        }

        // Button - Encrypt [radio_decrypt]
        private void radio_encrypt_CheckedChanged(object sender, EventArgs e)
        {
            //EnDeCode.Text = this.EnOption.Text;
            optEnDe = Options.Encrypt;
            status_mode.Text = "Encrypt";
            General_ResetTextBoxAndVariables();
            General_ChangeParameterInfo(optEnDe);
            General_InitializeAllComponents();
        }

        // Button - Decrypt [radio_decrypt]
        private void radio_decrypt_CheckedChanged(object sender, EventArgs e)
        {
            //EnDeCode.Text = this.DeOption.Text;
            optEnDe = Options.Decrypt;
            status_mode.Text = "Decrypt";
            General_ResetTextBoxAndVariables();
            General_ChangeParameterInfo(optEnDe);
            General_InitializeAllComponents();
        }

        // Button - Browse [button_browse]
        private void button_browse_Click(object sender, EventArgs e)
        {
            OpenFileDialog fileBrowser = new OpenFileDialog();
            fileBrowser.Title = "SuperCryptEX - Browse File";

            // If Mode = Encrypt
            if (optEnDe == Options.Encrypt)
            {
                fileBrowser.Filter = "All File|*.*";
                if (fileBrowser.ShowDialog() == DialogResult.OK)
                {
                    inputPath = fileBrowser.FileName;
                    text_input.Text = inputPath;
                    string getExt = Path.GetExtension(inputPath);
                    string dummy = inputPath.Replace(getExt, "");
                    text_output.Text = dummy + ".mx_encrypted";

                    // Cek Jika File > 10240 KB (10 MB)
                    FileInfo fileInfo = new FileInfo(inputPath);
                    // File Size Default : Bytes
                    double fileLength_KB = fileInfo.Length / Math.Pow(1024, 1);
                    if (fileLength_KB > 10240)
                    {
                        MessageBox.Show("The File That You Choose is Too Large and It will Take a Long Time to Process! (This is Just a Warning)\n" +
                            "File Size : " + String.Format("{0:n}", fileLength_KB) + " KB\n" +
                            "(Expected Time : " + Math.Truncate(fileLength_KB * 0.0004).ToString() + "s)",
                            "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    }
                }
            }
            // If Mode = Decrypt
            else
            {
                fileBrowser.Filter = "MX Encrypted File|*.mx_encrypted";
                if (DialogResult.OK == fileBrowser.ShowDialog())
                {
                    inputPath = fileBrowser.FileName;
                    string encryptedName = inputPath;
                    text_input.Text = encryptedName;
                    int dotLastIndex = encryptedName.LastIndexOf(".");
                    string originalName = encryptedName.Substring(0, dotLastIndex);
                    text_output.Text = originalName;
                }
            }
        }

        // Button - Save [button_save]
        private void button_save_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveBrowser = new SaveFileDialog();
            saveBrowser.Title = "SuperCryptEX - Save To";

            // If Mode = Encrypt
            if (optEnDe == Options.Encrypt)
            {
                saveBrowser.Filter = "MX Encrypted File|*.mx_encrypted";
            }
            // If Mode = Decrypt
            else
            {
                saveBrowser.Filter = "All File|*.*";
            }

            DialogResult dialogResult = saveBrowser.ShowDialog();
            // Cek Apakah Nama File Bukan Empty String
            string fileName = saveBrowser.FileName;
            if (("" != fileName) && (dialogResult == DialogResult.OK))
            {
                text_output.Text = fileName;
            }
        }

        // Button - Host Key [button_host_key]
        private void button_host_key_Click(object sender, EventArgs e)
        {
            OpenFileDialog hostBrowser = new OpenFileDialog();
            if (optEnDe == Options.Encrypt)
            {
                hostBrowser.Title = "SuperCryptEX - Browse Host Private Key";
                hostBrowser.Filter = "MX Host Private Key (.mx_hpriv)|*.mx_hpriv";
            }
            else
            {
                hostBrowser.Title = "SuperCryptEX - Browse Host Public Key";
                hostBrowser.Filter = "MX Host Public Key (.mx_hpub)|*.mx_hpub";
            }

            if (DialogResult.OK == hostBrowser.ShowDialog())
            {
                hostKeyPath = hostBrowser.FileName;
                text_host_key.Text = hostKeyPath;
            }
        }

        // Button - Client Key [button_client_key]
        private void button_client_key_Click(object sender, EventArgs e)
        {
            OpenFileDialog clientBrowser = new OpenFileDialog();
            if (optEnDe == Options.Encrypt)
            {
                clientBrowser.Title = "SuperCryptEX - Browse Client Public Key";
                clientBrowser.Filter = "MX Client Public Key (.mx_cpub)|*.mx_cpub";
            }
            else
            {
                clientBrowser.Title = "SuperCryptEX - Browse Client Private Key";
                clientBrowser.Filter = "MX Client Private Key (.mx_cpriv)|*.mx_cpriv";
            }

            if (DialogResult.OK == clientBrowser.ShowDialog())
            {
                clientKeyPath = clientBrowser.FileName;
                text_client_key.Text = clientKeyPath;
            }
        }

        // Button - Check ElGamal Keys [button_check_key]
        private void button_check_key_Click(object sender, EventArgs e)
        {
            if (General_RetrieveHostAndClientKey(egHost, text_host_key.Text, egClient, text_client_key.Text, optEnDe) == ErrorID.Success)
            {
                if (optEnDe == Options.Encrypt)
                {
                    MessageBox.Show("=== Host Private Key ===\np = " + egHost.var_p + "\ng = " + egHost.var_g + "\ny = " + egHost.var_y + "\nk = " + egHost.var_k);
                    MessageBox.Show("=== Client Public Key ===\np = " + egClient.var_p + "\ng = " + egClient.var_g + "\ny = " + egClient.var_y + "\nk = " + egClient.var_k);
                }
                else
                {
                    MessageBox.Show("=== Host Public Key ===\np = " + egHost.var_p + "\nx = " + egHost.var_x + "\na = " + egHost.var_a);
                    MessageBox.Show("=== Client Private Key ===\np = " + egClient.var_p + "\nx = " + egClient.var_x + "\na = " + egClient.var_a);
                }
            }
            else
            {
                MessageBox.Show("Keypair Not Valid!");
            }
        }

        // Button - Enable Password Field [button_enable_pass]
        private void button_enable_pass_Click(object sender, EventArgs e)
        {
            text_password.Enabled = true;
        }

        // Component - Background Worker [background_worker_program] -> DO WORK <-
        private void background_worker_program_DoWork(object sender, DoWorkEventArgs e)
        {
            // ~ Start Stopwatch
            sw.Start();

            // -----------------------------------------------------------
            MainProcess_DoWork();
            // -----------------------------------------------------------

            // ~ Stop Stopwatch
            sw.Stop();
        }

        // Component - Background Worker [background_worker_program] -> PROGRESS CHANGED <-
        private void background_worker_program_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            status_progress_bar.Value += progressBarIterator;
        }

        // Component - Background Worker [background_worker_program] -> WORKER COMPLETED <-
        private void background_worker_program_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            /*
            string strOpt;
            if (Options.Encrypt == optEnDe)
            {
                strOpt = "Encryption";
            }
            else
            {
                strOpt = "Decryption";
            }
            MessageBox.Show(strOpt + " Process Completed!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            sw.Restart();
            sw.Stop();
            */
            sw.Stop();
            status_progress_bar.Value = 0;
        }

        // Component - Code Execution Timer [timer_program]
        private void timer_program_Tick(object sender, EventArgs e)
        {
            TimeSpan ts = sw.Elapsed;
            status_timer.Text = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }

        #endregion

        // ------------------------------------------------------------
        // Tool Strip Menu Item - Click Event untuk Tool Strip
        // ------------------------------------------------------------
        #region Tool Strip Components

        // Tool Strip Menu - Generate Key
        private void toolStripMenuItem_generateKey_Click(object sender, EventArgs e)
        {
            GenerateKeyForm gkForm = new GenerateKeyForm();
            gkForm.ShowDialog();
        }

        // Tool Strip Menu - Test AES
        private void toolStripMenuItem_testAES_Click(object sender, EventArgs e)
        {
            AESForm aesForm = new AESForm();
            aesForm.ShowDialog();
        }

        // Tool Strip Menu - Test SHA3
        private void toolStripMenuItem_testSHA3_Click(object sender, EventArgs e)
        {
            SHA3Form shaForm = new SHA3Form();
            shaForm.ShowDialog();
        }

        // Tool Strip Menu - Test ElGamal
        private void toolStripMenuItem_testElGamal_Click(object sender, EventArgs e)
        {
            ElGamalForm egForm = new ElGamalForm();
            egForm.ShowDialog();
        }

        // Tool Strip Menu - About
        private void toolStripMenuItem_about_Click(object sender, EventArgs e)
        {
            AboutSuperCryptEX aboutForm = new AboutSuperCryptEX();
            aboutForm.ShowDialog();
        }

        // Tool Strip Menu - Reset
        private void toolStripMenuItem_reset_Click(object sender, EventArgs e)
        {

        }

        // Tool Strip Menu - Exit
        private void toolStripMenuItem_exit_Click(object sender, EventArgs e)
        {
            Close();
        }

        #endregion

    }
}