/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.crypto;

/**
 * DES cipher implementation.
 */
class DES_ECB extends BlockCipherBase {
    /** True, if this class is being used for triple DES (EDE). */
    private boolean tripleDes;

    /** Algorithm of the key. */
    protected String keyAlgorithm;

    /** DES ciphers encrypt/decrypt in block size of 8 bytes. */
    protected static final int BLOCK_SIZE = 8;

    /** s0p. */
    private static final int s0p[] = initTable(0x40410100,
                                0x72cf4bacb769d40aL, 0x2853f695813e1de0L,
                                0xf5a7295e13cb8c6L, 0xf36d49a024d78e1bL);

    /** s1p. */
    private static final int s1p[] = initTable(0x08021002,
                                0xf06c93a62d1a5ec1L, 0x4bd27805b7e9843fL,
                                0x5ea7f590834d287bL, 0xe2091f6cd43ab1c6L);

    /** s2p. */
    private static final int s2p[] = initTable(0x20808020,
                                0xc71da4d35268f98eL, 0xa719f2ce5b6304bL,
                                0x71829a6d073ea4f8L, 0xbc4f25d350e9cb16L);

    /** s3p. */
    private static final int s3p[] = initTable(0x02080201,
                                0x7a1f0cb5e9839748L, 0xd6216bc2305eadf4L,
                                0xd3496a1cb0250de2L, 0x8f74f1a756cb389eL);

    /** s4p. */
    private static final int s4p[] = initTable(0x01002084,
                                0x842fda7c4196bde0L, 0x6853a7091bf5c23eL,
                                0xeb5c419a86f07825L, 0xb20fde346da317c9L);

    /** s5p. */
    private static final int s5p[] = initTable(0x10040408,
                                0x950e52b43f68a9c7L, 0x4bd021edfc83167aL,
                                0x38a7e50a82d45f61L, 0xf64b9c7029bec31dL);

    /** s6p. */
    private static final int s6p[] = initTable(0x80200840,
                                0x215cfa304d968769L, 0xd2af05c3eb78be14L,
                                0xb6e9214edb301c85L, 0xd5392f478afc76aL);

    /** s7p. */
    private static final int s7p[] = initTable(0x04104010,
                                0xde30a5cf18637b9cL, 0x275af90684bd42e1L,
                                0x429f3806dba5e15aL, 0xf4c963bc1e708d27L);

    /** initPermRight. */
    private static int[] initPermRight = initPerm(128, 32, 2, -3,
                              0xc3b3432020332020L, 0x83b3432020332020L);
    /** initPermLeft. */
    private static int[] initPermLeft = initPerm(128, 32, 2, -3,
                              0x8742032067420320L, 0x4742032067420320L);
     /** perm. */
    private static int[] perm = initPerm(64, 64, 4, -1,
                              0x4420411201004021L, 0x1001200101000000L);

    /**
     * Data used for key expansion.
     */
    private static byte[] expandData = ("\020\0313KM1L3}\014I1JT*}\025" +
            "\017\0128R=M;KM,Zx<I1X\017\020}\013}\000(R=}\021-H<Zk\020" +
            "%}\001Z5P8I(R3}\014:\134;\017*R5P8I1{}\014:\134-U1\017y1}" +
            "\023<M0T-U:I4KH\015tM0KqZ94KxR3[\017\013=H<Zx<S}\000(R=MI" +
            "\020\000\134*}\031-H<IL3}\033}\0002\017LI1JT*}\025;KM1L3P" +
            "\016u:I4KxI}\023<M0K5\016:94KxR3[<M0KqJ\0169(R3}\014:\134" +
            ";qZ5P8\0174<:\134-U:I=P8I1}\023\020\005SM1L3}\03391JT*}" +
            "\0313\017\034:=M;KM1Rx<I1J8\016\014S}\000(R=MI-H<Zx\01751Z" +
            "5P8I1J3}\014:\134-\0171=P8I1}\023t:\134-U:R\020\040I}\023<" +
            "M0K5U:I4K}\021\016,U0KqZ5<KxR3}\024\017\0238<Zx<I}\010(R=M" +
            ";=\020\0222}\031-H<Z<3}\033}\000(\134\020\03391JT*}\0313KM" +
            "1L3}\014\017!JI4KxR}\013<M0K}\005\016-<KxR3}\024M0KqZ9\020" +
            ")J3}\014:\134-}\001Z5P8I(\020=*\134-U:I4X8I1}\023<<\017" +
            "\010U1L3}\033iJT*}\031-S\020\002MM;KM1L}\000<I1JT:\017\021" +
            "}\010(R=M;=H<Zx<S\020\032<3}\033}\000(\134*}\031-H<I\020$X" +
            "8I1}\023<<:\134-U:I=\0172}\013<M0K}\005:I4KxI\016\042HKqZ5" +
            "P3xR3}\014U\017\035,Zx<I1X(R=M;K8\017\015}\021-H<Zk}\033}" +
            "\000(R2\017AJT*}\031-SM1L3}\0339\020\024}\000<I1JT:=M;KM1" +
            "R\01683xR3}\014U0KqZ5<\017c}\014:\134-U1Z5P8I1J\020(T-U:I" +
            "4KHI1}\023<M*\020\004IL3}\033}\0002T*}\031-HU\017U;KM1L3P" +
            "<I1JT*M\016\001X(R=M;K8<Zx<I\017C}\033}\000(R2}\031-H<Z<" +
            "\020+HI1}\023<M*\134-U:I4X\015#[<M0KqJI4KxR\016<;qZ5P8pR3" +
            "}\014:H\017\011Rx<I1J8R=M;KM,\016\025I-H<Zx}\013}\000(R=" +
            "\017\0302T*}\031-HU1L3}\033i\017\003P<I1JT*MM;KM1L\0160pR" +
            "3}\014:HKqZ5P3\016;t:\134-U:R5P8I1{\02035U:I4K}\0211}\023" +
            "<M0T").getBytes();

    /** DES key data. */
    private byte[][] dkey;
    
    /**
     * Constructor.
     *
     * @param useTripleDes true if the class is being used for triple DES
     *
     */
    public DES_ECB(boolean useTripleDes) {
        super(BLOCK_SIZE);
        tripleDes = useTripleDes;

        if (useTripleDes) {
            keyAlgorithm = "DESEDE";
        } else {
            keyAlgorithm = "DES";
        }
    }

    /**
     * Called by the factory method to set the mode and padding parameters.
     * Need because Class.newInstance does not take args.
     *
     * @param mode the mode parsed from the transformation parameter of
     *             getInstance and upper cased
     * @param padding the paddinge parsed from the transformation parameter of
     *                getInstance and upper cased
     */
    protected void setChainingModeAndPadding(String mode, String padding)
            throws NoSuchPaddingException {
        // Note: The chaining mode is implicitly set by using this class.

        setPadding(padding);
    }

    /**
     * Initializes this cipher with a key and a set of algorithm
     * parameters.
     *
     * @param mode the operation mode of this cipher
     * @param key the encryption key
     * @param params the algorithm parameters
     *
     * @exception java.security.InvalidKeyException if the given key
     * is inappropriate for initializing this cipher
     * @exception java.security.InvalidAlgorithmParameterException
     * if the given algorithm parameters are inappropriate for this cipher
     */
    public void init(int mode, Key key, CryptoParameter params)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        doInit(mode, keyAlgorithm, key, false, null);
    }

    /**
     * Initializes key.
     * @param data key data
     * @param mode cipher mode
     * @exception InvalidKeyException if the given key is inappropriate
     * for this cipher
     */
    protected void initKey(byte[] data, int mode)
            throws InvalidKeyException {

        if (data.length != (tripleDes ? 24 : 8)) {
            throw new InvalidKeyException();
        }

        int keyCount = data.length >> 3;
        dkey = new byte[keyCount][];
        for (int i = 0; i < keyCount; i++) {
            dkey[i] = expandKey(data, i << 3);
        }
    }

    /**
     * Depending on the mode, either encrypts
     * or decrypts the data in the queue.
     * @param out will contain the result of encryption
     * or decryption operation
     * @param offset is the offset in out
     */
    protected void processBlock(byte[] out, int offset) {

        if (dkey.length == 1) {
            cipherBlock(0, mode == Cipher.ENCRYPT_MODE);
        } else {
            if (mode == Cipher.ENCRYPT_MODE) {
                cipherBlock(0, true);
                cipherBlock(1, false);
                cipherBlock(2, true);
            } else {
                cipherBlock(2, false);
                cipherBlock(1, true);
                cipherBlock(0, false);
            }
        }
        System.arraycopy(holdData, 0, out, offset, BLOCK_SIZE);
        holdCount = 0;
    }

    /**
     * Initializes data for permutation.
     * @param value seed value
     * @param period perion for value modification
     * @param divisor divisor of value
     * @param offset initial offset in the table
     * @param deltas1 packed offsets for elements 0 - 15
     * @param deltas2 packed offsets for elements 16 - 31
     * @return initialized data
     */
    private static int[] initPerm(int value, int period,
                  int divisor, int offset, long deltas1, long deltas2) {

        int[] result = new int[256];
        int count = 0;

        while (true) {

            offset += ((((count & 0x1f) < 16 ? deltas1 : deltas2) >>
                      ((15 - count & 0xf) << 2)) & 0xf) + 1;

            if (offset > 1023) {
                return result;
            }

            count++;

            if (count > 1 && count % period == 1) {
                value = value == 1 ? 128 : (value / divisor);
            }

            result[offset >> 2] |= (value << ((3 - offset & 3) << 3));
        }
    }

    /**
     * Initializes static data used by DES.
     * @param bitmask mask of bits used in this table
     * @param l1 order of words 0 - 15
     * @param l2 order of words 16 - 31
     * @param l3 order of words 32 - 47
     * @param l4 order of words 48 - 63
     * @return the table
     */
    private static final int[] initTable(int bitmask, long l1, long l2,
                                     long l3, long l4) {

        int[] words = new int[16];
        int count = 1;

        for (int i = 0; i < 8; i++) {

            int mask;
            if ((mask = bitmask & (0xf << (i << 2))) == 0) {
                continue;
            }

            for (int j = 0; j < count; j++) {
                words[count + j] = words[j] | mask;
            }
            count += count;
        }

        int[] data = new int[64];
        for (int i = 0; i < 64; i++) {
            data[i] = words[((int) ((i < 32 ? (i < 16 ? l1 : l2) :
                                    (i < 48 ? l3 : l4)) >>
                                     ((15 - (i & 0xf)) << 2))) & 0xf];
        }
        return data;
    }

    /**
     * Performs the encryption/decryption of data.
     * @param keyIndex index of the the key
     * @param encryptMode indicates if its encryption or decryption
     */
    private void cipherBlock(int keyIndex, boolean encryptMode) {

        byte[] key = dkey[keyIndex];
        byte[] data = holdData;

        int j = encryptMode ? 0 : 128 - BLOCK_SIZE;
        int offset = (encryptMode ? 0 : 16) - BLOCK_SIZE;

        // initial permutations

        int t, v;
        int left = 0;
        for (int i = 0; i < 8; i++) {
            left |= initPermLeft[(v = i << 5) + 16 + ((t = data[i]) & 0xf)] |
                    initPermLeft[v + ((t >> 4) & 0xf)];
        }

        int right = 0;
        for (int i = 0; i < 8; i++) {
            right |= initPermRight[(v = i << 5) + 16 + ((t = data[i]) & 0xf)] |
                     initPermRight[v + ((t >> 4) & 0xf)];
        }

        int i = 0;
        while (true) {
            // making the first bit and last bit adjacent
            // move the first bit to the last
            int temp = (right << 1) | ((right >> 31) & 1);

            // Mangler Function
            // every 6 bit is fed into the sbox, which
            // produces 4 bit output
            left ^= s0p[(temp & 0x3f) ^ key[j]]
                    ^ s1p[((temp >>  4) & 0x3f) ^ key[j + 1]]
                    ^ s2p[((temp >>  8) & 0x3f) ^ key[j + 2]]
                    ^ s3p[((temp >> 12) & 0x3f) ^ key[j + 3]]
                    ^ s4p[((temp >> 16) & 0x3f) ^ key[j + 4]]
                    ^ s5p[((temp >> 20) & 0x3f) ^ key[j + 5]]
                    ^ s6p[((temp >> 24) & 0x3f) ^ key[j + 6]];

            // making the last sbox input last bit from right[0]
            temp = ((right & 1) << 5) | ((right >> 27) & 0x1f);
            left ^= s7p[temp ^ key[j + 7]];

            if (i++ == 15) {
                break;
            }

            temp = left;
            left = right;
            right = temp;
            j -= offset;
        }

        // permutations

        int high = perm[left & 0xf] |
                   perm[32 + ((left >> 8) & 0xf)] |
                   perm[64 + ((left >> 16) & 0xf)] |
                   perm[96 + ((left >> 24) & 0xf)] |
                   perm[128 + (right & 0xf)] |
                   perm[160 + ((right >> 8) & 0xf)] |
                   perm[192 + ((right >> 16) & 0xf)] |
                   perm[224 + ((right >> 24) & 0xf)];

        int low  = perm[16 + ((left >> 4) & 0xf)] |
                   perm[48 + ((left >> 12) & 0xf)] |
                   perm[80 + ((left >> 20) & 0xf)] |
                   perm[112 + ((left >> 28) & 0xf)] |
                   perm[144 + ((right >> 4) & 0xf)] |
                   perm[176 + ((right >> 12) & 0xf)] |
                   perm[208 + ((right >> 20) & 0xf)] |
                   perm[240 + ((right >> 28) & 0xf)];

        data[0] = (byte) low;
        data[1] = (byte) (low >> 8);
        data[2] = (byte) (low >> 16);
        data[3] = (byte) (low >> 24);
        data[4] = (byte) high;
        data[5] = (byte) (high >> 8);
        data[6] = (byte) (high >> 16);
        data[7] = (byte) (high >> 24);
    }

    /**
     * Implements part of the DES algorithm.
     * @param key An 8 byte array containing the key data
     * @param keyOffset offset into the key byte array
     * @return the result of operation
     */
    private static byte[] expandKey(byte[] key, int keyOffset) {

        byte ek[] = new byte[128];
        int pos = 0;

        for (int i = 0; i < 8; i++) {

            int octet = key[keyOffset++];
            int len;

            for (int j = 0; j < 7; j++)  {

                len = expandData[pos++];
                int offset = 0;

                if ((octet & (0x80 >> j)) != 0) {

                    while (len-- > 0) {
                        int v;
                        if ((v = expandData[pos++]) == 125) {
                            offset += 16;
                        } else {
                            ek[offset += (v >> 3)] |= (1 << (v & 0x7));
                        }
                    }
                } else {
                    pos += len;
                }
            }
        }
        return ek;
    }
}
