Open Source Repository

Home /open-symphony/oscore-2.2.6 | Repository Home


com/opensymphony/module/random/Rijndael_Algorithm.java
/*
 * Copyright (c) 2002-2003 by OpenSymphony
 * All rights reserved.
 */
package com.opensymphony.module.random;


/* ====================================================================
 * The OpenSymphony Software License, Version 1.1
 *
 * (this license is derived and fully compatible with the Apache Software
 * License - see http://www.apache.org/LICENSE.txt)
 *
 * Copyright (c) 2001 The OpenSymphony Group. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        OpenSymphony Group (http://www.opensymphony.com/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "OpenSymphony" and "The OpenSymphony Group"
 *    must not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact [email protected] .
 *
 * 5. Products derived from this software may not be called "OpenSymphony"
 *    or "OSCore", nor may "OpenSymphony" or "OSCore" appear in their
 *    name, without prior written permission of the OpenSymphony Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */
import java.io.PrintWriter;

import java.security.InvalidKeyException;


/**
 * DOCUMENT ME!
 *
 @author $author$
 @version $Revision: 131 $
 */
public final class Rijndael_Algorithm // implicit no-argument constructor
 {
    //~ Static fields/initializers /////////////////////////////////////////////

    static final String NAME = "Rijndael_Algorithm";
    static final boolean IN = true;
    static final boolean OUT = false;
    static final boolean DEBUG = Rijndael_Properties.GLOBAL_DEBUG;
    static final int debuglevel = DEBUG ? Rijndael_Properties.getLevel(NAME0;
    static final PrintWriter err = DEBUG ? Rijndael_Properties.getOutput() null;
    static final boolean TRACE = Rijndael_Properties.isTraceable(NAME);

    // Constants and variables
    //...........................................................................
    static final int BLOCK_SIZE = 16// default block size in bytes
    static final int[] alog = new int[256];
    static final int[] log = new int[256];
    static final byte[] S = new byte[256];
    static final byte[] Si = new byte[256];
    static final int[] T1 = new int[256];
    static final int[] T2 = new int[256];
    static final int[] T3 = new int[256];
    static final int[] T4 = new int[256];
    static final int[] T5 = new int[256];
    static final int[] T6 = new int[256];
    static final int[] T7 = new int[256];
    static final int[] T8 = new int[256];
    static final int[] U1 = new int[256];
    static final int[] U2 = new int[256];
    static final int[] U3 = new int[256];
    static final int[] U4 = new int[256];
    static final byte[] rcon = new byte[30];
    static final int[][][] shifts = new int[][][] {
        {
            {00},
            {13},
            {22},
            {31}
        },
        {
            {00},
            {15},
            {24},
            {33}
        },
        {
            {00},
            {17},
            {35},
            {44}
        }
    };
    private static final char[] HEX_DIGITS = {
        '0''1''2''3''4''5''6''7''8''9''A''B''C''D',
        'E''F'
    };

    // Static code - to intialise S-boxes and T-boxes
    //...........................................................................
    static {
        long time = System.currentTimeMillis();

        if (DEBUG && (debuglevel > 6)) {
            System.out.println("Algorithm Name: " + Rijndael_Properties.FULL_NAME);
            System.out.println("Electronic Codebook (ECB) Mode");
            System.out.println();
        }

        int ROOT = 0x11B;
        int i;
        int j = 0;

        //
        // produce log and alog tables, needed for multiplying in the
        // field GF(2^m) (generator = 3)
        //
        alog[01;

        for (i = 1; i < 256; i++) {
            j = (alog[i - 1<< 1^ alog[i - 1];

            if ((j & 0x100!= 0) {
                j ^= ROOT;
            }

            alog[i= j;
        }

        for (i = 1; i < 255; i++) {
            log[alog[i]] = i;
        }

        byte[][] A = new byte[][] {
            {11111000},
            {01111100},
            {00111110},
            {00011111},
            {10001111},
            {11000111},
            {11100011},
            {11110001}
        };
        byte[] B = new byte[] {01100011};

        //
        // substitution box based on F^{-1}(x)
        //
        int t;
        byte[][] box = new byte[256][8];
        box[1][71;

        for (i = 2; i < 256; i++) {
            j = alog[255 - log[i]];

            for (t = 0; t < 8; t++) {
                box[i][t(byte) ((j >>> (- t)) 0x01);
            }
        }

        //
        // affine transform:  box[i] <- B + A*box[i]
        //
        byte[][] cox = new byte[256][8];

        for (i = 0; i < 256; i++) {
            for (t = 0; t < 8; t++) {
                cox[i][t= B[t];

                for (j = 0; j < 8; j++) {
                    cox[i][t^= (A[t][j* box[i][j]);
                }
            }
        }

        //
        // S-boxes and inverse S-boxes
        //
        for (i = 0; i < 256; i++) {
            S[i(byte) (cox[i][0<< 7);

            for (t = 1; t < 8; t++) {
                S[i^= (cox[i][t<< (- t));
            }

            Si[S[i0xFF(bytei;
        }

        //
        // T-boxes
        //
        byte[][] G = new byte[][] {
            {2113},
            {3211},
            {1321},
            {1132}
        };
        byte[][] AA = new byte[4][8];

        for (i = 0; i < 4; i++) {
            for (j = 0; j < 4; j++) {
                AA[i][j= G[i][j];
            }

            AA[i][i + 41;
        }

        byte pivot;
        byte tmp;
        byte[][] iG = new byte[4][4];

        for (i = 0; i < 4; i++) {
            pivot = AA[i][i];

            if (pivot == 0) {
                t = i + 1;

                while ((AA[t][i== 0&& (t < 4)) {
                    t++;
                }

                if (t == 4) {
                    throw new RuntimeException("G matrix is not invertible");
                else {
                    for (j = 0; j < 8; j++) {
                        tmp = AA[i][j];
                        AA[i][j= AA[t][j];
                        AA[t][j(bytetmp;
                    }

                    pivot = AA[i][i];
                }
            }

            for (j = 0; j < 8; j++) {
                if (AA[i][j!= 0) {
                    AA[i][j(bytealog[((255 + log[AA[i][j0xFF]) - log[pivot & 0xFF]) 255];
                }
            }

            for (t = 0; t < 4; t++) {
                if (i != t) {
                    for (j = i + 1; j < 8; j++) {
                        AA[t][j^= mul(AA[i][j], AA[t][i]);
                    }

                    AA[t][i0;
                }
            }
        }

        for (i = 0; i < 4; i++) {
            for (j = 0; j < 4; j++) {
                iG[i][j= AA[i][j + 4];
            }
        }

        int s;

        for (t = 0; t < 256; t++) {
            s = S[t];
            T1[t= mul4(s, G[0]);
            T2[t= mul4(s, G[1]);
            T3[t= mul4(s, G[2]);
            T4[t= mul4(s, G[3]);

            s = Si[t];
            T5[t= mul4(s, iG[0]);
            T6[t= mul4(s, iG[1]);
            T7[t= mul4(s, iG[2]);
            T8[t= mul4(s, iG[3]);

            U1[t= mul4(t, iG[0]);
            U2[t= mul4(t, iG[1]);
            U3[t= mul4(t, iG[2]);
            U4[t= mul4(t, iG[3]);
        }

        //
        // round constants
        //
        rcon[01;

        int r = 1;

        for (t = 1; t < 30;) {
            rcon[t++(byte) (r = mul(2, r));
        }

        time = System.currentTimeMillis() - time;

        if (DEBUG && (debuglevel > 8)) {
            System.out.println("==========");
            System.out.println();
            System.out.println("Static Data");
            System.out.println();
            System.out.println("S[]:");

            for (i = 0; i < 16; i++) {
                for (j = 0; j < 16; j++) {
                    System.out.print("0x" + byteToString(S[(i * 16+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("Si[]:");

            for (i = 0; i < 16; i++) {
                for (j = 0; j < 16; j++) {
                    System.out.print("0x" + byteToString(Si[(i * 16+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("iG[]:");

            for (i = 0; i < 4; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + byteToString(iG[i][j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T1[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T1[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T2[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T2[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T3[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T3[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T4[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T4[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T5[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T5[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T6[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T6[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T7[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T7[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("T8[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(T8[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("U1[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(U1[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("U2[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(U2[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("U3[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(U3[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("U4[]:");

            for (i = 0; i < 64; i++) {
                for (j = 0; j < 4; j++) {
                    System.out.print("0x" + intToString(U4[(i * 4+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("rcon[]:");

            for (i = 0; i < 5; i++) {
                for (j = 0; j < 6; j++) {
                    System.out.print("0x" + byteToString(rcon[(i * 6+ j]) ", ");
                }

                System.out.println();
            }

            System.out.println();
            System.out.println("Total initialization time: " + time + " ms.");
            System.out.println();
        }
    }

    //~ Methods ////////////////////////////////////////////////////////////////

    /**
     * Return The number of rounds for a given Rijndael's key and block sizes.
     *
     @param keySize    The size of the user key material in bytes.
     @param blockSize  The desired block size in bytes.
     @return The number of rounds for a given Rijndael's key and
     *      block sizes.
     */
    public static int getRounds(int keySize, int blockSize) {
        switch (keySize) {
        case 16:
            return (blockSize == 1610 ((blockSize == 2412 14);

        case 24:
            return (blockSize != 3212 14;

        default// 32 bytes = 256 bits

            return 14;
        }
    }

    /**
     * Convenience method to decrypt exactly one block of plaintext, assuming
     * Rijndael's default block size (128-bit).
     *
     @param  in         The ciphertext.
     @param  inOffset   Index of in from which to start considering data.
     @param  sessionKey The session key to use for decryption.
     @return The plaintext generated from a ciphertext using the session key.
     */
    public static byte[] blockDecrypt(byte[] in, int inOffset, Object sessionKey) {
        if (DEBUG) {
            trace(IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
        }

        int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]// extract decryption round keys
        int ROUNDS = Kd.length - 1;
        int[] Kdr = Kd[0];

        // ciphertext to ints + key
        int t0 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Kdr[0];
        int t1 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Kdr[1];
        int t2 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Kdr[2];
        int t3 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Kdr[3];

        int a0;
        int a1;
        int a2;
        int a3;

        for (int r = 1; r < ROUNDS; r++) { // apply round transforms
            Kdr = Kd[r];
            a0 = (T5[(t0 >>> 240xFF^ T6[(t3 >>> 160xFF^ T7[(t2 >>> 80xFF^ T8[t1 & 0xFF]) ^ Kdr[0];
            a1 = (T5[(t1 >>> 240xFF^ T6[(t0 >>> 160xFF^ T7[(t3 >>> 80xFF^ T8[t2 & 0xFF]) ^ Kdr[1];
            a2 = (T5[(t2 >>> 240xFF^ T6[(t1 >>> 160xFF^ T7[(t0 >>> 80xFF^ T8[t3 & 0xFF]) ^ Kdr[2];
            a3 = (T5[(t3 >>> 240xFF^ T6[(t2 >>> 160xFF^ T7[(t1 >>> 80xFF^ T8[t0 & 0xFF]) ^ Kdr[3];
            t0 = a0;
            t1 = a1;
            t2 = a2;
            t3 = a3;

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("PT" + r + "=" + intToString(t0+ intToString(t1+ intToString(t2+ intToString(t3));
            }
        }

        // last round is special
        byte[] result = new byte[16]// the resulting plaintext
        Kdr = Kd[ROUNDS];

        int tt = Kdr[0];
        result[0(byte) (Si[(t0 >>> 240xFF(tt >>> 24));
        result[1(byte) (Si[(t3 >>> 160xFF(tt >>> 16));
        result[2(byte) (Si[(t2 >>> 80xFF(tt >>> 8));
        result[3(byte) (Si[t1 & 0xFF^ tt);
        tt = Kdr[1];
        result[4(byte) (Si[(t1 >>> 240xFF(tt >>> 24));
        result[5(byte) (Si[(t0 >>> 160xFF(tt >>> 16));
        result[6(byte) (Si[(t3 >>> 80xFF(tt >>> 8));
        result[7(byte) (Si[t2 & 0xFF^ tt);
        tt = Kdr[2];
        result[8(byte) (Si[(t2 >>> 240xFF(tt >>> 24));
        result[9(byte) (Si[(t1 >>> 160xFF(tt >>> 16));
        result[10(byte) (Si[(t0 >>> 80xFF(tt >>> 8));
        result[11(byte) (Si[t3 & 0xFF^ tt);
        tt = Kdr[3];
        result[12(byte) (Si[(t3 >>> 240xFF(tt >>> 24));
        result[13(byte) (Si[(t2 >>> 160xFF(tt >>> 16));
        result[14(byte) (Si[(t1 >>> 80xFF(tt >>> 8));
        result[15(byte) (Si[t0 & 0xFF^ tt);

        if (DEBUG && (debuglevel > 6)) {
            System.out.println("PT=" + toString(result));
            System.out.println();
        }

        if (DEBUG) {
            trace(OUT, "blockDecrypt()");
        }

        return result;
    }

    /**
     * Decrypt exactly one block of ciphertext.
     *
     @param  in         The ciphertext.
     @param  inOffset   Index of in from which to start considering data.
     @param  sessionKey The session key to use for decryption.
     @param  blockSize  The block size in bytes of this Rijndael.
     @return The plaintext generated from a ciphertext using the session key.
     */
    public static byte[] blockDecrypt(byte[] in, int inOffset, Object sessionKey, int blockSize) {
        if (blockSize == BLOCK_SIZE) {
            return blockDecrypt(in, inOffset, sessionKey);
        }

        if (DEBUG) {
            trace(IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
        }

        Object[] sKey = (Object[]) sessionKey; // extract decryption round keys
        int[][] Kd = (int[][]) sKey[1];

        int BC = blockSize / 4;
        int ROUNDS = Kd.length - 1;
        int SC = (BC == 4((BC == 62);
        int s1 = shifts[SC][1][1];
        int s2 = shifts[SC][2][1];
        int s3 = shifts[SC][3][1];
        int[] a = new int[BC];
        int[] t = new int[BC]// temporary work array
        int i;
        byte[] result = new byte[blockSize]// the resulting plaintext
        int j = 0;
        int tt;

        for (i = 0; i < BC; i++// ciphertext to ints + key
         {
            t[i(((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Kd[0][i];
        }

        for (int r = 1; r < ROUNDS; r++) { // apply round transforms

            for (i = 0; i < BC; i++) {
                a[i(T5[(t[i>>> 240xFF^ T6[(t[(i + s1% BC>>> 160xFF^ T7[(t[(i + s2% BC>>> 80xFF^ T8[t[(i + s3% BC0xFF]) ^ Kd[r][i];
            }

            System.arraycopy(a, 0, t, 0, BC);

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("PT" + r + "=" + toString(t));
            }
        }

        for (i = 0; i < BC; i++) { // last round is special
            tt = Kd[ROUNDS][i];
            result[j++(byte) (Si[(t[i>>> 240xFF(tt >>> 24));
            result[j++(byte) (Si[(t[(i + s1% BC>>> 160xFF(tt >>> 16));
            result[j++(byte) (Si[(t[(i + s2% BC>>> 80xFF(tt >>> 8));
            result[j++(byte) (Si[t[(i + s3% BC0xFF^ tt);
        }

        if (DEBUG && (debuglevel > 6)) {
            System.out.println("PT=" + toString(result));
            System.out.println();
        }

        if (DEBUG) {
            trace(OUT, "blockDecrypt()");
        }

        return result;
    }

    /**
     * Convenience method to encrypt exactly one block of plaintext, assuming
     * Rijndael's default block size (128-bit).
     *
     @param  in         The plaintext.
     @param  inOffset   Index of in from which to start considering data.
     @param  sessionKey The session key to use for encryption.
     @return The ciphertext generated from a plaintext using the session key.
     */
    public static byte[] blockEncrypt(byte[] in, int inOffset, Object sessionKey) {
        if (DEBUG) {
            trace(IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
        }

        int[][] Ke = (int[][]) ((Object[]) sessionKey)[0]// extract encryption round keys
        int ROUNDS = Ke.length - 1;
        int[] Ker = Ke[0];

        // plaintext to ints + key
        int t0 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Ker[0];
        int t1 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Ker[1];
        int t2 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Ker[2];
        int t3 = (((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Ker[3];

        int a0;
        int a1;
        int a2;
        int a3;

        for (int r = 1; r < ROUNDS; r++) { // apply round transforms
            Ker = Ke[r];
            a0 = (T1[(t0 >>> 240xFF^ T2[(t1 >>> 160xFF^ T3[(t2 >>> 80xFF^ T4[t3 & 0xFF]) ^ Ker[0];
            a1 = (T1[(t1 >>> 240xFF^ T2[(t2 >>> 160xFF^ T3[(t3 >>> 80xFF^ T4[t0 & 0xFF]) ^ Ker[1];
            a2 = (T1[(t2 >>> 240xFF^ T2[(t3 >>> 160xFF^ T3[(t0 >>> 80xFF^ T4[t1 & 0xFF]) ^ Ker[2];
            a3 = (T1[(t3 >>> 240xFF^ T2[(t0 >>> 160xFF^ T3[(t1 >>> 80xFF^ T4[t2 & 0xFF]) ^ Ker[3];
            t0 = a0;
            t1 = a1;
            t2 = a2;
            t3 = a3;

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("CT" + r + "=" + intToString(t0+ intToString(t1+ intToString(t2+ intToString(t3));
            }
        }

        // last round is special
        byte[] result = new byte[BLOCK_SIZE]// the resulting ciphertext
        Ker = Ke[ROUNDS];

        int tt = Ker[0];
        result[0(byte) (S[(t0 >>> 240xFF(tt >>> 24));
        result[1(byte) (S[(t1 >>> 160xFF(tt >>> 16));
        result[2(byte) (S[(t2 >>> 80xFF(tt >>> 8));
        result[3(byte) (S[t3 & 0xFF^ tt);
        tt = Ker[1];
        result[4(byte) (S[(t1 >>> 240xFF(tt >>> 24));
        result[5(byte) (S[(t2 >>> 160xFF(tt >>> 16));
        result[6(byte) (S[(t3 >>> 80xFF(tt >>> 8));
        result[7(byte) (S[t0 & 0xFF^ tt);
        tt = Ker[2];
        result[8(byte) (S[(t2 >>> 240xFF(tt >>> 24));
        result[9(byte) (S[(t3 >>> 160xFF(tt >>> 16));
        result[10(byte) (S[(t0 >>> 80xFF(tt >>> 8));
        result[11(byte) (S[t1 & 0xFF^ tt);
        tt = Ker[3];
        result[12(byte) (S[(t3 >>> 240xFF(tt >>> 24));
        result[13(byte) (S[(t0 >>> 160xFF(tt >>> 16));
        result[14(byte) (S[(t1 >>> 80xFF(tt >>> 8));
        result[15(byte) (S[t2 & 0xFF^ tt);

        if (DEBUG && (debuglevel > 6)) {
            System.out.println("CT=" + toString(result));
            System.out.println();
        }

        if (DEBUG) {
            trace(OUT, "blockEncrypt()");
        }

        return result;
    }

    /**
     * Encrypt exactly one block of plaintext.
     *
     @param  in         The plaintext.
     @param  inOffset   Index of in from which to start considering data.
     @param  sessionKey The session key to use for encryption.
     @param  blockSize  The block size in bytes of this Rijndael.
     @return The ciphertext generated from a plaintext using the session key.
     */
    public static byte[] blockEncrypt(byte[] in, int inOffset, Object sessionKey, int blockSize) {
        if (blockSize == BLOCK_SIZE) {
            return blockEncrypt(in, inOffset, sessionKey);
        }

        if (DEBUG) {
            trace(IN, "blockEncrypt(" + in + ", " + inOffset + ", " + sessionKey + ", " + blockSize + ")");
        }

        Object[] sKey = (Object[]) sessionKey; // extract encryption round keys
        int[][] Ke = (int[][]) sKey[0];

        int BC = blockSize / 4;
        int ROUNDS = Ke.length - 1;
        int SC = (BC == 4((BC == 62);
        int s1 = shifts[SC][1][0];
        int s2 = shifts[SC][2][0];
        int s3 = shifts[SC][3][0];
        int[] a = new int[BC];
        int[] t = new int[BC]// temporary work array
        int i;
        byte[] result = new byte[blockSize]// the resulting ciphertext
        int j = 0;
        int tt;

        for (i = 0; i < BC; i++// plaintext to ints + key
         {
            t[i(((in[inOffset++0xFF<< 24((in[inOffset++0xFF<< 16((in[inOffset++0xFF<< 8(in[inOffset++0xFF)) ^ Ke[0][i];
        }

        for (int r = 1; r < ROUNDS; r++) { // apply round transforms

            for (i = 0; i < BC; i++) {
                a[i(T1[(t[i>>> 240xFF^ T2[(t[(i + s1% BC>>> 160xFF^ T3[(t[(i + s2% BC>>> 80xFF^ T4[t[(i + s3% BC0xFF]) ^ Ke[r][i];
            }

            System.arraycopy(a, 0, t, 0, BC);

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("CT" + r + "=" + toString(t));
            }
        }

        for (i = 0; i < BC; i++) { // last round is special
            tt = Ke[ROUNDS][i];
            result[j++(byte) (S[(t[i>>> 240xFF(tt >>> 24));
            result[j++(byte) (S[(t[(i + s1% BC>>> 160xFF(tt >>> 16));
            result[j++(byte) (S[(t[(i + s2% BC>>> 80xFF(tt >>> 8));
            result[j++(byte) (S[t[(i + s3% BC0xFF^ tt);
        }

        if (DEBUG && (debuglevel > 6)) {
            System.out.println("CT=" + toString(result));
            System.out.println();
        }

        if (DEBUG) {
            trace(OUT, "blockEncrypt()");
        }

        return result;
    }

    // Rijndael own methods
    //...........................................................................

    /** @return The default length in bytes of the Algorithm input block. */
    public static int blockSize() {
        return BLOCK_SIZE;
    }

    // main(): use to generate the Intermediate Values KAT
    //...........................................................................
    public static void main(String[] args) {
        self_test(16);
        self_test(24);
        self_test(32);
    }

    // Basic API methods
    //...........................................................................

    /**
     * Convenience method to expand a user-supplied key material into a
     * session key, assuming Rijndael's default block size (128-bit).
     *
     @param k The 128/192/256-bit user-key to use.
     @exception  InvalidKeyException  If the key is invalid.
     */
    public static Object makeKey(byte[] kthrows InvalidKeyException {
        return makeKey(k, BLOCK_SIZE);
    }

    /**
     * Expand a user-supplied key material into a session key.
     *
     @param k        The 128/192/256-bit user-key to use.
     @param blockSize  The block size in bytes of this Rijndael.
     @exception  InvalidKeyException  If the key is invalid.
     */
    public static synchronized Object makeKey(byte[] k, int blockSizethrows InvalidKeyException {
        if (DEBUG) {
            trace(IN, "makeKey(" + k + ", " + blockSize + ")");
        }

        if (k == null) {
            throw new InvalidKeyException("Empty key");
        }

        if (!((k.length == 16|| (k.length == 24|| (k.length == 32))) {
            throw new InvalidKeyException("Incorrect key length");
        }

        int ROUNDS = getRounds(k.length, blockSize);
        int BC = blockSize / 4;
        int[][] Ke = new int[ROUNDS + 1][BC]// encryption round keys
        int[][] Kd = new int[ROUNDS + 1][BC]// decryption round keys
        int ROUND_KEY_COUNT = (ROUNDS + 1* BC;
        int KC = k.length / 4;
        int[] tk = new int[KC];
        int i;
        int j;

        // copy user material bytes into temporary ints
        for (i = 0, j = 0; i < KC;) {
            tk[i++((k[j++0xFF<< 24((k[j++0xFF<< 16((k[j++0xFF<< 8(k[j++0xFF);
        }

        // copy values into round key arrays
        int t = 0;

        for (j = 0(j < KC&& (t < ROUND_KEY_COUNT); j++, t++) {
            Ke[t / BC][t % BC= tk[j];
            Kd[ROUNDS - (t / BC)][t % BC= tk[j];
        }

        int tt;
        int rconpointer = 0;

        while (t < ROUND_KEY_COUNT) {
            // extrapolate using phi (the round key evolution function)
            tt = tk[KC - 1];
            tk[0^= (((S[(tt >>> 160xFF0xFF<< 24((S[(tt >>> 80xFF0xFF<< 16((S[tt & 0xFF0xFF<< 8(S[(tt >>> 240xFF0xFF((rcon[rconpointer++0xFF<< 24));

            if (KC != 8) {
                for (i = 1, j = 0; i < KC;) {
                    tk[i++^= tk[j++];
                }
            else {
                for (i = 1, j = 0; i < (KC / 2);) {
                    tk[i++^= tk[j++];
                }

                tt = tk[(KC / 21];
                tk[KC / 2^= ((S[tt & 0xFF0xFF((S[(tt >>> 80xFF0xFF<< 8((S[(tt >>> 160xFF0xFF<< 16((S[(tt >>> 240xFF0xFF<< 24));

                for (j = KC / 2, i = j + 1; i < KC;) {
                    tk[i++^= tk[j++];
                }
            }

            // copy values into round key arrays
            for (j = 0(j < KC&& (t < ROUND_KEY_COUNT); j++, t++) {
                Ke[t / BC][t % BC= tk[j];
                Kd[ROUNDS - (t / BC)][t % BC= tk[j];
            }
        }

        for (int r = 1; r < ROUNDS; r++// inverse MixColumn where needed
         {
            for (j = 0; j < BC; j++) {
                tt = Kd[r][j];
                Kd[r][j= U1[(tt >>> 240xFF^ U2[(tt >>> 160xFF^ U3[(tt >>> 80xFF^ U4[tt & 0xFF];
            }
        }

        // assemble the encryption (Ke) and decryption (Kd) round keys into
        // one sessionKey object
        Object[] sessionKey = new Object[] {Ke, Kd};

        if (DEBUG) {
            trace(OUT, "makeKey()");
        }

        return sessionKey;
    }

    /** A basic symmetric encryption/decryption test. */
    public static boolean self_test() {
        return self_test(BLOCK_SIZE);
    }

    static void debug(String s) {
        err.println(">>> " + NAME + ": " + s);
    }

    // multiply two elements of GF(2^m)
    static final int mul(int a, int b) {
        return ((a != 0&& (b != 0)) ? alog[(log[a & 0xFF+ log[b & 0xFF]) 2550;
    }

    // convenience method used in generating Transposition boxes
    static final int mul4(int a, byte[] b) {
        if (a == 0) {
            return 0;
        }

        a = log[a & 0xFF];

        int a0 = (b[0!= 0(alog[(a + log[b[00xFF]) 2550xFF0;
        int a1 = (b[1!= 0(alog[(a + log[b[10xFF]) 2550xFF0;
        int a2 = (b[2!= 0(alog[(a + log[b[20xFF]) 2550xFF0;
        int a3 = (b[3!= 0(alog[(a + log[b[30xFF]) 2550xFF0;

        return (a0 << 24(a1 << 16(a2 << 8| a3;
    }

    static void trace(boolean in, String s) {
        if (TRACE) {
            err.println((in ? "==> " "<== "+ NAME + "." + s);
        }
    }

    static void trace(String s) {
        if (TRACE) {
            err.println("<=> " + NAME + "." + s);
        }
    }

    // utility static methods (from cryptix.util.core ArrayUtil and Hex classes)
    //...........................................................................

    /**
     * Compares two byte arrays for equality.
     *
     @return true if the arrays have identical contents
     */
    private static boolean areEqual(byte[] a, byte[] b) {
        int aLength = a.length;

        if (aLength != b.length) {
            return false;
        }

        for (int i = 0; i < aLength; i++) {
            if (a[i!= b[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns a string of 2 hexadecimal digits (most significant
     * digit first) corresponding to the lowest 8 bits of <i>n</i>.
     */
    private static String byteToString(int n) {
        char[] buf = {HEX_DIGITS[(n >>> 40x0F], HEX_DIGITS[n & 0x0F]};

        return new String(buf);
    }

    /**
     * Returns a string of 8 hexadecimal digits (most significant
     * digit first) corresponding to the integer <i>n</i>, which is
     * treated as unsigned.
     */
    private static String intToString(int n) {
        char[] buf = new char[8];

        for (int i = 7; i >= 0; i--) {
            buf[i= HEX_DIGITS[n & 0x0F];
            n >>>= 4;
        }

        return new String(buf);
    }

    /** A basic symmetric encryption/decryption test for a given key size. */
    private static boolean self_test(int keysize) {
        if (DEBUG) {
            trace(IN, "self_test(" + keysize + ")");
        }

        boolean ok = false;

        try {
            byte[] kb = new byte[keysize];
            byte[] pt = new byte[BLOCK_SIZE];
            int i;

            for (i = 0; i < keysize; i++) {
                kb[i(bytei;
            }

            for (i = 0; i < BLOCK_SIZE; i++) {
                pt[i(bytei;
            }

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("==========");
                System.out.println();
                System.out.println("KEYSIZE=" (* keysize));
                System.out.println("KEY=" + toString(kb));
                System.out.println();
            }

            Object key = makeKey(kb, BLOCK_SIZE);

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("Intermediate Ciphertext Values (Encryption)");
                System.out.println();
                System.out.println("PT=" + toString(pt));
            }

            byte[] ct = blockEncrypt(pt, 0, key, BLOCK_SIZE);

            if (DEBUG && (debuglevel > 6)) {
                System.out.println("Intermediate Plaintext Values (Decryption)");
                System.out.println();
                System.out.println("CT=" + toString(ct));
            }

            byte[] cpt = blockDecrypt(ct, 0, key, BLOCK_SIZE);

            ok = areEqual(pt, cpt);

            if (!ok) {
                throw new RuntimeException("Symmetric operation failed");
            }
        catch (Exception x) {
            if (DEBUG && (debuglevel > 0)) {
                debug("Exception encountered during self-test: " + x.getMessage());
                x.printStackTrace();
            }
        }

        if (DEBUG && (debuglevel > 0)) {
            debug("Self-test OK? " + ok);
        }

        if (DEBUG) {
            trace(OUT, "self_test()");
        }

        return ok;
    }

    /**
     * Returns a string of hexadecimal digits from a byte array. Each
     * byte is converted to 2 hex symbols.
     */
    private static String toString(byte[] ba) {
        int length = ba.length;
        char[] buf = new char[length * 2];

        for (int i = 0, j = 0, k; i < length;) {
            k = ba[i++];
            buf[j++= HEX_DIGITS[(k >>> 40x0F];
            buf[j++= HEX_DIGITS[k & 0x0F];
        }

        return new String(buf);
    }

    /**
     * Returns a string of hexadecimal digits from an integer array. Each
     * int is converted to 4 hex symbols.
     */
    private static String toString(int[] ia) {
        int length = ia.length;
        char[] buf = new char[length * 8];

        for (int i = 0, j = 0, k; i < length; i++) {
            k = ia[i];
            buf[j++= HEX_DIGITS[(k >>> 280x0F];
            buf[j++= HEX_DIGITS[(k >>> 240x0F];
            buf[j++= HEX_DIGITS[(k >>> 200x0F];
            buf[j++= HEX_DIGITS[(k >>> 160x0F];
            buf[j++= HEX_DIGITS[(k >>> 120x0F];
            buf[j++= HEX_DIGITS[(k >>> 80x0F];
            buf[j++= HEX_DIGITS[(k >>> 40x0F];
            buf[j++= HEX_DIGITS[k & 0x0F];
        }

        return new String(buf);
    }
}