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

namespace SuperCryptEX
{
    class Class_AES
    {
        #region Parameters

        // Parameters
        private int Nb;         // Block Size           Selalu 4            (128)-Bit
        private int Nk;         // Key Size             Antara 4, 6, 8      (128, 192, 256)-Bit
        private int Nr;         // Jumlah Round         Antara 10, 12, 14

        private byte[,] Sbox;   // Variabel Konstanta Substitution Box (S-Box)
        private byte[,] iSbox;  // Variabel Konstanta Inverse Substitution Box (Inv S-Box)
        private byte[,] Rcon;   // Variabel Konstanta Round Constants (Rcon)

        private byte[] key;     // Variabel untuk Menampung Key Bytes, Berukuran 4 x Key Size (4 x Nk)
        private byte[,] w;      // Variabel untuk Menampung Key Schedule (Word)
        private byte[,] State;  // Variabel untuk Menampung Input Bytes

        #endregion

        #region Constructor

        // AES Constructor
        public Class_AES(BlockSize keySize, byte[] keyBytes, BlockSize blockSize)
        {
            //Generate_NaNkNr();

            // Inisialisasi Nb, Nk, dan Nr
            Set_NbNkNr(keySize, blockSize);

            // Masukkan Password ke Variabel "key" yang Berukuran 16/24/32 Bytes
            key = new byte[Nk * 4];  // Nk = 4, 6, 8
            keyBytes.CopyTo(key, 0);

            // Generate Semua Konstanta
            Generate_Sbox();
            Generate_InvSbox();
            Generate_Rcon();

            // Lakukan Key Expansion menjadi Key Schedule dan Simpan pada Variabel "w"
            Process_KeyExpansion();
        }

        #endregion

        #region Core Functions

        // Constant Function - Matriks Konstanta S-Box (Dalam Bytes)
        private void Generate_Sbox()
        {
            Sbox = new byte[16, 16] {
     /*       0     1     2     3     4     5     6     7     8     9     a     b     c     d     e     f */
     /*0*/  {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76},
     /*1*/  {0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0},
     /*2*/  {0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15},
     /*3*/  {0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75},
     /*4*/  {0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84},
     /*5*/  {0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf},
     /*6*/  {0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8},
     /*7*/  {0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2},
     /*8*/  {0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73},
     /*9*/  {0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb},
     /*a*/  {0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79},
     /*b*/  {0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08},
     /*c*/  {0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a},
     /*d*/  {0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e},
     /*e*/  {0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf},
     /*f*/  {0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16} };
        }

        // Constant Function - Matriks Konstanta Invers S-Box (Dalam Bytes)
        private void Generate_InvSbox()
        {
            iSbox = new byte[16, 16] {
     /*       0     1     2     3     4     5     6     7     8     9     a     b     c     d     e     f */
     /*0*/  {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb},
     /*1*/  {0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb},
     /*2*/  {0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e},
     /*3*/  {0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25},
     /*4*/  {0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92},
     /*5*/  {0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84},
     /*6*/  {0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06},
     /*7*/  {0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b},
     /*8*/  {0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73},
     /*9*/  {0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e},
     /*a*/  {0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b},
     /*b*/  {0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4},
     /*c*/  {0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f},
     /*d*/  {0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef},
     /*e*/  {0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61},
     /*f*/  {0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d} };
        }

        // Constant Function - Matriks Konstanta Round Constants (Dalam Bytes)
        private void Generate_Rcon()
        {
            Rcon = new byte[11, 4] {
            {0x00, 0x00, 0x00, 0x00}, // 00
            {0x01, 0x00, 0x00, 0x00}, // 01
            {0x02, 0x00, 0x00, 0x00}, // 02
            {0x04, 0x00, 0x00, 0x00}, // 04
            {0x08, 0x00, 0x00, 0x00}, // 08
            {0x10, 0x00, 0x00, 0x00}, // 10
            {0x20, 0x00, 0x00, 0x00}, // 20
            {0x40, 0x00, 0x00, 0x00}, // 40
            {0x80, 0x00, 0x00, 0x00}, // 80
            {0x1b, 0x00, 0x00, 0x00}, // 1B
            {0x36, 0x00, 0x00, 0x00}  // 36
            };
        }

        // Core Function - Inisialisasi Nb, Nk, dan Nr
        private void Set_NbNkNr(BlockSize keySize, BlockSize blockSize)
        {
            // Set Ukuran Block (Nb -> 4)
            if (BlockSize.Bit_128 == blockSize)
            {
                Nb = 4;
            }
            else if (BlockSize.Bit_192 == blockSize)
            {
                Nb = 4;
            }
            else if (BlockSize.Bit_256 == blockSize)
            {
                Nb = 4;
            }

            // Set Ukuran Key & Round (Nk -> 4, 6, 8) (Nr -> 10, 12, 14)
            if (BlockSize.Bit_128 == keySize)
            {
                Nk = 4;
                Nr = 10;
            }
            else if (BlockSize.Bit_192 == keySize)
            {
                Nk = 6;
                Nr = 12;
            }
            else if (BlockSize.Bit_256 == keySize)
            {
                Nk = 8;
                Nr = 14;
            }
        }

        // Core Function - Encrypt 16-Byte Input
        public void Go_Encrypt(byte[] input, byte[] output)
        {
            // Masukkan 16 Bytes Input ke Sebuah State Array
            State = new byte[4, Nb];  // Selalu [4,4] -> 128-Bit (4 x 4 = 16 Bytes)
            for (int i = 0; i < (4 * Nb); ++i)
            {
                /*
                 * i = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
                 * Urutan Array : Kebawah -> (0,0) (1,0) (2,0) (3,0) ... (3,3)
                 * 
                 * State Array : 
                 * (0,0) (0,1) (0,2) (0,3)
                 * (1,0) (1,1) (1,2) (1,3)
                 * (2,0) (2,1) (2,2) (2,3)
                 * (3,0) (3,1) (3,2) (3,3)
                 */
                State[i % 4, i / 4] = input[i];
            }

            // Initial Process - AddRoundKey untuk Round ke-0
            Process_AddRoundKey(0);

            // Round Process (Dilakukan dari [1] Hingga [Nr - 1])
            for (int round = 1; round <= (Nr - 1); ++round)
            {
                Process_SubBytes();
                Process_ShiftRows();
                Process_MixColumns();
                Process_AddRoundKey(round);
            }

            // Final Process
            Process_SubBytes();
            Process_ShiftRows();
            Process_AddRoundKey(Nr);

            // Write ke Output Byte
            for (int i = 0; i < (4 * Nb); ++i)
            {
                output[i] = State[i % 4, i / 4];
            }
        }

        // Core Function - Decrypt 16-Byte Input
        public void Go_Decrypt(byte[] input, byte[] output)  // decipher 16-bit input
        {
            // Masukkan 16 Bytes Input ke Sebuah State Array
            State = new byte[4, Nb];  // Selalu [4,4] -> 128-Bit (4 x 4 = 16 Bytes)
            for (int i = 0; i < (4 * Nb); ++i)
            {
                State[i % 4, i / 4] = input[i];
            }

            // Initial Process - AddRoundKey untuk Round ke-Nr
            Process_AddRoundKey(Nr);

            // Round Process (Dilakukan dari [Nr - 1] Hingga [1]) -> Iterasi Mundur
            for (int round = Nr - 1; round >= 1; --round)
            {
                Process_InvShiftRows();
                Process_InvSubBytes();
                Process_AddRoundKey(round);
                Process_InvMixColumns();
            }

            // Final Process
            Process_InvShiftRows();
            Process_InvSubBytes();
            Process_AddRoundKey(0);

            // Write ke Output Byte
            for (int i = 0; i < (4 * Nb); ++i)
            {
                output[i] = State[i % 4, i / 4];
            }
        }

        #endregion

        #region Functions

        // Process Function - Add Round Key
        private void Process_AddRoundKey(int round)
        {
            // Meng-XORkan State dengan Key pada Round Tersebut (Dilakukan Setiap Round dan Sebanyak Nr)
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    // State         =      State  (XOR)  Word(Round)
                    State[i, j] = (byte)(State[i, j] ^ w[(round * 4) + j, i]);
                }
            }
        }

        // Process Function - Sub Bytes
        private void Process_SubBytes()
        {
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    // Lakukan Substitusi dengan S-Box
                    State[i, j] = Sbox[(State[i, j] >> 4), (State[i, j] & 0x0f)];
                }
            }
        }

        // Process Function - Inverse Sub Bytes
        private void Process_InvSubBytes()
        {
            for (int r = 0; r < 4; ++r)
            {
                for (int c = 0; c < 4; ++c)
                {
                    // Lakukan Substitusi dengan Invers S-Box
                    State[r, c] = iSbox[(State[r, c] >> 4), (State[r, c] & 0x0f)];
                }
            }
        }

        // Process Function - Shift Rows
        private void Process_ShiftRows()
        {
            // Copy State ke Temp Array
            byte[,] temp = new byte[4, 4];
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    temp[i, j] = State[i, j];
                }
            }

            // Shift Temp dan Masukkan ke State
            for (int i = 1; i < 4; ++i)  // Mulai dari 1, Karena Row 0 Tidak Dilakukan Pergeseran
            {
                for (int j = 0; j < 4; ++j)
                {
                    /* Info :   (i),(j + i % Nb)
                     * Row 1 -> (1),(1 + 0 % 4 = 1) | (1),(1 + 1 % 4 = 2) | (1),(1 + 2 % 4 = 3) | (1),(1 + 3 % 4 = 0)
                     * Row 2 -> (2),(2 + 0 % 4 = 2) | (2),(2 + 1 % 4 = 3) | (2),(2 + 2 % 4 = 0) | (2),(2 + 3 % 4 = 1)
                     * Row 3 -> (3),(3 + 0 % 4 = 3) | (3),(3 + 1 % 4 = 0) | (3),(3 + 2 % 4 = 1) | (3),(3 + 3 % 4 = 2)
                     * 
                     * Jadi, Tempat Baru State akan Ditempati Temp pada Posisi :
                     * (0,0), (0,1), (0,2), (0,3)
                     * (1,1), (1,2), (1,3), (1,0)
                     * (2,2), (2,3), (2,0), (2,1)
                     * (3,3), (3,0), (3,1), (3,2)
                     */
                    State[i, j] = temp[i, (j + i) % Nb]; // Nb = 4
                }
            }
        }

        // Process Function - Inverse Shift Rows
        private void Process_InvShiftRows()
        {
            // Copy State ke Temp Array
            byte[,] temp = new byte[4, 4];
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    temp[i, j] = State[i, j];
                }
            }

            // Shift Temp dan Masukkan ke State
            for (int i = 1; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    // Kebalikan dari Shift Rows
                    State[i, (j + i) % Nb] = temp[i, j];
                }
            }
        }

        // Process Function - Mix Columns
        private void Process_MixColumns()
        {
            // Copy State ke Temp Array
            byte[,] temp = new byte[4, 4];
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    temp[i, j] = State[i, j];
                }
            }

            // Lakukan Perkalian
            for (int j = 0; j < 4; ++j)
            {
                /* Matriks Galois Field :
                 * 02 03 01 01
                 * 01 02 03 01
                 * 01 01 02 03
                 * 03 01 01 02
                 */

                // Perkalian State dengan Matriks Galois Field :
                //                               x02                        x03                        x01                        x01
                State[0, j] = (byte)(gFieldMul_02(temp[0, j]) ^ gFieldMul_03(temp[1, j]) ^ gFieldMul_01(temp[2, j]) ^ gFieldMul_01(temp[3, j]));
                //                               x01                        x02                        x03                        x01
                State[1, j] = (byte)(gFieldMul_01(temp[0, j]) ^ gFieldMul_02(temp[1, j]) ^ gFieldMul_03(temp[2, j]) ^ gFieldMul_01(temp[3, j]));
                //                               x01                        x01                        x02                        x03
                State[2, j] = (byte)(gFieldMul_01(temp[0, j]) ^ gFieldMul_01(temp[1, j]) ^ gFieldMul_02(temp[2, j]) ^ gFieldMul_03(temp[3, j]));
                //                               x03                        x01                        x01                        x02
                State[3, j] = (byte)(gFieldMul_03(temp[0, j]) ^ gFieldMul_01(temp[1, j]) ^ gFieldMul_01(temp[2, j]) ^ gFieldMul_02(temp[3, j]));
            }
        }

        // Process Function - Inverse Mix Columns
        private void Process_InvMixColumns()
        {
            // Copy State ke Temp Array
            byte[,] temp = new byte[4, 4];
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    temp[i, j] = State[i, j];
                }
            }

            // Lakukan Perkalian
            for (int j = 0; j < 4; ++j)
            {
                /* Invers Matriks Galois Field :
                 * 0E 0B 0D 09
                 * 09 0E 0B 0D
                 * 0D 09 0E 0B
                 * 0B 0D 09 0E
                 */

                // Perkalian State dengan Invers Matriks Galois Field :
                //                               x0e                        x0b                        x0d                        x09
                State[0, j] = (byte)(gFieldMul_0e(temp[0, j]) ^ gFieldMul_0b(temp[1, j]) ^ gFieldMul_0d(temp[2, j]) ^ gFieldMul_09(temp[3, j]));
                //                               x09                        x0e                        x0b                        x0d
                State[1, j] = (byte)(gFieldMul_09(temp[0, j]) ^ gFieldMul_0e(temp[1, j]) ^ gFieldMul_0b(temp[2, j]) ^ gFieldMul_0d(temp[3, j]));
                //                               x0d                        x09                        x0e                        x0b
                State[2, j] = (byte)(gFieldMul_0d(temp[0, j]) ^ gFieldMul_09(temp[1, j]) ^ gFieldMul_0e(temp[2, j]) ^ gFieldMul_0b(temp[3, j]));
                //                               x0b                        x0d                        x09                        x0e
                State[3, j] = (byte)(gFieldMul_0b(temp[0, j]) ^ gFieldMul_0d(temp[1, j]) ^ gFieldMul_09(temp[2, j]) ^ gFieldMul_0e(temp[3, j]));
            }
        }

        // Process Function - Key Expansion
        private void Process_KeyExpansion()
        {
            /* Inisialisasi Variabel Word (w) dengan :
             * Kolom (Ke Samping) = 4 Kolom (Selalu 4)
             * Baris (Ke Bawah)   = Nb x (Nr + 1) -> Misal Key = 256-Bit -> Baris = 4 x (14 + 1) = 60 Baris
             */
            w = new byte[Nb * (Nr + 1), 4];

            // Round Pertama Word Merupakan Byte Password Itu Sendiri
            // Row = 0
            for (int row = 0; row < Nk; row++) // Range = 0 - 7                     -> Nk = 8 (256-Bit)
            {
                w[row, 0] = key[4 * row];
                w[row, 1] = key[4 * row + 1];
                w[row, 2] = key[4 * row + 2];
                w[row, 3] = key[4 * row + 3];
            }


            // Round ke (Nk) Hingga (Nb x (Nr + 1)) Word Merupakan Hasil Ekspansi Kunci
            // Row = Nk (4, 6, 8)
            byte[] temp = new byte[4];
            for (int row = Nk; row < Nb * (Nr + 1); row++) // Range = 8 - 59        -> Nb x (Nr + 1) = 60 (256-Bit)
            {
                // Temp = W[Row - 1]
                temp[0] = w[row - 1, 0];
                temp[1] = w[row - 1, 1];
                temp[2] = w[row - 1, 2];
                temp[3] = w[row - 1, 3];

                if (row % Nk == 0)
                {
                    // Temp = Sub(Rot(Temp) XOR Rcon[Row / Nk]
                    temp = SubstituteWord(RotateWord(temp));

                    temp[0] = (byte)(temp[0] ^ Rcon[row / Nk, 0]);
                    temp[1] = (byte)(temp[1] ^ Rcon[row / Nk, 1]);
                    temp[2] = (byte)(temp[2] ^ Rcon[row / Nk, 2]);
                    temp[3] = (byte)(temp[3] ^ Rcon[row / Nk, 3]);
                }
                else if (Nk > 6 && (row % Nk == 4))
                {
                    temp = SubstituteWord(temp);
                }

                // W[Row] = W[Row - Nk] XOR Temp
                w[row, 0] = (byte)(w[row - Nk, 0] ^ temp[0]);
                w[row, 1] = (byte)(w[row - Nk, 1] ^ temp[1]);
                w[row, 2] = (byte)(w[row - Nk, 2] ^ temp[2]);
                w[row, 3] = (byte)(w[row - Nk, 3] ^ temp[3]);
            }
        }

        // Function - Perkalian Matriks Galois Field Multiplication
        private static byte gFieldMul_01(byte b)
        {
            return b;
        }
        private static byte gFieldMul_02(byte b)
        {
            if (b < 0x80)
                return (byte)(b << 1);
            else
                return (byte)(b << 1 ^ 0x1b);
        }
        private static byte gFieldMul_03(byte b)
        {
            return (byte)(gFieldMul_02(b) ^ b);
        }
        private static byte gFieldMul_09(byte b)
        {
            return (byte)(gFieldMul_02(gFieldMul_02(gFieldMul_02(b))) ^
                          b);
        }
        private static byte gFieldMul_0b(byte b)
        {
            return (byte)(gFieldMul_02(gFieldMul_02(gFieldMul_02(b))) ^
                          gFieldMul_02(b) ^
                          b);
        }
        private static byte gFieldMul_0d(byte b)
        {
            return (byte)(gFieldMul_02(gFieldMul_02(gFieldMul_02(b))) ^
                          gFieldMul_02(gFieldMul_02(b)) ^
                          b);
        }
        private static byte gFieldMul_0e(byte b)
        {
            return (byte)(gFieldMul_02(gFieldMul_02(gFieldMul_02(b))) ^
                          gFieldMul_02(gFieldMul_02(b)) ^
                          gFieldMul_02(b));
        }

        // Function - Substitusi Word
        private byte[] SubstituteWord(byte[] word)
        {
            byte[] result = new byte[4];
            result[0] = Sbox[word[0] >> 4, word[0] & 0x0f];
            result[1] = Sbox[word[1] >> 4, word[1] & 0x0f];
            result[2] = Sbox[word[2] >> 4, word[2] & 0x0f];
            result[3] = Sbox[word[3] >> 4, word[3] & 0x0f];
            return result;
        }

        // Function - Rotasi Word
        private byte[] RotateWord(byte[] word)
        {
            byte[] result = new byte[4];
            result[0] = word[1];
            result[1] = word[2];
            result[2] = word[3];
            result[3] = word[0];
            return result;
        }

        #endregion

    }
}
