using System;

using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Engines
{
    /**
    * an implementation of Rijndael, based on the documentation and reference implementation
    * by Paulo Barreto, Vincent Rijmen, for v2.0 August '99.
    * <p>
    * Note: this implementation is based on information prior to readonly NIST publication.
	* </p>
    */
    public class RijndaelEngine
		: IBlockCipher
    {
        private static readonly int MAXROUNDS = 14;

        private static readonly int MAXKC = (256/4);

        private static readonly byte[] Logtable = {
            (byte)0,    (byte)0,    (byte)25,   (byte)1,    (byte)50,   (byte)2,    (byte)26,   (byte)198,
            (byte)75,   (byte)199,  (byte)27,   (byte)104,  (byte)51,   (byte)238,  (byte)223,  (byte)3,
            (byte)100,  (byte)4,    (byte)224,  (byte)14,   (byte)52,   (byte)141,  (byte)129,  (byte)239,
            (byte)76,   (byte)113,  (byte)8,    (byte)200,  (byte)248,  (byte)105,  (byte)28,   (byte)193,
            (byte)125,  (byte)194,  (byte)29,   (byte)181,  (byte)249,  (byte)185,  (byte)39,   (byte)106,
            (byte)77,   (byte)228,  (byte)166,  (byte)114,  (byte)154,  (byte)201,  (byte)9,    (byte)120,
            (byte)101,  (byte)47,   (byte)138,  (byte)5,    (byte)33,   (byte)15,   (byte)225,  (byte)36,
            (byte)18,   (byte)240,  (byte)130,  (byte)69,   (byte)53,   (byte)147,  (byte)218,  (byte)142,
            (byte)150,  (byte)143,  (byte)219,  (byte)189,  (byte)54,   (byte)208,  (byte)206,  (byte)148,
            (byte)19,   (byte)92,   (byte)210,  (byte)241,  (byte)64,   (byte)70,   (byte)131,  (byte)56,
            (byte)102,  (byte)221,  (byte)253,  (byte)48,   (byte)191,  (byte)6,    (byte)139,  (byte)98,
            (byte)179,  (byte)37,   (byte)226,  (byte)152,  (byte)34,   (byte)136,  (byte)145,  (byte)16,
            (byte)126,  (byte)110,  (byte)72,   (byte)195,  (byte)163,  (byte)182,  (byte)30,   (byte)66,
            (byte)58,   (byte)107,  (byte)40,   (byte)84,   (byte)250,  (byte)133,  (byte)61,   (byte)186,
            (byte)43,   (byte)121,  (byte)10,   (byte)21,   (byte)155,  (byte)159,  (byte)94,   (byte)202,
            (byte)78,   (byte)212,  (byte)172,  (byte)229,  (byte)243,  (byte)115,  (byte)167,  (byte)87,
            (byte)175,  (byte)88,   (byte)168,  (byte)80,   (byte)244,  (byte)234,  (byte)214,  (byte)116,
            (byte)79,   (byte)174,  (byte)233,  (byte)213,  (byte)231,  (byte)230,  (byte)173,  (byte)232,
            (byte)44,   (byte)215,  (byte)117,  (byte)122,  (byte)235,  (byte)22,   (byte)11,   (byte)245,
            (byte)89,   (byte)203,  (byte)95,   (byte)176,  (byte)156,  (byte)169,  (byte)81,   (byte)160,
            (byte)127,  (byte)12,   (byte)246,  (byte)111,  (byte)23,   (byte)196,  (byte)73,   (byte)236,
            (byte)216,  (byte)67,   (byte)31,   (byte)45,   (byte)164,  (byte)118,  (byte)123,  (byte)183,
            (byte)204,  (byte)187,  (byte)62,   (byte)90,   (byte)251,  (byte)96,   (byte)177,  (byte)134,
            (byte)59,   (byte)82,   (byte)161,  (byte)108,  (byte)170,  (byte)85,   (byte)41,   (byte)157,
            (byte)151,  (byte)178,  (byte)135,  (byte)144,  (byte)97,   (byte)190,  (byte)220,  (byte)252,
            (byte)188,  (byte)149,  (byte)207,  (byte)205,  (byte)55,   (byte)63,   (byte)91,   (byte)209,
            (byte)83,   (byte)57,   (byte)132,  (byte)60,   (byte)65,   (byte)162,  (byte)109,  (byte)71,
            (byte)20,   (byte)42,   (byte)158,  (byte)93,   (byte)86,   (byte)242,  (byte)211,  (byte)171,
            (byte)68,   (byte)17,   (byte)146,  (byte)217,  (byte)35,   (byte)32,   (byte)46,   (byte)137,
            (byte)180,  (byte)124,  (byte)184,  (byte)38,   (byte)119,  (byte)153,  (byte)227,  (byte)165,
            (byte)103,  (byte)74,   (byte)237,  (byte)222,  (byte)197,  (byte)49,   (byte)254,  (byte)24,
            (byte)13,   (byte)99,   (byte)140,  (byte)128,  (byte)192,  (byte)247,  (byte)112,  (byte)7
        };

        private static readonly byte[] Alogtable = {
            (byte)0,   (byte)3,   (byte)5,  (byte)15,  (byte)17,  (byte)51,  (byte)85, (byte)255,  (byte)26,  (byte)46, (byte)114, (byte)150, (byte)161, (byte)248,  (byte)19,  (byte)53,
            (byte)95, (byte)225,  (byte)56,  (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247,   (byte)2,   (byte)6,  (byte)10,  (byte)30,  (byte)34, (byte)102, (byte)170,
            (byte)229,  (byte)52,  (byte)92, (byte)228,  (byte)55,  (byte)89, (byte)235,  (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230,  (byte)49,
            (byte)83, (byte)245,   (byte)4,  (byte)12,  (byte)20,  (byte)60,  (byte)68, (byte)204,  (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205,
            (byte)76, (byte)212, (byte)103, (byte)169, (byte)224,  (byte)59,  (byte)77, (byte)215,  (byte)98, (byte)166, (byte)241,   (byte)8,  (byte)24,  (byte)40, (byte)120, (byte)136,
            (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206,  (byte)73, (byte)219, (byte)118, (byte)154,
            (byte)181, (byte)196,  (byte)87, (byte)249,  (byte)16,  (byte)48,  (byte)80, (byte)240,  (byte)11,  (byte)29,  (byte)39, (byte)105, (byte)187, (byte)214,  (byte)97, (byte)163,
            (byte)254,  (byte)25,  (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236,  (byte)47, (byte)113, (byte)147, (byte)174, (byte)233,  (byte)32,  (byte)96, (byte)160,
            (byte)251,  (byte)22,  (byte)58,  (byte)78, (byte)210, (byte)109, (byte)183, (byte)194,  (byte)93, (byte)231,  (byte)50,  (byte)86, (byte)250,  (byte)21,  (byte)63,  (byte)65,
            (byte)195,  (byte)94, (byte)226,  (byte)61,  (byte)71, (byte)201,  (byte)64, (byte)192,  (byte)91, (byte)237,  (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117,
            (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239,  (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128,
            (byte)155, (byte)182, (byte)193,  (byte)88, (byte)232,  (byte)35, (byte)101, (byte)175, (byte)234,  (byte)37, (byte)111, (byte)177, (byte)200,  (byte)67, (byte)197,  (byte)84,
            (byte)252,  (byte)31,  (byte)33,  (byte)99, (byte)165, (byte)244,   (byte)7,   (byte)9,  (byte)27,  (byte)45, (byte)119, (byte)153, (byte)176, (byte)203,  (byte)70, (byte)202,
            (byte)69, (byte)207,  (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227,  (byte)62,  (byte)66, (byte)198,  (byte)81, (byte)243,  (byte)14,
            (byte)18,  (byte)54,  (byte)90, (byte)238,  (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242,  (byte)13,  (byte)23,
            (byte)57,  (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253,  (byte)28,  (byte)36, (byte)108, (byte)180, (byte)199,  (byte)82, (byte)246,   (byte)1,
            (byte)3,   (byte)5,  (byte)15,  (byte)17,  (byte)51,  (byte)85, (byte)255,  (byte)26,  (byte)46, (byte)114, (byte)150, (byte)161, (byte)248,  (byte)19,  (byte)53,
            (byte)95, (byte)225,  (byte)56,  (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247,   (byte)2,   (byte)6,  (byte)10,  (byte)30,  (byte)34, (byte)102, (byte)170,
            (byte)229,  (byte)52,  (byte)92, (byte)228,  (byte)55,  (byte)89, (byte)235,  (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230,  (byte)49,
            (byte)83, (byte)245,   (byte)4,  (byte)12,  (byte)20,  (byte)60,  (byte)68, (byte)204,  (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205,
            (byte)76, (byte)212, (byte)103, (byte)169, (byte)224,  (byte)59,  (byte)77, (byte)215,  (byte)98, (byte)166, (byte)241,   (byte)8,  (byte)24,  (byte)40, (byte)120, (byte)136,
            (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206,  (byte)73, (byte)219, (byte)118, (byte)154,
            (byte)181, (byte)196,  (byte)87, (byte)249,  (byte)16,  (byte)48,  (byte)80, (byte)240,  (byte)11,  (byte)29,  (byte)39, (byte)105, (byte)187, (byte)214,  (byte)97, (byte)163,
            (byte)254,  (byte)25,  (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236,  (byte)47, (byte)113, (byte)147, (byte)174, (byte)233,  (byte)32,  (byte)96, (byte)160,
            (byte)251,  (byte)22,  (byte)58,  (byte)78, (byte)210, (byte)109, (byte)183, (byte)194,  (byte)93, (byte)231,  (byte)50,  (byte)86, (byte)250,  (byte)21,  (byte)63,  (byte)65,
            (byte)195,  (byte)94, (byte)226,  (byte)61,  (byte)71, (byte)201,  (byte)64, (byte)192,  (byte)91, (byte)237,  (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117,
            (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239,  (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128,
            (byte)155, (byte)182, (byte)193,  (byte)88, (byte)232,  (byte)35, (byte)101, (byte)175, (byte)234,  (byte)37, (byte)111, (byte)177, (byte)200,  (byte)67, (byte)197,  (byte)84,
            (byte)252,  (byte)31,  (byte)33,  (byte)99, (byte)165, (byte)244,   (byte)7,   (byte)9,  (byte)27,  (byte)45, (byte)119, (byte)153, (byte)176, (byte)203,  (byte)70, (byte)202,
            (byte)69, (byte)207,  (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227,  (byte)62,  (byte)66, (byte)198,  (byte)81, (byte)243,  (byte)14,
            (byte)18,  (byte)54,  (byte)90, (byte)238,  (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242,  (byte)13,  (byte)23,
            (byte)57,  (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253,  (byte)28,  (byte)36, (byte)108, (byte)180, (byte)199,  (byte)82, (byte)246,   (byte)1,
            };

        private static readonly byte[] S = {
            (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197,  (byte)48,   (byte)1, (byte)103,  (byte)43, (byte)254, (byte)215, (byte)171, (byte)118,
            (byte)202, (byte)130, (byte)201, (byte)125, (byte)250,  (byte)89,  (byte)71, (byte)240, (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192,
            (byte)183, (byte)253, (byte)147,  (byte)38,  (byte)54,  (byte)63, (byte)247, (byte)204,  (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216,  (byte)49,  (byte)21,
            (byte)4, (byte)199,  (byte)35, (byte)195,  (byte)24, (byte)150,   (byte)5, (byte)154,   (byte)7,  (byte)18, (byte)128, (byte)226, (byte)235,  (byte)39, (byte)178, (byte)117,
            (byte)9, (byte)131,  (byte)44,  (byte)26,  (byte)27, (byte)110,  (byte)90, (byte)160,  (byte)82,  (byte)59, (byte)214, (byte)179,  (byte)41, (byte)227,  (byte)47, (byte)132,
            (byte)83, (byte)209,   (byte)0, (byte)237,  (byte)32, (byte)252, (byte)177,  (byte)91, (byte)106, (byte)203, (byte)190,  (byte)57,  (byte)74,  (byte)76,  (byte)88, (byte)207,
            (byte)208, (byte)239, (byte)170, (byte)251,  (byte)67,  (byte)77,  (byte)51, (byte)133,  (byte)69, (byte)249,   (byte)2, (byte)127,  (byte)80,  (byte)60, (byte)159, (byte)168,
            (byte)81, (byte)163,  (byte)64, (byte)143, (byte)146, (byte)157,  (byte)56, (byte)245, (byte)188, (byte)182, (byte)218,  (byte)33,  (byte)16, (byte)255, (byte)243, (byte)210,
            (byte)205,  (byte)12,  (byte)19, (byte)236,  (byte)95, (byte)151,  (byte)68,  (byte)23, (byte)196, (byte)167, (byte)126,  (byte)61, (byte)100,  (byte)93,  (byte)25, (byte)115,
            (byte)96, (byte)129,  (byte)79, (byte)220,  (byte)34,  (byte)42, (byte)144, (byte)136,  (byte)70, (byte)238, (byte)184,  (byte)20, (byte)222,  (byte)94,  (byte)11, (byte)219,
            (byte)224,  (byte)50,  (byte)58,  (byte)10,  (byte)73,   (byte)6,  (byte)36,  (byte)92, (byte)194, (byte)211, (byte)172,  (byte)98, (byte)145, (byte)149, (byte)228, (byte)121,
            (byte)231, (byte)200,  (byte)55, (byte)109, (byte)141, (byte)213,  (byte)78, (byte)169, (byte)108,  (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174,   (byte)8,
            (byte)186, (byte)120,  (byte)37,  (byte)46,  (byte)28, (byte)166, (byte)180, (byte)198, (byte)232, (byte)221, (byte)116,  (byte)31,  (byte)75, (byte)189, (byte)139, (byte)138,
            (byte)112,  (byte)62, (byte)181, (byte)102,  (byte)72,   (byte)3, (byte)246,  (byte)14,  (byte)97,  (byte)53,  (byte)87, (byte)185, (byte)134, (byte)193,  (byte)29, (byte)158,
            (byte)225, (byte)248, (byte)152,  (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, (byte)155,  (byte)30, (byte)135, (byte)233, (byte)206,  (byte)85,  (byte)40, (byte)223,
            (byte)140, (byte)161, (byte)137,  (byte)13, (byte)191, (byte)230,  (byte)66, (byte)104,  (byte)65, (byte)153,  (byte)45,  (byte)15, (byte)176,  (byte)84, (byte)187,  (byte)22,
        };

        private static readonly byte[] Si = {
            (byte)82,   (byte)9, (byte)106, (byte)213,  (byte)48,  (byte)54, (byte)165,  (byte)56, (byte)191,  (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251,
            (byte)124, (byte)227,  (byte)57, (byte)130, (byte)155,  (byte)47, (byte)255, (byte)135,  (byte)52, (byte)142,  (byte)67,  (byte)68, (byte)196, (byte)222, (byte)233, (byte)203,
            (byte)84, (byte)123, (byte)148,  (byte)50, (byte)166, (byte)194,  (byte)35,  (byte)61, (byte)238,  (byte)76, (byte)149,  (byte)11,  (byte)66, (byte)250, (byte)195,  (byte)78,
            (byte)8,  (byte)46, (byte)161, (byte)102,  (byte)40, (byte)217,  (byte)36, (byte)178, (byte)118,  (byte)91, (byte)162,  (byte)73, (byte)109, (byte)139, (byte)209,  (byte)37,
            (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152,  (byte)22, (byte)212, (byte)164,  (byte)92, (byte)204,  (byte)93, (byte)101, (byte)182, (byte)146,
            (byte)108, (byte)112,  (byte)72,  (byte)80, (byte)253, (byte)237, (byte)185, (byte)218,  (byte)94,  (byte)21,  (byte)70,  (byte)87, (byte)167, (byte)141, (byte)157, (byte)132,
            (byte)144, (byte)216, (byte)171,   (byte)0, (byte)140, (byte)188, (byte)211,  (byte)10, (byte)247, (byte)228,  (byte)88,   (byte)5, (byte)184, (byte)179,  (byte)69,   (byte)6,
            (byte)208,  (byte)44,  (byte)30, (byte)143, (byte)202,  (byte)63,  (byte)15,   (byte)2, (byte)193, (byte)175, (byte)189,   (byte)3,   (byte)1,  (byte)19, (byte)138, (byte)107,
            (byte)58, (byte)145,  (byte)17,  (byte)65,  (byte)79, (byte)103, (byte)220, (byte)234, (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115,
            (byte)150, (byte)172, (byte)116,  (byte)34, (byte)231, (byte)173,  (byte)53, (byte)133, (byte)226, (byte)249,  (byte)55, (byte)232,  (byte)28, (byte)117, (byte)223, (byte)110,
            (byte)71, (byte)241,  (byte)26, (byte)113,  (byte)29,  (byte)41, (byte)197, (byte)137, (byte)111, (byte)183,  (byte)98,  (byte)14, (byte)170,  (byte)24, (byte)190,  (byte)27,
            (byte)252,  (byte)86,  (byte)62,  (byte)75, (byte)198, (byte)210, (byte)121,  (byte)32, (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205,  (byte)90, (byte)244,
            (byte)31, (byte)221, (byte)168,  (byte)51, (byte)136,   (byte)7, (byte)199,  (byte)49, (byte)177,  (byte)18,  (byte)16,  (byte)89,  (byte)39, (byte)128, (byte)236,  (byte)95,
            (byte)96,  (byte)81, (byte)127, (byte)169,  (byte)25, (byte)181,  (byte)74,  (byte)13,  (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239,
            (byte)160, (byte)224,  (byte)59,  (byte)77, (byte)174,  (byte)42, (byte)245, (byte)176, (byte)200, (byte)235, (byte)187,  (byte)60, (byte)131,  (byte)83, (byte)153,  (byte)97,
            (byte)23,  (byte)43,   (byte)4, (byte)126, (byte)186, (byte)119, (byte)214,  (byte)38, (byte)225, (byte)105,  (byte)20,  (byte)99,  (byte)85,  (byte)33,  (byte)12, (byte)125,
            };

        private static readonly int[] rcon = {
            0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 };

        static byte[][] shifts0 = new byte [][]
        {
            new byte [] { 0, 8, 16, 24 },
            new byte [] { 0, 8, 16, 24 },
            new byte [] { 0, 8, 16, 24 },
            new byte [] { 0, 8, 16, 32 },
            new byte [] { 0, 8, 24, 32 }
        };

        static byte[][] shifts1 =
        {
            new byte [] { 0, 24, 16, 8 },
            new byte [] { 0, 32, 24, 16 },
            new byte [] { 0, 40, 32, 24 },
            new byte [] { 0, 48, 40, 24 },
            new byte [] { 0, 56, 40, 32 }
        };

        /**
        * multiply two elements of GF(2^m)
        * needed for MixColumn and InvMixColumn
        */
        private byte Mul0x2(
            int b)
        {
            if (b != 0)
            {
                return Alogtable[25 + (Logtable[b] & 0xff)];
            }
            else
            {
                return 0;
            }
        }

        private byte Mul0x3(
            int b)
        {
            if (b != 0)
            {
                return Alogtable[1 + (Logtable[b] & 0xff)];
            }
            else
            {
                return 0;
            }
        }

        private byte Mul0x9(
            int b)
        {
            if (b >= 0)
            {
                return Alogtable[199 + b];
            }
            else
            {
                return 0;
            }
        }

        private byte Mul0xb(
            int b)
        {
            if (b >= 0)
            {
                return Alogtable[104 + b];
            }
            else
            {
                return 0;
            }
        }

        private byte Mul0xd(
            int b)
        {
            if (b >= 0)
            {
                return Alogtable[238 + b];
            }
            else
            {
                return 0;
            }
        }

        private byte Mul0xe(
            int b)
        {
            if (b >= 0)
            {
                return Alogtable[223 + b];
            }
            else
            {
                return 0;
            }
        }

        /**
        * xor corresponding text input and round key input bytes
        */
        private void KeyAddition(
            long[] rk)
        {
            A0 ^= rk[0];
            A1 ^= rk[1];
            A2 ^= rk[2];
            A3 ^= rk[3];
        }

        private long Shift(
            long	r,
            int	shift)
        {
			//return (((long)((ulong) r >> shift) | (r << (BC - shift)))) & BC_MASK;

			ulong temp = (ulong) r >> shift;

			// NB: This corrects for Mono Bug #79087 (fixed in 1.1.17)
			if (shift > 31)
			{
				temp &= 0xFFFFFFFFUL;
			}

			return ((long) temp | (r << (BC - shift))) & BC_MASK;
        }

		/**
        * Row 0 remains unchanged
        * The other three rows are shifted a variable amount
        */
        private void ShiftRow(
            byte[]      shiftsSC)
        {
            A1 = Shift(A1, shiftsSC[1]);
            A2 = Shift(A2, shiftsSC[2]);
            A3 = Shift(A3, shiftsSC[3]);
        }

        private long ApplyS(
            long    r,
            byte[]  box)
        {
            long    res = 0;

            for (int j = 0; j < BC; j += 8)
            {
                res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j;
            }

            return res;
        }

        /**
        * Replace every byte of the input by the byte at that place
        * in the nonlinear S-box
        */
        private void Substitution(
            byte[]      box)
        {
            A0 = ApplyS(A0, box);
            A1 = ApplyS(A1, box);
            A2 = ApplyS(A2, box);
            A3 = ApplyS(A3, box);
        }

        /**
        * Mix the bytes of every column in a linear way
        */
        private void MixColumn()
        {
            long r0, r1, r2, r3;

            r0 = r1 = r2 = r3 = 0;

            for (int j = 0; j < BC; j += 8)
            {
                int a0 = (int)((A0 >> j) & 0xff);
                int a1 = (int)((A1 >> j) & 0xff);
                int a2 = (int)((A2 >> j) & 0xff);
                int a3 = (int)((A3 >> j) & 0xff);

                r0 |= (long)((Mul0x2(a0) ^ Mul0x3(a1) ^ a2 ^ a3) & 0xff) << j;

                r1 |= (long)((Mul0x2(a1) ^ Mul0x3(a2) ^ a3 ^ a0) & 0xff) << j;

                r2 |= (long)((Mul0x2(a2) ^ Mul0x3(a3) ^ a0 ^ a1) & 0xff) << j;

                r3 |= (long)((Mul0x2(a3) ^ Mul0x3(a0) ^ a1 ^ a2) & 0xff) << j;
            }

            A0 = r0;
            A1 = r1;
            A2 = r2;
            A3 = r3;
        }

        /**
        * Mix the bytes of every column in a linear way
        * This is the opposite operation of Mixcolumn
        */
        private void InvMixColumn()
        {
            long r0, r1, r2, r3;

            r0 = r1 = r2 = r3 = 0;
            for (int j = 0; j < BC; j += 8)
            {
                int a0 = (int)((A0 >> j) & 0xff);
                int a1 = (int)((A1 >> j) & 0xff);
                int a2 = (int)((A2 >> j) & 0xff);
                int a3 = (int)((A3 >> j) & 0xff);

                //
                // pre-lookup the log table
                //
                a0 = (a0 != 0) ? (Logtable[a0 & 0xff] & 0xff) : -1;
                a1 = (a1 != 0) ? (Logtable[a1 & 0xff] & 0xff) : -1;
                a2 = (a2 != 0) ? (Logtable[a2 & 0xff] & 0xff) : -1;
                a3 = (a3 != 0) ? (Logtable[a3 & 0xff] & 0xff) : -1;

                r0 |= (long)((Mul0xe(a0) ^ Mul0xb(a1) ^ Mul0xd(a2) ^ Mul0x9(a3)) & 0xff) << j;

                r1 |= (long)((Mul0xe(a1) ^ Mul0xb(a2) ^ Mul0xd(a3) ^ Mul0x9(a0)) & 0xff) << j;

                r2 |= (long)((Mul0xe(a2) ^ Mul0xb(a3) ^ Mul0xd(a0) ^ Mul0x9(a1)) & 0xff) << j;

                r3 |= (long)((Mul0xe(a3) ^ Mul0xb(a0) ^ Mul0xd(a1) ^ Mul0x9(a2)) & 0xff) << j;
            }

            A0 = r0;
            A1 = r1;
            A2 = r2;
            A3 = r3;
        }

        /**
        * Calculate the necessary round keys
        * The number of calculations depends on keyBits and blockBits
        */
        private long[][] GenerateWorkingKey(
            byte[]      key)
        {
            int         KC;
            int         t, rconpointer = 0;
            int         keyBits = key.Length * 8;
            byte[,]    tk = new byte[4,MAXKC];
            //long[,]    W = new long[MAXROUNDS+1,4];
            long[][]    W = new long[MAXROUNDS+1][];

            for (int i = 0; i < MAXROUNDS+1; i++) W[i] = new long[4];

            switch (keyBits)
            {
            case 128:
                KC = 4;
                break;
            case 160:
                KC = 5;
                break;
            case 192:
                KC = 6;
                break;
            case 224:
                KC = 7;
                break;
            case 256:
                KC = 8;
                break;
            default :
                throw new ArgumentException("Key length not 128/160/192/224/256 bits.");
            }

            if (keyBits >= blockBits)
            {
                ROUNDS = KC + 6;
            }
            else
            {
                ROUNDS = (BC / 8) + 6;
            }

            //
            // copy the key into the processing area
            //
            int index = 0;

            for (int i = 0; i < key.Length; i++)
            {
                tk[i % 4,i / 4] = key[index++];
            }

            t = 0;

            //
            // copy values into round key array
            //
            for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC / 8)); j++, t++)
            {
                for (int i = 0; i < 4; i++)
                {
                    W[t / (BC / 8)][i] |= (long)(tk[i,j] & 0xff) << ((t * 8) % BC);
                }
            }

            //
            // while not enough round key material calculated
            // calculate new values
            //
            while (t < (ROUNDS+1)*(BC/8))
            {
                for (int i = 0; i < 4; i++)
                {
                    tk[i,0] ^= S[tk[(i+1)%4,KC-1] & 0xff];
                }
                tk[0,0] ^= (byte) rcon[rconpointer++];

                if (KC <= 6)
                {
                    for (int j = 1; j < KC; j++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            tk[i,j] ^= tk[i,j-1];
                        }
                    }
                }
                else
                {
                    for (int j = 1; j < 4; j++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            tk[i,j] ^= tk[i,j-1];
                        }
                    }
                    for (int i = 0; i < 4; i++)
                    {
                        tk[i,4] ^= S[tk[i,3] & 0xff];
                    }
                    for (int j = 5; j < KC; j++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            tk[i,j] ^= tk[i,j-1];
                        }
                    }
                }

                //
                // copy values into round key array
                //
                for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC/8)); j++, t++)
                {
                    for (int i = 0; i < 4; i++)
                    {
                        W[t / (BC/8)][i] |= (long)(tk[i,j] & 0xff) << ((t * 8) % (BC));
                    }
                }
            }
            return W;
        }

        private int         BC;
        private long        BC_MASK;
        private int         ROUNDS;
        private int         blockBits;
        private long[][]    workingKey;
        private long        A0, A1, A2, A3;
        private bool     forEncryption;
        private byte[]      shifts0SC;
        private byte[]      shifts1SC;

        /**
        * default constructor - 128 bit block size.
        */
        public RijndaelEngine() : this(128) {}

        /**
        * basic constructor - set the cipher up for a given blocksize
        *
        * @param blocksize the blocksize in bits, must be 128, 192, or 256.
        */
        public RijndaelEngine(
            int blockBits)
        {
            switch (blockBits)
            {
            case 128:
                BC = 32;
                BC_MASK = 0xffffffffL;
                shifts0SC = shifts0[0];
                shifts1SC = shifts1[0];
                break;
            case 160:
                BC = 40;
                BC_MASK = 0xffffffffffL;
                shifts0SC = shifts0[1];
                shifts1SC = shifts1[1];
                break;
            case 192:
                BC = 48;
                BC_MASK = 0xffffffffffffL;
                shifts0SC = shifts0[2];
                shifts1SC = shifts1[2];
                break;
            case 224:
                BC = 56;
                BC_MASK = 0xffffffffffffffL;
                shifts0SC = shifts0[3];
                shifts1SC = shifts1[3];
                break;
            case 256:
                BC = 64;
                BC_MASK = unchecked( (long)0xffffffffffffffffL);
                shifts0SC = shifts0[4];
                shifts1SC = shifts1[4];
                break;
            default:
                throw new ArgumentException("unknown blocksize to Rijndael");
            }

            this.blockBits = blockBits;
        }

        /**
        * initialise a Rijndael cipher.
        *
        * @param forEncryption whether or not we are for encryption.
        * @param parameters the parameters required to set up the cipher.
        * @exception ArgumentException if the parameters argument is
        * inappropriate.
        */
        public void Init(
            bool           forEncryption,
            ICipherParameters  parameters)
        {
            if (typeof(KeyParameter).IsInstanceOfType(parameters))
            {
                workingKey = GenerateWorkingKey(((KeyParameter)parameters).GetKey());
                this.forEncryption = forEncryption;
                return;
            }

            throw new ArgumentException("invalid parameter passed to Rijndael init - " + parameters.GetType().ToString());
        }

		public string AlgorithmName
        {
            get { return "Rijndael"; }
        }

		public bool IsPartialBlockOkay
		{
			get { return false; }
		}

		public int GetBlockSize()
        {
            return BC / 2;
        }

		public int ProcessBlock(
            byte[]	input,
            int		inOff,
            byte[]	output,
            int		outOff)
        {
            if (workingKey == null)
            {
                throw new InvalidOperationException("Rijndael engine not initialised");
            }

            if ((inOff + (BC / 2)) > input.Length)
            {
                throw new DataLengthException("input buffer too short");
            }

            if ((outOff + (BC / 2)) > output.Length)
            {
                throw new DataLengthException("output buffer too short");
            }

			UnPackBlock(input, inOff);

			if (forEncryption)
            {
                EncryptBlock(workingKey);
            }
            else
            {
                DecryptBlock(workingKey);
            }

			PackBlock(output, outOff);

			return BC / 2;
        }

        public void Reset()
        {
        }

        private  void UnPackBlock(
            byte[]      bytes,
            int         off)
        {
            int     index = off;

            A0 = (long)(bytes[index++] & 0xff);
            A1 = (long)(bytes[index++] & 0xff);
            A2 = (long)(bytes[index++] & 0xff);
            A3 = (long)(bytes[index++] & 0xff);

            for (int j = 8; j != BC; j += 8)
            {
                A0 |= (long)(bytes[index++] & 0xff) << j;
                A1 |= (long)(bytes[index++] & 0xff) << j;
                A2 |= (long)(bytes[index++] & 0xff) << j;
                A3 |= (long)(bytes[index++] & 0xff) << j;
            }
        }

        private  void PackBlock(
            byte[]      bytes,
            int         off)
        {
            int     index = off;

            for (int j = 0; j != BC; j += 8)
            {
                bytes[index++] = (byte)(A0 >> j);
                bytes[index++] = (byte)(A1 >> j);
                bytes[index++] = (byte)(A2 >> j);
                bytes[index++] = (byte)(A3 >> j);
            }
        }

        private  void EncryptBlock(
            long[][] rk)
        {
            int r;

            //
            // begin with a key addition
            //
            KeyAddition(rk[0]);

            //
            // ROUNDS-1 ordinary rounds
            //
            for (r = 1; r < ROUNDS; r++)
            {
                Substitution(S);
                ShiftRow(shifts0SC);
                MixColumn();
                KeyAddition(rk[r]);
            }

            //
            // Last round is special: there is no MixColumn
            //
            Substitution(S);
            ShiftRow(shifts0SC);
            KeyAddition(rk[ROUNDS]);
        }

        private void DecryptBlock(
            long[][] rk)
        {
            int r;

            // To decrypt: apply the inverse operations of the encrypt routine,
            //             in opposite order
            //
            // (KeyAddition is an involution: it 's equal to its inverse)
            // (the inverse of Substitution with table S is Substitution with the inverse table of S)
            // (the inverse of Shiftrow is Shiftrow over a suitable distance)
            //

            // First the special round:
            //   without InvMixColumn
            //   with extra KeyAddition
            //
            KeyAddition(rk[ROUNDS]);
            Substitution(Si);
            ShiftRow(shifts1SC);

            //
            // ROUNDS-1 ordinary rounds
            //
            for (r = ROUNDS-1; r > 0; r--)
            {
                KeyAddition(rk[r]);
                InvMixColumn();
                Substitution(Si);
                ShiftRow(shifts1SC);
            }

            //
            // End with the extra key addition
            //
            KeyAddition(rk[0]);
        }
    }

}
