Difference between revisions of "AES LSL Implementation"

From Second Life Wiki
Jump to navigation Jump to search
Line 26: Line 26:
|[http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 Cipher Feed-Back] mode is very similar to CBC, except that it does not require cipher inversion to decrypt, meaning you can (if you wish) remove all decryption code to increase available memory.
|[http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 Cipher Feed-Back] mode is very similar to CBC, except that it does not require cipher inversion to decrypt, meaning you can (if you wish) remove all decryption code to increase available memory.
|-
|-
|MODE_OFB
|MODE_NOFB
|[http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 Output Feed-Back] mode is almost identical to CFB.
|N-length [http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 Output Feed-Back] mode is almost identical to CFB. In this case N is the size of a block, or 128-bit. This is in preference to 8-bit OFB which is considered insecure.
|}
|}



Revision as of 08:13, 12 September 2008

AES LSL Implementation

Description

The following is an LSL implementation of the AES Strong Encryption AES symmetric-key, block-cipher encryption scheme.

License

You may use the following code freely in your own work, provided you credit the author (Haravikk Mistral) and provide a link to this wiki article where feasible. You may use this implementation in commercial works.

Details

Basic usage

This script is designed to operate as an encryption "server", in that you communicate with it using linked-messages to set it up, and then ultimately encrypt/decrypt your text. To learn how to do this, you are perhaps best off looking at the helper functions and examples available for this script.

Modes of operation

This AES script supports a number of standard modes of operation, which determine how individual blocks of input data are processed. Some are quicker than others, while others are more secure. Here is a summary of the different modes of operation:

Mode Description
MODE_ECB Electronic Code-Book mode encrypts each block individually. This is the fastest mode of operation, however it is also the weakest, as for identical input blocks it will provide the same ciphertext blocks as output, decreasing the security of these blocks, or the layout of the message can be guessed.
MODE_CBC Cipher Block Chaining mode is the most commonly used mode. It works by XORing previous blocks of ciphertext with new blocks of input, making it unlikely that repeating patterns will occur.
MODE_CFB Cipher Feed-Back mode is very similar to CBC, except that it does not require cipher inversion to decrypt, meaning you can (if you wish) remove all decryption code to increase available memory.
MODE_NOFB N-length Output Feed-Back mode is almost identical to CFB. In this case N is the size of a block, or 128-bit. This is in preference to 8-bit OFB which is considered insecure.

Padding modes

This AES engine supports a number of different modes, you must be careful when using this engine with other AES implementations to be sure that you are able to select a supported padding method.

Mode Description
PAD_NONE Only usable with MODE_CFB and MODE_OFB above, this mode indicates that encryption is able to be performed on incomplete blocks of data, rather than padding them to 128-bits. Other modes of operation cannot be padded in this way, and will switch to PAD_RBT. When using MODE_CFB and MODE_OFB and want to maintain data-length, you should use this mode instead of PAD_RBT.
PAD_RBT Residual Block Termination is a type of padding which essentially switches to CFB mode temporarily in order to encrypt any leftover-data that did not fit correctly within a block. The advantage of this mode is that it allows MODE_ECB and MODE_CBC to be performed without having to increase the size of the data.
PAD_NULLS This mode is intended to allow compatibility with PHP's mcrypt implementation. It simply adds as many null-characters (zero-bytes) to the input as required to complete any incomplete blocks. When decrypting ALL trailing null-characters are removed so you must be careful with your inputs.
PAD_ZEROES Adds zero-bytes to the end of the input, with the final byte describing the number of padding-bytes that were added. For this reason, at least one byte must be added to the input, so inputs that fit correctly into blocks will be extended anyway by a full-block.
PAD_RANDOM Identical to PAD_ZEROES except that random bytes are used instead of zeroes.

Notes on security

For security you are recommended to use MODE_CBC (the default) as it is widely used, and good at destroying any recognisable patterns in your input-data.

When using MODE_CBC you should provide an input-vector (using the init command), and strive to change this regularly, in many cases doing so after every interaction is recommended, but you must ensure both the encrypting party, and the decrypting party always have the same input-vector, or decryption will fail. An easy way to do this is to encrypt the input-vector after every-pass, using PAD_RBC or PAD_NONE to ensure the length of the input-vector does not change. Make sure your input-vector is always handled as hexadecimal characters for correctness!

An alternative to changing the input-vector is to simply ensure the first block of your input is always different, as this will cause all subsequent blocks to appear different too. This could be achieved by placing a time-stamp at the start of your messages for example. The AES algorithm itself will provide extremely different results for only slightly different inputs so a unix-timestamp could change your ciphertext completely.

The one thing this library does not provide is a way to produce keys securely, other libraries may provide functionality to this end. Getting keys to your clients/servers without revealing them to an attacker is extremely important, as the key used to encrypt a message is also used to decrypt it.

Script

<lsl>// These variables are used to build communications. Commands are sent as // combined bits in the integer argument of a link-message, and are // recovered using masks, you may wish to read about bit-masks before // editing these values. These are used so the string argument is // kept free for data only. // // Commands take the following form (in hex): // 0xFFMMIOvv // Where the letters are: // F Filter, used to quickly determine if a message is for us. // C Command; encrypt/decrypt etc. // I Type of data provided (hex, base64, etc.). // O Desired type of data to be returned (hex, base64, etc.), // this is unused in replies as the reply's value for I will // be the request's value for O. // v Variable, depends on mode.

// This mask allows the filter byte to be retrieved quickly integer LSLAES_FILTER_MASK = 0xFF000000; // This mask allows the mask byte to be retrieved quickly integer LSLAES_COMMAND_MASK = 0x00FF0000; // This mask allows the input type to be retrieved quickly integer LSLAES_INPUT_TYPE_MASK = 0x0000F000; // This mask allows the output type to be retireved quickly integer LSLAES_OUTPUT_TYPE_MASK = 0x00000F00; // This mask allows the variable to retrieved quickly integer LSLAES_VARIABLE_MASK = 0x000000FF; // How many bits right variable must be shifted integer LSLAES_VARIABLE_SHIFT = 0;

// A request integer LSLAES_FILTER_REQUEST = 0x81000000; // A reply integer LSLAES_FILTER_REPLY = 0x82000000;

// An error occurred integer LSLAES_COMMAND_ERROR = 0x00000000; // Prime engine with key integer LSLAES_COMMAND_PRIME = 0x00010000; // Encrypt message using expanded key integer LSLAES_COMMAND_ENCRYPT = 0x00020000; // Decrypt message using expanded key integer LSLAES_COMMAND_DECRYPT = 0x00030000; // Hash message using expanded key, variable is used to determine hash- // length in 32-bit words. i.e - if var == 4 then length = 4 * 32 = 128. // Max size is LSLAES_HASH_SIZE integer LSLAES_COMMAND_HASH = 0x00040000; // Sets-up the engine by specifying comma-separated flags integer LSLAES_COMMAND_SETUP = 0x00050000; // Initialise the engine with an input-vector integer LSLAES_COMMAND_INIT = 0x00060000;

// Input type is hex integer LSLAES_INPUT_HEX = 0x00000000; // Input type is base64 integer LSLAES_INPUT_BASE64 = 0x00001000;

// Output type is hex integer LSLAES_OUTPUT_HEX = 0x00000000; // Output type is base64 integer LSLAES_OUTPUT_BASE64 = 0x00000100;

// Maximum hash-size integer LSLAES_HASH_SIZE = 512;

// Refuse any data longer than this many characters integer LSLAES_MAX_SIZE = 3072;

string LSLAES_HEX_CHARS = "0123456789ABCDEF"; string LSLAES_BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// The inverted Rijndael SBox, values in the state are substituted with these list LSLAES_SBOX_INVERTED = [

   0x52096ad5, 0x3036a538, 0xbf40a39e, 0x81f3d7fb, 0x7ce33982, 
   0x9b2fff87, 0x348e4344, 0xc4dee9cb, 0x547b9432, 0xa6c2233d, 
   0xee4c950b, 0x42fac34e, 0x082ea166, 0x28d924b2, 0x765ba249, 
   0x6d8bd125, 0x72f8f664, 0x86689816, 0xd4a45ccc, 0x5d65b692, 
   0x6c704850, 0xfdedb9da, 0x5e154657, 0xa78d9d84, 0x90d8ab00, 
   0x8cbcd30a, 0xf7e45805, 0xb8b34506, 0xd02c1e8f, 0xca3f0f02, 
   0xc1afbd03, 0x01138a6b, 0x3a911141, 0x4f67dcea, 0x97f2cfce, 
   0xf0b4e673, 0x96ac7422, 0xe7ad3585, 0xe2f937e8, 0x1c75df6e, 
   0x47f11a71, 0x1d29c589, 0x6fb7620e, 0xaa18be1b, 0xfc563e4b, 
   0xc6d27920, 0x9adbc0fe, 0x78cd5af4, 0x1fdda833, 0x8807c731, 
   0xb1121059, 0x2780ec5f, 0x60517fa9, 0x19b54a0d, 0x2de57a9f, 
   0x93c99cef, 0xa0e03b4d, 0xae2af5b0, 0xc8ebbb3c, 0x83539961, 
   0x172b047e, 0xba77d626, 0xe1691463, 0x55210c7d

];

integer lslAESSBoxInvertedCached = -1; integer lslAESSBoxInvertedCache = 0;

// Treats the above list as a byte-array, retrieving the desired byte value. integer lslAESGetSBoxInvertedByte(integer n) {

   integer b = n % 4;  // Byte within integer
           n /= 4;     // Integer
   b = 3 - b;

   if (lslAESSBoxInvertedCached == n) n = lslAESSBoxInvertedCache;
   else {
       lslAESSBoxInvertedCached = n;
       lslAESSBoxInvertedCache = llList2Integer(LSLAES_SBOX_INVERTED, n);
   }
   return (lslAESSBoxInvertedCache >> (b << 3)) & 0xFF;

}

// The Rijndael SBox, values in the state are substituted with these list LSLAES_SBOX = [

   0x637c777b, 0xf26b6fc5, 0x3001672b, 0xfed7ab76, 0xca82c97d, 
   0xfa5947f0, 0xadd4a2af, 0x9ca472c0, 0xb7fd9326, 0x363ff7cc, 
   0x34a5e5f1, 0x71d83115, 0x04c723c3, 0x1896059a, 0x071280e2,
   0xeb27b275, 0x09832c1a, 0x1b6e5aa0, 0x523bd6b3, 0x29e32f84, 
   0x53d100ed, 0x20fcb15b, 0x6acbbe39, 0x4a4c58cf, 0xd0efaafb, 
   0x434d3385, 0x45f9027f, 0x503c9fa8, 0x51a3408f, 0x929d38f5, 
   0xbcb6da21, 0x10fff3d2, 0xcd0c13ec, 0x5f974417, 0xc4a77e3d, 
   0x645d1973, 0x60814fdc, 0x222a9088, 0x46eeb814, 0xde5e0bdb, 
   0xe0323a0a, 0x4906245c, 0xc2d3ac62, 0x9195e479, 0xe7c8376d, 
   0x8dd54ea9, 0x6c56f4ea, 0x657aae08, 0xba78252e, 0x1ca6b4c6, 
   0xe8dd741f, 0x4bbd8b8a, 0x703eb566, 0x4803f60e, 0x613557b9, 
   0x86c11d9e, 0xe1f89811, 0x69d98e94, 0x9b1e87e9, 0xce5528df, 
   0x8ca1890d, 0xbfe64268, 0x41992d0f, 0xb054bb16

];

integer lslAESSBoxCached = -1; integer lslAESSBoxCache = 0;

// Treats the above list as a byte-array, retrieving the desired byte value. integer lslAESGetSBoxByte(integer n) {

   integer b = n % 4;  // Byte within integer
           n /= 4;     // Integer
   b = 3 - b;

   if (lslAESSBoxCached == n) n = lslAESSBoxCache;
   else {
       lslAESSBoxCached = n;
       lslAESSBoxCache = llList2Integer(LSLAES_SBOX, n);
   }
   return (lslAESSBoxCache >> (b << 3)) & 0xFF;

}

// The following constants define modes of operation integer LSLAES_MODE_ECB = 0; integer LSLAES_MODE_CBC = 1; integer LSLAES_MODE_CFB = 2; integer LSLAES_MODE_NOFB = 3;

// Used to set mode list LSLAES_MODES = [

   "MODE_ECB",     LSLAES_MODE_ECB,
   "MODE_CBC",     LSLAES_MODE_CBC,
   "MODE_CFB",     LSLAES_MODE_CFB,
   "MODE_NOFB",    LSLAES_MODE_NOFB

];

// The following contstants define types of padding integer LSLAES_PAD_NONE = 0; // Only compatible with CBF and OBF modes integer LSLAES_PAD_RBT = 1; // XOR leftover bytes with re-encrypted

                                    // first-block. Length remains the same

integer LSLAES_PAD_NULLS = 2; // Adds zeroes (null characters) to the

                                    // end, which are trimmed afterwards.
                                    // Padding creates blocks of lslAESPadSize

integer LSLAES_PAD_RANDOM = 3; // Adds random bytes to the end of the

                                    // data until it reaches a multiple of 
                                    // lslAESPadSize in length. Final byte 
                                    // identifies how many were added. This 
                                    // scheme causes padding to ALWAYS be 
                                    // added.

integer LSLAES_PAD_ZEROES = 4; // Identical to LSLAES_PAD_RANDOM except

                                    // that zero-bytes are added.

// Used to set padding type list LSLAES_PADS = [

   "PAD_NONE",        LSLAES_PAD_NONE,
   "PAD_RBT",      LSLAES_PAD_RBT,
   "PAD_NULLS",    LSLAES_PAD_NULLS,
   "PAD_RANDOM",   LSLAES_PAD_RANDOM,
   "PAD_ZEROES",   LSLAES_PAD_ZEROES

];

integer LSLAES_PAD_SIZE = 512;

integer lslAESMode = LSLAES_MODE_CBC; integer lslAESPad = LSLAES_PAD_RBT; integer lslAESPadSize = LSLAES_PAD_SIZE;

// Used for the actual encryption, generated from Key list lslAESRoundKey = []; // The number of rounds to perform (bigger key == more rounds) integer lslAESRounds = 0;

// The following are used for the state instead of a list integer lslAESStateX0Y0 = 0; integer lslAESStateX0Y1 = 0; integer lslAESStateX0Y2 = 0; integer lslAESStateX0Y3 = 0; integer lslAESStateX1Y0 = 0; integer lslAESStateX1Y1 = 0; integer lslAESStateX1Y2 = 0; integer lslAESStateX1Y3 = 0; integer lslAESStateX2Y0 = 0; integer lslAESStateX2Y1 = 0; integer lslAESStateX2Y2 = 0; integer lslAESStateX2Y3 = 0; integer lslAESStateX3Y0 = 0; integer lslAESStateX3Y1 = 0; integer lslAESStateX3Y2 = 0; integer lslAESStateX3Y3 = 0;

// Used to initialise state for CBC and other mode integer lslAESInputVector0 = 0; integer lslAESInputVector1 = 0; integer lslAESInputVector2 = 0; integer lslAESInputVector3 = 0;

//##########################################################################// // HIGH-LEVEL FUNCTIONS // //##########################################################################// // The following functions are the ones to call to encrypt/decrypt // //##########################################################################// // Performs a cipher with necessary padding performed before execution list lslAESPadCipher(list data) {

   integer bits = llList2Integer(data, 0);
   data = llDeleteSubList((data = []) + data, 0, 0);
   
   integer padding = lslAESPad;
   if (padding == LSLAES_PAD_NONE) {
       if ((lslAESMode == LSLAES_MODE_CFB) || 
           (lslAESMode == LSLAES_MODE_NOFB)) 
           return [bits] + lslAESCipher((data = []) + data);
       padding = LSLAES_PAD_RBT;
   }
   
   integer blockSize = lslAESPadSize;
   if (padding == LSLAES_PAD_RBT) blockSize = 128;
   
   integer blocks = bits / blockSize;
   integer extra  = bits % blockSize;
   
   if (padding == LSLAES_PAD_RBT) {
       // This scheme takes the last encrypted block, encrypts it again and XORs 
       // it with any leftover data, maintaining data-length. If input is less 
       // than a block in size then the current input-vector is used.
       list final = [];
       if (extra > 0) {
           integer bytes = extra / 8;
           integer words = llCeil((float)bytes / 4.0);
           
           // Grab leftover words
           list t = llList2List(data, -words, -1);
           
           // Encrypt all other data
           list lb = [];
           if (blocks < 1) {
               // If not enough for a block, we generate lb using 
               // a double cipher of input vector.
               data = [];
               lb = lslAESCipher(
                   lslAESCipher((data = []) + [
                       lslAESInputVector0, lslAESInputVector1, 
                       lslAESInputVector2, lslAESInputVector3
                   ])
               );
           } else {
               // If there are blocks, we encrypt normally, then 
               // double-encrypt the final block for lb
               data = lslAESCipher(
                   llDeleteSubList((data = []) + data, -words, -1)
               );
               lb = lslAESCipher(llList2List(data, -16, -1));
           }
           
           // XOR lb with t
           integer i = 0; integer l = (t != []);
           do 
               final = (final = []) + final + 
                   [llList2Integer(t, i) ^ llList2Integer(lb, i)];
           while ((++i) < l);
           
           return [bits] + (data = final = []) + data + final;
       } 
       return lslAESCipher((data = []) + data);
   } else {
       // This scheme works by adding bytes until the data is a 
       // multiple of lslAESPadSize bits long. In the case of 
       // PAD_NULLS this will only add extra data if needed, 
       // while the other types must always add at least one 
       // byte, as they also leave a note of bytes added in the 
       // final byte.
       extra = blockSize - extra; // Bits to add
       integer bytes = extra / 8; // Bytes to add
       if (bytes <= 0) {
           if (padding == LSLAES_PAD_NULLS) 
               jump skip; // Doesn't need to add anything
               
           bytes = blockSize / 8;
           extra += blockSize;
       }
       
       bits += extra;
       
       integer words = bytes / 4; // Words to add
       
       // First add bytes to end-word
       extra = bytes % 4;
       if (extra > 0) {
           integer i = 0; integer v = llList2Integer(data, -1);
           if ((extra == bytes) && (padding != LSLAES_PAD_NULLS)) {
               v = v | bytes;
               i = 1;
           }
           
           integer byte = 0;
           while (i < extra) {
               if (padding == LSLAES_PAD_RANDOM) 
                   byte = (integer)llFrand(256.0) & 0xFF;
               v = v | (byte << (i << 3));
               ++i;
           }
           
           data = llListReplaceList((data = []) + data, [v], -1, -1);
       }
       
       // Now, if needed, add words to end of data
       if (words > 0) {
           integer final = -1;
           if (padding != LSLAES_PAD_NULLS) 
               final = words - 1;
           
           integer word = 0; integer byte = 0;
           integer i = 0;
           integer j = 0; list w = [];
           do {
               word = j = 0; // New word
               do {
                   if ((padding != LSLAES_PAD_NULLS) && 
                       (i == final) && !j) 
                       byte = bytes;
                   else if (padding == LSLAES_PAD_RANDOM) 
                       byte = (integer)llFrand(256.0) & 0xFF;
                   
                   word = word | (byte << (j << 3));
               } while ((++j) < 4);
               
               w = (w = []) + w + [word];
           } while ((++i) < words);
           
           data = (data = w = []) + data + w;
       }
       
       @skip;
       return [bits] + lslAESCipher((data = []) + data);
   }

}

// Performs an inverse cipher with appropriate padding handling performed list lslAESInvertPadCipher(list data) {

   integer bits = llList2Integer(data, 0);
   data = llDeleteSubList((data = []) + data, 0, 0);
   
   integer padding = lslAESPad;
   if (padding == LSLAES_PAD_NONE) {
       if ((lslAESMode == LSLAES_MODE_CFB) || 
           (lslAESMode == LSLAES_MODE_NOFB)) 
           return [bits] + lslAESInvertCipher((data = []) + data);
       padding = LSLAES_PAD_RBT;
   }
   
   integer blockSize = lslAESPadSize;
   if (padding == LSLAES_PAD_RBT) blockSize = 128;
   
   integer blocks = bits / blockSize;
   integer extra  = bits % blockSize;
   
   if (padding == LSLAES_PAD_RBT) {
       // This scheme takes the last encrypted block, encrypts it again and
       // XORs it with any leftover data, maintaining data-length. If input 
       // is less than a block in size then the current input-vector is used.
       list final = [];
       if (extra > 0) {
           integer bytes = extra / 8;
           integer words = llCeil((float)bytes / 4.0);
           
           // Grab leftover words
           list t = llList2List(data, -words, -1);
           
           // Decrypt all other data
           list lb = [];
           if (blocks < 1) {
               // If not enough for a block, we generate lb using 
               // a double cipher of input vector.
               data = [];
               lb = lslAESCipher(
                   lslAESCipher((data = []) + [
                       lslAESInputVector0, lslAESInputVector1, 
                       lslAESInputVector2, lslAESInputVector3
                   ])
               );
           } else {
               // If there are blocks, then we double-encrypt the 
               // last full block to generate lb, then decrypt normally
               lb = lslAESCipher(llList2List(data, -(16 + words), -(words + 1)));
               data = lslAESInvertCipher(
                   llDeleteSubList((data = []) + data, -words, -1)
               );
           }
           
           // XOR lb with t
           integer i = 0; integer l = (t != []);
           do 
               final = (final = []) + final + 
                   [llList2Integer(t, i) ^ llList2Integer(lb, i)];
           while ((++i) < l);
       
           return [bits] + (data = final = []) + data + final;
       }
       return [bits] + lslAESInvertCipher((data = []) + data);
   } else {
       // This scheme works by adding bytes until the data is a 
       // multiple of lslAESPadSize bits long. In the case of 
       // PAD_NULLS this will only add extra data if needed, 
       // while the other types must always add at least one 
       // byte, as they also leave a note of bytes added in the 
       // final byte.
       
       // If the data is not on a block boundary, then extra bits 
       // have snuck in (usually due to base64 conversion). We 
       // assume here that padding was done correctly, and ignore 
       // extra bits.
       if (extra > 0) {
           bits -= extra;
           extra = llCeil((float)extra / 32.0);
           if (extra <= 0) extra = 1;
           
           if (extra > (data != [])) return [0];
           data = llDeleteSubList((data = []) + data, -extra, -extra);
       }
       
       // Perform the decryption
       data = lslAESInvertCipher((data = []) + data);
       
       integer bytes = 0; integer words = 0;
       
       // Remove extra bytes as required
       if (padding == LSLAES_PAD_NULLS) {
           // We remove all zero-bytes at the end of the data
           integer l = data != [];
           integer v = 0;
           
           while (words < l) {
               v = llList2Integer(data, -(words + 1));
               
               if (v == 0) { // Four null-bytes
                   ++words;
                   bytes += 4;
               } else {
                   integer j = 0; integer byte = 0;
                   do {
                       byte = (v >> (j << 3)) & 0xFF;
                       
                       if (byte == 0) ++bytes;
                       else jump skip;
                   } while ((++j) < 4);
               }
           }
           @skip;
       } else {
           // Get the number of bytes to remove from the final byte
           bytes = llList2Integer(data, -1) & 0xFF;
           if ((bytes << 3) >= bits) return [0];
           words = bytes / 4;
       }
           
       // Lop-off words, excess bytes are accounted for later
       if (words > 0) 
           data = llDeleteSubList((data = []) + data, -words, -1);
           
       // Correct bit-header for output
       bits -= bytes << 3;
       
       return [bits] + (data = []) + data;
   }

}

// Calculate a length-bit hash using the currently expanded key. Length MUST // be a multiple of 32. list lslAESHash(list data, integer length) {

   // First pad data to a length * 2
   integer bits = llList2Integer(data, 0);
   integer i = 0;
   integer j = length << 1;

   // If too short, then add values
   if (bits < j) {
       integer a = (j - bits) / 32;
       do data = (data = []) + data + llList2Integer(data, i);
       while ((++i) < a);
       bits = j;
   }
   
   // Calculate a dirty 32-bit hash to give greater variation in results
   integer c = 0; i = 0; integer l = data != [];
   do c = c ^ llList2Integer(data, i);
   while ((++i) < bits);

   // Irreversibly reduce to length by XORing
   list out = [length];
   integer b = c;
   
   // Use only the dirty-hash for 32-bit hashes
   if (length == 32) out += c;
   else {
       integer w = (j = (bits / length)) - (i = 1);
       do {
           b = b ^ llList2Integer(data, i);
           if ((i % j) == w) {
               out = (out = []) + out + b;
               b = c;
           }
       } while ((++i) < l);
   }

   // Add any extra data if necessary and calculate hash
   if ((out != []) < (length / 32)) 
       out = lslAESPadCipher((out = data = []) + out + b);
   else
       out = lslAESPadCipher((out = data = []) + out);

   // Reduce to size
   return [length] + llList2List(out, 1, (length / 32) + ((out = []) != out));

}

// Decrypts a list of integers into a list of decrypted integers. // Padding adjustment must be performed as this function will only except data // that is a multiple of 128-bits long. list lslAESInvertCipher(list data) {

   // The following are used to pass blocks forward    
    integer prevBlock0 = 0;
    integer prevBlock1 = 0;
    integer prevBlock2 = 0;
    integer prevBlock3 = 0;
    
    integer nextBlock0 = 0;
    integer nextBlock1 = 0;
    integer nextBlock2 = 0;
    integer nextBlock3 = 0;

   // If non-ECB mode is enabled, we must prime the state with the input vector
   if (lslAESMode != LSLAES_MODE_ECB) {
       if (lslAESMode == LSLAES_MODE_NOFB) 
           lslAESLoadInputVector();
       else {
           prevBlock0 = lslAESInputVector0;
           prevBlock1 = lslAESInputVector1;
           prevBlock2 = lslAESInputVector2;
           prevBlock3 = lslAESInputVector3;
       }
   }
   
   integer j = 0;
   integer l = (data != []);
   list output = [];
   while (l > 0) {
       // Different modes treat blocks differently
       if (lslAESMode == LSLAES_MODE_ECB) {
           // For ECB we simply load the data
           j = llList2Integer(data, 0);
           lslAESStateX0Y0 = ((j >> 24) & 0xFF);
           lslAESStateX0Y1 = ((j >> 16) & 0xFF);
           lslAESStateX0Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX0Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 1);
           lslAESStateX1Y0 = ((j >> 24) & 0xFF);
           lslAESStateX1Y1 = ((j >> 16) & 0xFF);
           lslAESStateX1Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX1Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 2);
           lslAESStateX2Y0 = ((j >> 24) & 0xFF);
           lslAESStateX2Y1 = ((j >> 16) & 0xFF);
           lslAESStateX2Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX2Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 3);
           lslAESStateX3Y0 = ((j >> 24) & 0xFF);
           lslAESStateX3Y1 = ((j >> 16) & 0xFF);
           lslAESStateX3Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX3Y3 = ((j      ) & 0xFF);
       } else if (lslAESMode == LSLAES_MODE_CBC) {
           // For CBC we load it, and must keep a copy
           nextBlock0 = llList2Integer(data, 0);
           lslAESStateX0Y0 = ((nextBlock0 >> 24) & 0xFF);
           lslAESStateX0Y1 = ((nextBlock0 >> 16) & 0xFF);
           lslAESStateX0Y2 = ((nextBlock0 >> 8 ) & 0xFF);
           lslAESStateX0Y3 = ((nextBlock0      ) & 0xFF);
           nextBlock1 = llList2Integer(data, 1);
           lslAESStateX1Y0 = ((nextBlock1 >> 24) & 0xFF);
           lslAESStateX1Y1 = ((nextBlock1 >> 16) & 0xFF);
           lslAESStateX1Y2 = ((nextBlock1 >> 8 ) & 0xFF);
           lslAESStateX1Y3 = ((nextBlock1      ) & 0xFF);
           nextBlock2 = llList2Integer(data, 2);
           lslAESStateX2Y0 = ((nextBlock2 >> 24) & 0xFF);
           lslAESStateX2Y1 = ((nextBlock2 >> 16) & 0xFF);
           lslAESStateX2Y2 = ((nextBlock2 >> 8 ) & 0xFF);
           lslAESStateX2Y3 = ((nextBlock2      ) & 0xFF);
           nextBlock3 = llList2Integer(data, 3);
           lslAESStateX3Y0 = ((nextBlock3 >> 24) & 0xFF);
           lslAESStateX3Y1 = ((nextBlock3 >> 16) & 0xFF);
           lslAESStateX3Y2 = ((nextBlock3 >> 8 ) & 0xFF);
           lslAESStateX3Y3 = ((nextBlock3      ) & 0xFF);
       } else if (lslAESMode == LSLAES_MODE_CFB) {
           lslAESStateX0Y0 = ((prevBlock0 >> 24) & 0xFF);
           lslAESStateX0Y1 = ((prevBlock0 >> 16) & 0xFF);
           lslAESStateX0Y2 = ((prevBlock0 >> 8 ) & 0xFF);
           lslAESStateX0Y3 = ((prevBlock0      ) & 0xFF);
           lslAESStateX1Y0 = ((prevBlock1 >> 24) & 0xFF);
           lslAESStateX1Y1 = ((prevBlock1 >> 16) & 0xFF);
           lslAESStateX1Y2 = ((prevBlock1 >> 8 ) & 0xFF);
           lslAESStateX1Y3 = ((prevBlock1      ) & 0xFF);
           lslAESStateX2Y0 = ((prevBlock2 >> 24) & 0xFF);
           lslAESStateX2Y1 = ((prevBlock2 >> 16) & 0xFF);
           lslAESStateX2Y2 = ((prevBlock2 >> 8 ) & 0xFF);
           lslAESStateX2Y3 = ((prevBlock2      ) & 0xFF);
           lslAESStateX3Y0 = ((prevBlock3 >> 24) & 0xFF);
           lslAESStateX3Y1 = ((prevBlock3 >> 16) & 0xFF);
           lslAESStateX3Y2 = ((prevBlock3 >> 8 ) & 0xFF);
           lslAESStateX3Y3 = ((prevBlock3      ) & 0xFF);
       }

       if ((lslAESMode == LSLAES_MODE_CFB) || 
           (lslAESMode == LSLAES_MODE_NOFB)) 
            lslAESPerformCipher(); // CFB and NOFB don't need inverse cipher
       else {
           // Add last round key before rounds begin
           lslAESAddRoundKey(lslAESRounds);
    
           // There will be Rounds - 1 identical rounds
           j = lslAESRounds - 1;
           while (j > 0) {
               lslAESInvertShiftRows();
               lslAESInvertSubBytes();
               lslAESAddRoundKey(j--);
               lslAESInvertMixColumns();
           }
    
           // The last round requires no mixing
           lslAESInvertShiftRows();
           lslAESInvertSubBytes();
           lslAESAddRoundKey(0);
       }

       // Ciphertext is generated differently by different modes
       if (lslAESMode == LSLAES_MODE_ECB) {
            // For EBC we simply output ciphertext
           output = (output = []) + output + [
               (lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | (lslAESStateX0Y3),
               (lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | (lslAESStateX1Y3),
               (lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | (lslAESStateX2Y3),
               (lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | (lslAESStateX3Y3)
           ];
        } else if (lslAESMode == LSLAES_MODE_CBC) {
            // For CBC we XOR with previous block before output
           output = (output = []) + output + [
               prevBlock0 ^ 
               ((lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | (lslAESStateX0Y3)),
               prevBlock1 ^ 
               ((lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | (lslAESStateX1Y3)),
               prevBlock2 ^ 
               ((lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | (lslAESStateX2Y3)),
               prevBlock3 ^ 
               ((lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | (lslAESStateX3Y3))
           ];
           
           prevBlock0 = nextBlock0;
           prevBlock1 = nextBlock1;
           prevBlock2 = nextBlock2;
           prevBlock3 = nextBlock3;
        } else if (lslAESMode == LSLAES_MODE_CFB) {
            // For CBF we XOR input block for output and carry 
           // input for encryption next-block
           prevBlock0 = llList2Integer(data, 0);
           prevBlock1 = llList2Integer(data, 1);
           prevBlock2 = llList2Integer(data, 2);
           prevBlock3 = llList2Integer(data, 3);
           
           output = (output = []) + output + [
               prevBlock0 ^ 
               ((lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | (lslAESStateX0Y3)),
               prevBlock1 ^ 
               ((lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | (lslAESStateX1Y3)),
               prevBlock2 ^ 
               ((lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | (lslAESStateX2Y3)),
               prevBlock3 ^ 
               ((lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | (lslAESStateX3Y3))
           ];
        } else if (lslAESMode == LSLAES_MODE_NOFB) {
            // For OBF we pass encrypted result into next-block,
            // but XOR with an input block for output
           output = (output = []) + output + [
               llList2Integer(data, 0) ^ 
               ((lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | (lslAESStateX0Y3)),
               llList2Integer(data, 1) ^ 
               ((lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | (lslAESStateX1Y3)),
               llList2Integer(data, 2) ^ 
               ((lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | (lslAESStateX2Y3)),
               llList2Integer(data, 3) ^ 
               ((lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | (lslAESStateX3Y3))
           ];
        }

        // Reduce input
       if (l > 4) 
           data = llList2List((data = []) + data, 4, -1);
       else data = [];
       l -= 4;
   }
   
   return (output = []) + output;

}

// Encrypts a list of integers into a list of encrypted integers. // Padding must be performed before being called so that data is a multiple of // 128-bits long. list lslAESCipher(list data) {

   // If non-EBC mode is enabled, we must prime the state with the input vector
   if (lslAESMode != LSLAES_MODE_ECB) lslAESLoadInputVector();

   integer l = (data != []);
   integer j = 0;
   list output = [];
   
   while (l > 0) {
       // Different modes treat blocks differently
       if (lslAESMode == LSLAES_MODE_ECB) {
           // For ECB simply copy input into state variables
           j = llList2Integer(data, 0);
           lslAESStateX0Y0 = ((j >> 24) & 0xFF);
           lslAESStateX0Y1 = ((j >> 16) & 0xFF);
           lslAESStateX0Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX0Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 1);
           lslAESStateX1Y0 = ((j >> 24) & 0xFF);
           lslAESStateX1Y1 = ((j >> 16) & 0xFF);
           lslAESStateX1Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX1Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 2);
           lslAESStateX2Y0 = ((j >> 24) & 0xFF);
           lslAESStateX2Y1 = ((j >> 16) & 0xFF);
           lslAESStateX2Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX2Y3 = ((j      ) & 0xFF);
           j = llList2Integer(data, 3);
           lslAESStateX3Y0 = ((j >> 24) & 0xFF);
           lslAESStateX3Y1 = ((j >> 16) & 0xFF);
           lslAESStateX3Y2 = ((j >> 8 ) & 0xFF);
           lslAESStateX3Y3 = ((j      ) & 0xFF);
       } else if (lslAESMode == LSLAES_MODE_CBC) {
           // For CBC we XOR with the previous block to reduce 
           // chances of patterns occurring
           j = llList2Integer(data, 0);
           lslAESStateX0Y0 = lslAESStateX0Y0 ^ ((j >> 24) & 0xFF);
           lslAESStateX0Y1 = lslAESStateX0Y1 ^ ((j >> 16) & 0xFF);
           lslAESStateX0Y2 = lslAESStateX0Y2 ^ ((j >> 8 ) & 0xFF);
           lslAESStateX0Y3 = lslAESStateX0Y3 ^ ((j      ) & 0xFF);
           j = llList2Integer(data, 1);
           lslAESStateX1Y0 = lslAESStateX1Y0 ^ ((j >> 24) & 0xFF);
           lslAESStateX1Y1 = lslAESStateX1Y1 ^ ((j >> 16) & 0xFF);
           lslAESStateX1Y2 = lslAESStateX1Y2 ^ ((j >> 8 ) & 0xFF);
           lslAESStateX1Y3 = lslAESStateX1Y3 ^ ((j      ) & 0xFF);
           j = llList2Integer(data, 2);
           lslAESStateX2Y0 = lslAESStateX2Y0 ^ ((j >> 24) & 0xFF);
           lslAESStateX2Y1 = lslAESStateX2Y1 ^ ((j >> 16) & 0xFF);
           lslAESStateX2Y2 = lslAESStateX2Y2 ^ ((j >> 8 ) & 0xFF);
           lslAESStateX2Y3 = lslAESStateX2Y3 ^ ((j      ) & 0xFF);
           j = llList2Integer(data, 3);
           lslAESStateX3Y0 = lslAESStateX3Y0 ^ ((j >> 24) & 0xFF);
           lslAESStateX3Y1 = lslAESStateX3Y1 ^ ((j >> 16) & 0xFF);
           lslAESStateX3Y2 = lslAESStateX3Y2 ^ ((j >> 8 ) & 0xFF);
           lslAESStateX3Y3 = lslAESStateX3Y3 ^ ((j      ) & 0xFF);
       }

       lslAESPerformCipher();
       
       if (lslAESMode == LSLAES_MODE_NOFB) {
           // For OFB we XOR blocks for output but don't carry them 
           // to the next stage.
           output = (output = []) + output + [
               llList2Integer(data, 0) ^ 
               ((lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | lslAESStateX0Y3),
               llList2Integer(data, 1) ^ 
               ((lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | lslAESStateX1Y3),
               llList2Integer(data, 2) ^ 
               ((lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | lslAESStateX2Y3),
               llList2Integer(data, 3) ^ 
               ((lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | lslAESStateX3Y3)
           ];
       } else {
           if (lslAESMode == LSLAES_MODE_CFB) {
               // For CFB we XOR blocks and carry them to the next 
               // stage (by keeping the result in state)
               j = llList2Integer(data, 0);
               lslAESStateX0Y0 = lslAESStateX0Y0 ^ ((j >> 24) & 0xFF);
               lslAESStateX0Y1 = lslAESStateX0Y1 ^ ((j >> 16) & 0xFF);
               lslAESStateX0Y2 = lslAESStateX0Y2 ^ ((j >> 8 ) & 0xFF);
               lslAESStateX0Y3 = lslAESStateX0Y3 ^ ((j      ) & 0xFF);
               j = llList2Integer(data, 1);
               lslAESStateX1Y0 = lslAESStateX1Y0 ^ ((j >> 24) & 0xFF);
               lslAESStateX1Y1 = lslAESStateX1Y1 ^ ((j >> 16) & 0xFF);
               lslAESStateX1Y2 = lslAESStateX1Y2 ^ ((j >> 8 ) & 0xFF);
               lslAESStateX1Y3 = lslAESStateX1Y3 ^ ((j      ) & 0xFF);
               j = llList2Integer(data, 2);
               lslAESStateX2Y0 = lslAESStateX2Y0 ^ ((j >> 24) & 0xFF);
               lslAESStateX2Y1 = lslAESStateX2Y1 ^ ((j >> 16) & 0xFF);
               lslAESStateX2Y2 = lslAESStateX2Y2 ^ ((j >> 8 ) & 0xFF);
               lslAESStateX2Y3 = lslAESStateX2Y3 ^ ((j      ) & 0xFF);
               j = llList2Integer(data, 3);
               lslAESStateX3Y0 = lslAESStateX3Y0 ^ ((j >> 24) & 0xFF);
               lslAESStateX3Y1 = lslAESStateX3Y1 ^ ((j >> 16) & 0xFF);
               lslAESStateX3Y2 = lslAESStateX3Y2 ^ ((j >> 8 ) & 0xFF);
               lslAESStateX3Y3 = lslAESStateX3Y3 ^ ((j      ) & 0xFF);
           }
       
           output = (output = []) + output + [
               (lslAESStateX0Y0 << 24) | (lslAESStateX0Y1 << 16) | 
                   (lslAESStateX0Y2 << 8) | lslAESStateX0Y3,
               (lslAESStateX1Y0 << 24) | (lslAESStateX1Y1 << 16) | 
                   (lslAESStateX1Y2 << 8) | lslAESStateX1Y3,
               (lslAESStateX2Y0 << 24) | (lslAESStateX2Y1 << 16) | 
                   (lslAESStateX2Y2 << 8) | lslAESStateX2Y3,
               (lslAESStateX3Y0 << 24) | (lslAESStateX3Y1 << 16) | 
                   (lslAESStateX3Y2 << 8) | lslAESStateX3Y3
           ];
       }

        // Reduce input
       if (l > 4) 
           data = llList2List((data = []) + data, 4, -1);
       else data = [];
       l -= 4;
   }

   return (output = []) + output;

}

// Simply performs the cipher operation on current state, separated // for convenience with CBF and OBF modes (which perform a cipher in // order to decrypt data). lslAESPerformCipher() {

   // Add first round-key before starting rounds
   lslAESAddRoundKey(0);

   // There will be Rounds iterations, the first 
   // Rounds - 1 are identical.
   integer j = 1;
   while (j < lslAESRounds) {
       lslAESSubBytes();
       lslAESShiftRows();
       lslAESMixColumns();
       lslAESAddRoundKey(j++);
   }

   // The last column performs no mix
   lslAESSubBytes();
   lslAESShiftRows();
   lslAESAddRoundKey(lslAESRounds);

}

// Expands the input vector for use in block differentiation lslAESLoadInputVector() {

   lslAESStateX0Y0 = (lslAESInputVector0 >> 24) & 0xFF;
   lslAESStateX0Y1 = (lslAESInputVector0 >> 16) & 0xFF;
   lslAESStateX0Y2 = (lslAESInputVector0 >> 8 ) & 0xFF;
   lslAESStateX0Y3 = (lslAESInputVector0      ) & 0xFF;
   
   lslAESStateX1Y0 = (lslAESInputVector1 >> 24) & 0xFF;
   lslAESStateX1Y1 = (lslAESInputVector1 >> 16) & 0xFF;
   lslAESStateX1Y2 = (lslAESInputVector1 >> 8 ) & 0xFF;
   lslAESStateX1Y3 = (lslAESInputVector1      ) & 0xFF;
   
   lslAESStateX2Y0 = (lslAESInputVector2 >> 24) & 0xFF;
   lslAESStateX2Y1 = (lslAESInputVector2 >> 16) & 0xFF;
   lslAESStateX2Y2 = (lslAESInputVector2 >> 8 ) & 0xFF;
   lslAESStateX2Y3 = (lslAESInputVector2      ) & 0xFF;
       
   lslAESStateX3Y0 = (lslAESInputVector3 >> 24) & 0xFF;
   lslAESStateX3Y1 = (lslAESInputVector3 >> 16) & 0xFF;
   lslAESStateX3Y2 = (lslAESInputVector3 >> 8 ) & 0xFF;
   lslAESStateX3Y3 = (lslAESInputVector3      ) & 0xFF;

}

//##########################################################################// // PROCESSING FUNCTIONS // //##########################################################################// // The following functions are used to process data that is being // // encrypted or decrypted. // //##########################################################################// // XORs the value with RoundKey values lslAESAddRoundKey(integer round) {

   round = round << 2;

   integer t = llList2Integer(lslAESRoundKey, round);
   lslAESStateX0Y0 = lslAESStateX0Y0 ^ ((t >> 24) & 0xFF);
   lslAESStateX0Y1 = lslAESStateX0Y1 ^ ((t >> 16) & 0xFF);
   lslAESStateX0Y2 = lslAESStateX0Y2 ^ ((t >> 8)  & 0xFF);
   lslAESStateX0Y3 = lslAESStateX0Y3 ^ (t         & 0xFF);

   t = llList2Integer(lslAESRoundKey, ++round);
   lslAESStateX1Y0 = lslAESStateX1Y0 ^ ((t >> 24) & 0xFF);
   lslAESStateX1Y1 = lslAESStateX1Y1 ^ ((t >> 16) & 0xFF);
   lslAESStateX1Y2 = lslAESStateX1Y2 ^ ((t >> 8)  & 0xFF);
   lslAESStateX1Y3 = lslAESStateX1Y3 ^ (t         & 0xFF);

   t = llList2Integer(lslAESRoundKey, ++round);
   lslAESStateX2Y0 = lslAESStateX2Y0 ^ ((t >> 24) & 0xFF);
   lslAESStateX2Y1 = lslAESStateX2Y1 ^ ((t >> 16) & 0xFF);
   lslAESStateX2Y2 = lslAESStateX2Y2 ^ ((t >> 8)  & 0xFF);
   lslAESStateX2Y3 = lslAESStateX2Y3 ^ (t         & 0xFF);

   t = llList2Integer(lslAESRoundKey, ++round);
   lslAESStateX3Y0 = lslAESStateX3Y0 ^ ((t >> 24) & 0xFF);
   lslAESStateX3Y1 = lslAESStateX3Y1 ^ ((t >> 16) & 0xFF);
   lslAESStateX3Y2 = lslAESStateX3Y2 ^ ((t >> 8)  & 0xFF);
   lslAESStateX3Y3 = lslAESStateX3Y3 ^ (t         & 0xFF);

}

// Performs a substitution using SBox lslAESSubBytes() {

   lslAESStateX0Y0 = lslAESGetSBoxByte(lslAESStateX0Y0);
   lslAESStateX0Y1 = lslAESGetSBoxByte(lslAESStateX0Y1);
   lslAESStateX0Y2 = lslAESGetSBoxByte(lslAESStateX0Y2);
   lslAESStateX0Y3 = lslAESGetSBoxByte(lslAESStateX0Y3);
   lslAESStateX1Y0 = lslAESGetSBoxByte(lslAESStateX1Y0);
   lslAESStateX1Y1 = lslAESGetSBoxByte(lslAESStateX1Y1);
   lslAESStateX1Y2 = lslAESGetSBoxByte(lslAESStateX1Y2);
   lslAESStateX1Y3 = lslAESGetSBoxByte(lslAESStateX1Y3);
   lslAESStateX2Y0 = lslAESGetSBoxByte(lslAESStateX2Y0);
   lslAESStateX2Y1 = lslAESGetSBoxByte(lslAESStateX2Y1);
   lslAESStateX2Y2 = lslAESGetSBoxByte(lslAESStateX2Y2);
   lslAESStateX2Y3 = lslAESGetSBoxByte(lslAESStateX2Y3);
   lslAESStateX3Y0 = lslAESGetSBoxByte(lslAESStateX3Y0);
   lslAESStateX3Y1 = lslAESGetSBoxByte(lslAESStateX3Y1);
   lslAESStateX3Y2 = lslAESGetSBoxByte(lslAESStateX3Y2);
   lslAESStateX3Y3 = lslAESGetSBoxByte(lslAESStateX3Y3);

}

// Performs a substition using SBoxInverted lslAESInvertSubBytes() {

   lslAESStateX0Y0 = lslAESGetSBoxInvertedByte(lslAESStateX0Y0);
   lslAESStateX0Y1 = lslAESGetSBoxInvertedByte(lslAESStateX0Y1);
   lslAESStateX0Y2 = lslAESGetSBoxInvertedByte(lslAESStateX0Y2);
   lslAESStateX0Y3 = lslAESGetSBoxInvertedByte(lslAESStateX0Y3);
   lslAESStateX1Y0 = lslAESGetSBoxInvertedByte(lslAESStateX1Y0);
   lslAESStateX1Y1 = lslAESGetSBoxInvertedByte(lslAESStateX1Y1);
   lslAESStateX1Y2 = lslAESGetSBoxInvertedByte(lslAESStateX1Y2);
   lslAESStateX1Y3 = lslAESGetSBoxInvertedByte(lslAESStateX1Y3);
   lslAESStateX2Y0 = lslAESGetSBoxInvertedByte(lslAESStateX2Y0);
   lslAESStateX2Y1 = lslAESGetSBoxInvertedByte(lslAESStateX2Y1);
   lslAESStateX2Y2 = lslAESGetSBoxInvertedByte(lslAESStateX2Y2);
   lslAESStateX2Y3 = lslAESGetSBoxInvertedByte(lslAESStateX2Y3);
   lslAESStateX3Y0 = lslAESGetSBoxInvertedByte(lslAESStateX3Y0);
   lslAESStateX3Y1 = lslAESGetSBoxInvertedByte(lslAESStateX3Y1);
   lslAESStateX3Y2 = lslAESGetSBoxInvertedByte(lslAESStateX3Y2);
   lslAESStateX3Y3 = lslAESGetSBoxInvertedByte(lslAESStateX3Y3);

}


// Performs row shifts lslAESShiftRows() {

   integer t = 0;

   // Rotate first row 1 columns to left
   t = lslAESStateX1Y0;
   lslAESStateX1Y0 = lslAESStateX1Y1;
   lslAESStateX1Y1 = lslAESStateX1Y2;
   lslAESStateX1Y2 = lslAESStateX1Y3;
   lslAESStateX1Y3 = t;

   // Rotate second row 2 columns to left
   t = lslAESStateX2Y0;
   lslAESStateX2Y0 = lslAESStateX2Y2;
   lslAESStateX2Y2 = t;

   t = lslAESStateX2Y1;
   lslAESStateX2Y1 = lslAESStateX2Y3;
   lslAESStateX2Y3 = t;

   // Rotate third row 3 columns to left
   t = lslAESStateX3Y0;
   lslAESStateX3Y0 = lslAESStateX3Y3;
   lslAESStateX3Y3 = lslAESStateX3Y2;
   lslAESStateX3Y2 = lslAESStateX3Y1;
   lslAESStateX3Y1 = t;

}

// Undoes a set of row shifts lslAESInvertShiftRows() {

   integer t = 0;

   // Rotate first row 1 columns to right
   t = lslAESStateX1Y3;
   lslAESStateX1Y3 = lslAESStateX1Y2;
   lslAESStateX1Y2 = lslAESStateX1Y1;
   lslAESStateX1Y1 = lslAESStateX1Y0;
   lslAESStateX1Y0 = t;

   // Rotate second row 2 columns to right
   t = lslAESStateX2Y0;
   lslAESStateX2Y0 = lslAESStateX2Y2;
   lslAESStateX2Y2 = t;

   t = lslAESStateX2Y1;
   lslAESStateX2Y1 = lslAESStateX2Y3;
   lslAESStateX2Y3 = t;

   // Rotate third row 3 columns to right
   t = lslAESStateX3Y0;
   lslAESStateX3Y0 = lslAESStateX3Y1;
   lslAESStateX3Y1 = lslAESStateX3Y2;
   lslAESStateX3Y2 = lslAESStateX3Y3;
   lslAESStateX3Y3 = t;

}

// Mixes columns of the state lslAESMixColumns() {

   integer t = lslAESStateX0Y0;
   integer t1 = lslAESStateX0Y0 ^ lslAESStateX1Y0 ^ 
       lslAESStateX2Y0 ^ lslAESStateX3Y0;

   integer t2 = lslAESXTimes(lslAESStateX0Y0 ^ lslAESStateX1Y0);
   lslAESStateX0Y0 = lslAESStateX0Y0 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX1Y0 ^ lslAESStateX2Y0);
   lslAESStateX1Y0 = lslAESStateX1Y0 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX2Y0 ^ lslAESStateX3Y0);
   lslAESStateX2Y0 = lslAESStateX2Y0 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX3Y0 ^ t);
   lslAESStateX3Y0 = lslAESStateX3Y0 ^ t2 ^ t1;

   t = lslAESStateX0Y1;
   t1 = lslAESStateX0Y1 ^ lslAESStateX1Y1 ^ lslAESStateX2Y1 ^ lslAESStateX3Y1;

   t2 = lslAESXTimes(lslAESStateX0Y1 ^ lslAESStateX1Y1);
   lslAESStateX0Y1 = lslAESStateX0Y1 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX1Y1 ^ lslAESStateX2Y1);
   lslAESStateX1Y1 = lslAESStateX1Y1 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX2Y1 ^ lslAESStateX3Y1);
   lslAESStateX2Y1 = lslAESStateX2Y1 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX3Y1 ^ t);
   lslAESStateX3Y1 = lslAESStateX3Y1 ^ t2 ^ t1;

   t = lslAESStateX0Y2;
   t1 = lslAESStateX0Y2 ^ lslAESStateX1Y2 ^ lslAESStateX2Y2 ^ lslAESStateX3Y2;

   t2 = lslAESXTimes(lslAESStateX0Y2 ^ lslAESStateX1Y2);
   lslAESStateX0Y2 = lslAESStateX0Y2 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX1Y2 ^ lslAESStateX2Y2);
   lslAESStateX1Y2 = lslAESStateX1Y2 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX2Y2 ^ lslAESStateX3Y2);
   lslAESStateX2Y2 = lslAESStateX2Y2 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX3Y2 ^ t);
   lslAESStateX3Y2 = lslAESStateX3Y2 ^ t2 ^ t1;

   t = lslAESStateX0Y3;
   t1 = lslAESStateX0Y3 ^ lslAESStateX1Y3 ^ lslAESStateX2Y3 ^ lslAESStateX3Y3;

   t2 = lslAESXTimes(lslAESStateX0Y3 ^ lslAESStateX1Y3);
   lslAESStateX0Y3 = lslAESStateX0Y3 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX1Y3 ^ lslAESStateX2Y3);
   lslAESStateX1Y3 = lslAESStateX1Y3 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX2Y3 ^ lslAESStateX3Y3);
   lslAESStateX2Y3 = lslAESStateX2Y3 ^ t2 ^ t1;

   t2 = lslAESXTimes(lslAESStateX3Y3 ^ t);
   lslAESStateX3Y3 = lslAESStateX3Y3 ^ t2 ^ t1;

}

// Used when column mixing integer lslAESXTimes(integer x) {

   return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)) & 0xFF;

}

// Used when column mixing integer lslAESMultiply(integer x, integer y) {

   integer xT  = lslAESXTimes(x);
   integer xT2 = lslAESXTimes(xT);
   integer xT3 = lslAESXTimes(xT2);

   return (((y & 1) * x) ^ (((y >> 1) & 1) * xT) ^ 
           (((y >> 2) & 1) * xT2) ^ (((y >> 3) & 1) * xT3) ^ 
           (((y >> 4) & 1) * lslAESXTimes(xT3))) & 0xFF;

}

// Try to understand this at your own peril! lslAESInvertMixColumns() {

   integer a = lslAESStateX0Y0;
   integer b = lslAESStateX1Y0;
   integer c = lslAESStateX2Y0;
   integer d = lslAESStateX3Y0;

   lslAESStateX0Y0 = lslAESMultiply(a, 0x0e) ^ lslAESMultiply(b, 0x0b) ^ 
       lslAESMultiply(c, 0x0d) ^ lslAESMultiply(d, 0x09);
   lslAESStateX1Y0 = lslAESMultiply(a, 0x09) ^ lslAESMultiply(b, 0x0e) ^ 
       lslAESMultiply(c, 0x0b) ^ lslAESMultiply(d, 0x0d);
   lslAESStateX2Y0 = lslAESMultiply(a, 0x0d) ^ lslAESMultiply(b, 0x09) ^ 
       lslAESMultiply(c, 0x0e) ^ lslAESMultiply(d, 0x0b);
   lslAESStateX3Y0 = lslAESMultiply(a, 0x0b) ^ lslAESMultiply(b, 0x0d) ^ 
       lslAESMultiply(c, 0x09) ^ lslAESMultiply(d, 0x0e);

   a = lslAESStateX0Y1;
   b = lslAESStateX1Y1;
   c = lslAESStateX2Y1;
   d = lslAESStateX3Y1;

   lslAESStateX0Y1 = lslAESMultiply(a, 0x0e) ^ lslAESMultiply(b, 0x0b) ^ 
       lslAESMultiply(c, 0x0d) ^ lslAESMultiply(d, 0x09);
   lslAESStateX1Y1 = lslAESMultiply(a, 0x09) ^ lslAESMultiply(b, 0x0e) ^ 
       lslAESMultiply(c, 0x0b) ^ lslAESMultiply(d, 0x0d);
   lslAESStateX2Y1 = lslAESMultiply(a, 0x0d) ^ lslAESMultiply(b, 0x09) ^ 
       lslAESMultiply(c, 0x0e) ^ lslAESMultiply(d, 0x0b);
   lslAESStateX3Y1 = lslAESMultiply(a, 0x0b) ^ lslAESMultiply(b, 0x0d) ^ 
       lslAESMultiply(c, 0x09) ^ lslAESMultiply(d, 0x0e);

   a = lslAESStateX0Y2;
   b = lslAESStateX1Y2;
   c = lslAESStateX2Y2;
   d = lslAESStateX3Y2;

   lslAESStateX0Y2 = lslAESMultiply(a, 0x0e) ^ lslAESMultiply(b, 0x0b) ^ 
       lslAESMultiply(c, 0x0d) ^ lslAESMultiply(d, 0x09);
   lslAESStateX1Y2 = lslAESMultiply(a, 0x09) ^ lslAESMultiply(b, 0x0e) ^ 
       lslAESMultiply(c, 0x0b) ^ lslAESMultiply(d, 0x0d);
   lslAESStateX2Y2 = lslAESMultiply(a, 0x0d) ^ lslAESMultiply(b, 0x09) ^ 
       lslAESMultiply(c, 0x0e) ^ lslAESMultiply(d, 0x0b);
   lslAESStateX3Y2 = lslAESMultiply(a, 0x0b) ^ lslAESMultiply(b, 0x0d) ^ 
       lslAESMultiply(c, 0x09) ^ lslAESMultiply(d, 0x0e);

   a = lslAESStateX0Y3;
   b = lslAESStateX1Y3;
   c = lslAESStateX2Y3;
   d = lslAESStateX3Y3;

   lslAESStateX0Y3 = lslAESMultiply(a, 0x0e) ^ lslAESMultiply(b, 0x0b) ^ 
       lslAESMultiply(c, 0x0d) ^ lslAESMultiply(d, 0x09);
   lslAESStateX1Y3 = lslAESMultiply(a, 0x09) ^ lslAESMultiply(b, 0x0e) ^ 
       lslAESMultiply(c, 0x0b) ^ lslAESMultiply(d, 0x0d);
   lslAESStateX2Y3 = lslAESMultiply(a, 0x0d) ^ lslAESMultiply(b, 0x09) ^ 
       lslAESMultiply(c, 0x0e) ^ lslAESMultiply(d, 0x0b);
   lslAESStateX3Y3 = lslAESMultiply(a, 0x0b) ^ lslAESMultiply(b, 0x0d) ^ 
       lslAESMultiply(c, 0x09) ^ lslAESMultiply(d, 0x0e);

}

//##########################################################################// // ENCRYPTION SET-UP FUNCTIONS // //##########################################################################// // The following functions are used to set-up the AES encryption engine // // using the value is Key. // //##########################################################################// // Takes the key bytes provided and sets up the engine ready to encrypt or // decrypt using it. list lslAESKeyExpansion(list keyBytes) {

   // Don't need the bit-count and the first round key is the key itself
   integer len = (
       (
           lslAESRoundKey = llDeleteSubList( // Remove header, copy into rounds
               (lslAESRoundKey = keyBytes = []) + keyBytes, 
               0, 
               0
           )
       ) != []); // Get the length

   // Check that we are within reasonable limits
   if ((len < 4) || (len > 8)) {
       lslAESRoundKey = [];
       return ["Invalid key size; must be 128, 192, or 256 bits!"];
   }

   // Calculate the number of required rounds
   lslAESRounds = len + 6;

   // All others are found from previous keys
   integer i = 0;
   integer x = (len * 3) + 28;

   integer t = llList2Integer(lslAESRoundKey, -1);

   do {
       if (!(i % len)) {
           // Rotate by 1, SubWord and Nudge 
           // [0x01020408, 0x01020408, 0x1b366cd8]
           t = ((lslAESGetSBoxByte((t >> 16) & 0xFF) ^ 
                    (0x000D8080 >> (7 ^ (i / len)))) << 24) | 
                (lslAESGetSBoxByte((t >>  8) & 0xFF) << 16) | 
                (lslAESGetSBoxByte((t      ) & 0xFF) <<  8) |
                 lslAESGetSBoxByte((t >> 24) & 0xFF);
       } else if ((len > 6) && (i % len) == 4) {
           // SubWord
           t = (lslAESGetSBoxByte((t >> 24) & 0xFF) << 24) | 
               (lslAESGetSBoxByte((t >> 16) & 0xFF) << 16) | 
               (lslAESGetSBoxByte((t >>  8) & 0xFF) <<  8) |
               (lslAESGetSBoxByte((t      ) & 0xFF)      );
       }

       // XOR k with four previous RoundKey values And add the 
       // new entries, yay!
       lslAESRoundKey = (lslAESRoundKey = []) + lslAESRoundKey + 
           (t = (t ^ llList2Integer(lslAESRoundKey, i)));
   } while ((i = -~i) < x);

   // On success no error message is returned
   return [1];

}

// Takes a list of 32-bit words and uses them to initialise the // input vector used by CBC and similar modes. Data must contain // at least 128-bits of data (all else is discarded) list lslAESSetInputVector(list data) {

   if ((data != []) < 5) 
       return ["Input vector must be at least 128-bits long"];
       
   // Ignore index 0 (the header)
   lslAESInputVector0 = llList2Integer(data, 1);
   lslAESInputVector1 = llList2Integer(data, 2);
   lslAESInputVector2 = llList2Integer(data, 3);
   lslAESInputVector3 = llList2Integer(data, 4);
   
   return [1];

}

//##########################################################################// // SERIALISATION FUNCTIONS // //##########################################################################// // The following functions are used to serialise a string into a list of // // byte data for use as a key, or to encrypt/decrypt. // //##########################################################################// // Load value as integers from a base64 string list lslAESBase64ToBytes(string base64Data) {

   integer x = llSubStringIndex(base64Data, "=");
   if (x > 0) 
       base64Data = llGetSubString(
           (base64Data = "") + base64Data, 
           0, 
           --x
       );
   return lslAESStringToBytes(
       (base64Data = "") + base64Data, 
       6, 
       LSLAES_BASE64_CHARS
   );

}

// Load value as integers from a hex string list lslAESHexToBytes(string hexData) {

   if (llGetSubString(hexData, 0, 1) == "0x") 
       hexData = llGetSubString((hexData = "") + hexData, 2, -1);
   return lslAESStringToBytes(
       (hexData = "") + hexData, 
       4, 
       LSLAES_HEX_CHARS
   );

}

// Loads a string as a list of integers, using the given bit <width> and // appropriate <alphabet> into Key list lslAESStringToBytes(string s, integer width, string alphabet) {

   integer l = llStringLength(s);

   list n = [l * width]; // Add bit-length
   integer bitbuf = 0;
   integer adjust = 32;

   integer i = 0;
   integer val;
   while (i < l) {
       val = llSubStringIndex(alphabet, llGetSubString(s, i, i));
       if (val < 0) {
           s = "";
           return (n = []) + 
               ["Invalid character at index "+(string)i];
       }

       if ((adjust -= width) <= 0) {
           bitbuf = bitbuf | (val >> -adjust);
           n = (n = []) + n + [bitbuf];

           adjust += 32;
           if (adjust < 32) bitbuf = (val << adjust);
           else bitbuf = 0;
       } else bitbuf = bitbuf | (val << adjust);

       ++i;
   }

   s = "";
   if (adjust < 32) 
       return (n = []) + n + [bitbuf];
   return (n = []) + n;

}

// Returns a hex string representing integers b, preceeded with "0x". string lslAESBytesToHex(list b) {

   return "0x" + lslAESBytesToString((b = []) + b, 4, LSLAES_HEX_CHARS);

}

// Returns a base64 string representing integers b, with padding equals signs string lslAESBytesToBase64(list b) {

   string s = lslAESBytesToString((b = []) + b, 6, LSLAES_BASE64_CHARS);
   integer l = llStringLength(s) % 3;
   if (l) {
       if (l == 1) return (s = "") + s + "==";
       return (s = "") + s + "=";
   }
   return (s = "") + s;

}

// Outputs integer data from b as characters representing width bits taken from // alphabet string lslAESBytesToString(list b, integer width, string alphabet) {

   integer bits = llList2Integer(b, 0);

   integer i = 0;
   integer mask = ~(-1 << width);
   integer shift = 32 - width;

   integer available = 0;
   integer prev = 0;
   integer buf;
   integer extra;
   integer value;

   string s = "";

   @lslAESBytesToStringLoop;
   if((bits -= 32) > -32) {
       available += 32 + (bits * (0 > bits));
       buf = llList2Integer(b, ++i);
       if (available >= width) {
           if (prev) {
               s = (s = "") + s + 
                   llGetSubString(
                       alphabet, 
                       value = (
                           extra | 
                           (
                               (buf >> (shift + prev)) & 
                               ~(-1 << (width - prev))
                           )
                       ), 
                       value
                   );
               buf = buf << (width - prev);
               available -= width;
           }
           while(available >= width) {
               s = (s = "") + s + 
                   llGetSubString(
                       alphabet, 
                       value = ((buf >> shift) & mask),
                       value
                   );
               buf = buf << width;
               available -= width;
           }
           if (prev = available) // Update prev
               extra = (buf >> shift) & mask;
           jump lslAESBytesToStringLoop;
       }
   }
   if(available) {
       mask = -1 << (width - prev);
       return (s = "") + s + 
           llGetSubString(
               alphabet, 
               value = ((extra & mask) | 
                       (
                           (buf >> (shift + prev)) & 
                           ((-1 << (width - available)) ^ mask))
                       ), 
               value
           );
   }
   return (s = "") + s;

}

error(integer link, string str, key id) {

   llMessageLinked(
       link,
       LSLAES_FILTER_REPLY | LSLAES_COMMAND_ERROR,
       str,
       id
   );

}

default {

   link_message(integer x, integer y, string msg, key id) {        
       // Is the message for us?
       if ((y & LSLAES_FILTER_MASK) == LSLAES_FILTER_REQUEST) {
           // Refuse overly large messages
           if (llStringLength(msg) > LSLAES_MAX_SIZE) {
               error(
                   x, 
                   "Maxmimum message length is " + 
                   (string)LSLAES_MAX_SIZE + 
                   " characters", 
                   id
               );
               return;
           }
           
           // Special case for COMMAND_SETUP
           if ((y & LSLAES_COMMAND_MASK) == LSLAES_COMMAND_SETUP) {
               // Break up flags
               if (msg != "") {
                   list flags = llCSV2List((msg = "") + msg);
                   integer i = 0; integer l = (flags != []);
                      integer j = 0; list flag = [];
                      do {
                          flag = [llToUpper(llList2String(flags, i))];
                          
                          if ((j = llListFindList(LSLAES_MODES, flag)) >= 0) 
                              lslAESMode = llList2Integer(LSLAES_MODES, ++j);
                          else if ((j = llListFindList(LSLAES_PADS, flag)) >= 0) 
                              lslAESPad  = llList2Integer(LSLAES_PADS, ++j);
                          else if ((string)flag == "PAD_SIZE") {
                              j = llList2Integer(flags, ++i); // Next value should be pad-size
                              if (j <= 0) j = LSLAES_PAD_SIZE;
                              else if (j > LSLAES_PAD_SIZE) {
                                  error(x, "Maximum pad-size is "+(string)LSLAES_PAD_SIZE+" bits", id);
                                  return;
                              } else if (j % 128) {
                                  error(x, "Pad-size must be a multiple of 128-bits", id);
                                  return;
                              }
                              
                              lslAESPadSize = j;
                          } else {
                              error(x, "Unsupported flag '"+(string)flag+"'", id);
                              return;
                          }
                      } while ((++i) < l);
               }
               
                  // Construct reply
               llMessageLinked(
                   x,
                   LSLAES_FILTER_REPLY | LSLAES_COMMAND_SETUP,
                   "",
                   id
               );
               return;
           }
           
           // What type of data do we have?
           integer type = y & LSLAES_INPUT_TYPE_MASK;
           list data = [];
           if (type == LSLAES_INPUT_HEX) 
               data = lslAESHexToBytes((msg = "") + msg);
           else if (type == LSLAES_INPUT_BASE64) 
               data = lslAESBase64ToBytes((msg = "") + msg);
           else data = [(msg = "") + "Unsupported input-type"];
               
           // Was data parsed successfully?
           if (llGetListEntryType(data, 0) != TYPE_INTEGER) {
               error(x, llList2String((data = []) + data, 0), id);
               return;
           }
           
           // Now determine mode of operation
           type = y & LSLAES_COMMAND_MASK;
           if (type == LSLAES_COMMAND_PRIME) 
               data = lslAESKeyExpansion((data = []) + data);
           else if (type == LSLAES_COMMAND_ENCRYPT) 
               data = lslAESPadCipher((data = []) + data);
           else if (type == LSLAES_COMMAND_DECRYPT) 
               data = lslAESInvertPadCipher((data = []) + data);
           else if (type == LSLAES_COMMAND_HASH) {
               // Get length of hash as 32-bit multiple
               integer l = ((y & LSLAES_VARIABLE_MASK) >> LSLAES_VARIABLE_SHIFT) << 5;
               if (l > LSLAES_HASH_SIZE) 
                   data = [
                       "Max hash-size is " + 
                       (string)LSLAES_HASH_SIZE + 
                       " bits"
                   ];
               else if (l <= 0) l = LSLAES_HASH_SIZE;
               
               data = lslAESHash((data = []) + data, l);
           } else if (type == LSLAES_COMMAND_INIT) 
               data = lslAESSetInputVector((data = []) + data);
           else data = ["Unsupported mode"];
           
           // Was mode executed successfully?
           if (llGetListEntryType(data, 0) != TYPE_INTEGER) {
               error(x, llList2String((data = []) + data, 0), id);
               return;
           }
           
           // Convert into requested output type
           integer output = 0;
           
           if ((type != LSLAES_COMMAND_PRIME) && (type != LSLAES_COMMAND_INIT)) {
               output = y & LSLAES_OUTPUT_TYPE_MASK;
               if (output == LSLAES_OUTPUT_HEX) {
                   msg = lslAESBytesToHex((data = []) + data);
                   output = LSLAES_INPUT_HEX;
               } else if (output == LSLAES_OUTPUT_BASE64) {
                   msg = lslAESBytesToBase64((data = []) + data);
                   output = LSLAES_INPUT_BASE64;
               } else {
                   error(x, "Invalid output type", id);
                   return;
               }
           }
           
           // Construct reply
           llMessageLinked(
               x,
               LSLAES_FILTER_REPLY | type | output,
               (msg = "") + msg,
               id
           );
       }
   }

}</lsl>