All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jinahya.kisa.aria.ARIAEngineProxy Maven / Gradle / Ivy

The newest version!
package com.github.jinahya.kisa.aria;

import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;

public class ARIAEngineProxy {

    private static final String NAME = "kr.re.nsri.aria.ARIAEngine";

    private static final Class CLASS;

    private static final Constructor CONSTRUCTOR;

    private static final Method SET_KEY;

    private static final Method SETUP_ROUND_KEYS;

    private static final Method ENCRYPT;

    private static final Method DECRYPT;

    static {
        try {
            CLASS = Class.forName(NAME);
            try {
                CONSTRUCTOR = CLASS.getConstructor(int.class);
                if (!CONSTRUCTOR.isAccessible()) {
                    CONSTRUCTOR.setAccessible(true);
                }
            } catch (final NoSuchMethodException nsme) {
                throw new ExceptionInInitializerError(nsme);
            }
            try {
                SET_KEY = CLASS.getDeclaredMethod("setKey", byte[].class);
                if (!SET_KEY.isAccessible()) {
                    SET_KEY.setAccessible(true);
                }
            } catch (final NoSuchMethodException nsme) {
                throw new ExceptionInInitializerError("unable to find setKey([B) from " + CLASS);
            }
            try {
                SETUP_ROUND_KEYS = CLASS.getDeclaredMethod("setupRoundKeys");
                if (!SETUP_ROUND_KEYS.isAccessible()) {
                    SETUP_ROUND_KEYS.setAccessible(true);
                }
            } catch (final NoSuchMethodException nsme) {
                throw new ExceptionInInitializerError("unable to find setupRoundKeys() from " + CLASS);
            }
            try {
                ENCRYPT = CLASS.getDeclaredMethod("encrypt", byte[].class, int.class, byte[].class, int.class);
                if (!ENCRYPT.isAccessible()) {
                    ENCRYPT.setAccessible(true);
                }
            } catch (final NoSuchMethodException nsme) {
                throw new ExceptionInInitializerError("unable to find encrypt([B, I, [B, I) from " + CLASS);
            }
            try {
                DECRYPT = CLASS.getDeclaredMethod("decrypt", byte[].class, int.class, byte[].class, int.class);
                if (!DECRYPT.isAccessible()) {
                    DECRYPT.setAccessible(true);
                }
            } catch (final NoSuchMethodException nsme) {
                throw new ExceptionInInitializerError("unable to find decrypt([B, I, [B, I) from " + CLASS);
            }
        } catch (final ClassNotFoundException cnfe) {
            throw new ExceptionInInitializerError(cnfe);
        }
    }

    // -----------------------------------------------------------------------------------------------------------------
    private static final Method BUFFER_POSITION;

    static {
        Method bufferPosition;
        try {
            bufferPosition = ByteBuffer.class.getMethod("position", int.class);
        } catch (final NoSuchMethodException nsme) {
            bufferPosition = null;
        }
        BUFFER_POSITION = bufferPosition;
    }

    // -----------------------------------------------------------------------------------------------------------------
    static final int BLOCK_SIZE = 128;

    static final int BLOCK_BYTES = BLOCK_SIZE / Byte.SIZE;

    // -----------------------------------------------------------------------------------------------------------------
    private static ARIAEngineProxy newInstance(final int mode, final byte[] key) throws InvalidKeyException {
        if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE) {
            throw new IllegalArgumentException(
                    "invalid mode: " + mode + ";" +
                    " not Cipher.ENCRYPT_MODE(" + Cipher.ENCRYPT_MODE + ")" +
                    " nor Cipher.DECRYPT_MODE(" + Cipher.DECRYPT_MODE + ")"
            );
        }
        if (key == null) {
            throw new NullPointerException("key is null");
        }
        final Object engine;
        try {
            engine = CONSTRUCTOR.newInstance(key.length * Byte.SIZE);
            SET_KEY.invoke(engine, key);
            SETUP_ROUND_KEYS.invoke(engine);
        } catch (final InstantiationException ie) {
            throw new RuntimeException(ie);
        } catch (final IllegalAccessException iae) {
            throw new RuntimeException(iae);
        } catch (final InvocationTargetException ite) {
            final Throwable cause = ite.getCause();
            if (cause instanceof InvalidKeyException) {
                throw (InvalidKeyException) cause;
            }
            throw new RuntimeException(ite);
        }
        if (mode == Cipher.ENCRYPT_MODE) {
            return new ARIAEngineProxy(engine) {
                @Override
                public int decrypt(final byte[] input, final int inputOffset, final byte[] output,
                                   final int outputOffset)
                        throws ShortBufferException {
                    throw new IllegalStateException("not initialized for decryption");
                }
            };
        }
        assert mode == Cipher.DECRYPT_MODE;
        return new ARIAEngineProxy(engine) {
            @Override
            public int encrypt(final byte[] input, final int inputOffset, final byte[] output, final int outputOffset)
                    throws ShortBufferException {
                throw new IllegalStateException("not initialized for encryption");
            }
        };
    }

    /**
     * Creates a new instance, for encryption, with specified key.
     *
     * @param key the key.
     * @return a new instance for encryption.
     * @throws InvalidKeyException if {@code key}'s length is not {@code 128}, {@code 192}, nor {@code 256};
     */
    public static ARIAEngineProxy newInstanceForEncryption(final byte[] key) throws InvalidKeyException {
        return newInstance(Cipher.ENCRYPT_MODE, key);
    }

    /**
     * Creates a new instance, for decryption, with specified key.
     *
     * @param key the key.
     * @return a new instance for decryption.
     * @throws InvalidKeyException if {@code key}'s length is not {@code 128}, {@code 192}, nor {@code 256};
     */
    public static ARIAEngineProxy newInstanceForDecryption(final byte[] key) throws InvalidKeyException {
        return newInstance(Cipher.DECRYPT_MODE, key);
    }

    // -----------------------------------------------------------------------------------------------------------------
    private ARIAEngineProxy(final Object engine) {
        super();
        if (engine == null) {
            throw new NullPointerException("engine is null");
        }
        this.engine = engine;
    }

    // -----------------------------------------------------------------------------------------------------------------
    private void check(final byte[] input, final int inputOffset, final byte[] output, final int outputOffset)
            throws ShortBufferException {
        if (input == null) {
            throw new NullPointerException("input is null");
        }
        if (inputOffset < 0) {
            throw new IllegalArgumentException("inputOffset(" + inputOffset + ") is negative");
        }
        if (inputOffset > input.length) {
            throw new IllegalArgumentException(
                    "inputOffset(" + inputOffset + ") > input.length(" + input.length + ")"
            );
        }
        final int inputLength = input.length - inputOffset;
        final int requiredBytes = inputLength / BLOCK_BYTES * BLOCK_BYTES;
        if (output == null) {
            throw new NullPointerException("output is null");
        }
        if (outputOffset < 0) {
            throw new IllegalArgumentException("outputOffset(" + outputOffset + ") is negative");
        }
        if (outputOffset > output.length) {
            throw new IllegalArgumentException(
                    "outputOffset(" + outputOffset + ") > output.length(" + output.length + ")"
            );
        }
        final int outputLength = output.length - outputOffset;
        if (outputLength < requiredBytes) {
            throw new ShortBufferException("outputLength(" + outputLength + " < " + requiredBytes);
        }
    }

    /**
     * Encrypts bytes of specified input, starting at specified index, sets result on specified output, starting at
     * specified index, and returns the number of bytes set.
     *
     * @param input        the input to encrypt.
     * @param inputOffset  the starting index of the {@code input}.
     * @param output       the output on which results are set.
     * @param outputOffset the starting index of the {@code output}.
     * @return the number of bytes set on the {@code output} which is same as the number of bytes processed in
     * {@code input}.
     * @throws ShortBufferException when there is not enough space on the {@code output}.
     */
    public int encrypt(final byte[] input, int inputOffset, final byte[] output, int outputOffset)
            throws ShortBufferException {
        check(input, inputOffset, output, outputOffset);
        final int blocks = (input.length - inputOffset) / BLOCK_BYTES;
        for (int b = 0; b < blocks; b++) {
            try {
                ENCRYPT.invoke(engine, input, inputOffset, output, outputOffset);
                inputOffset += BLOCK_BYTES;
                outputOffset += BLOCK_BYTES;
            } catch (final IllegalAccessException iae) {
                throw new RuntimeException("unable to encrypt", iae);
            } catch (final InvocationTargetException ite) {
                throw new RuntimeException("unable to encrypt", ite.getCause());
            }
        }
        return blocks * BLOCK_BYTES;
    }

    /**
     * Encrypts remaining bytes on specified input buffer, and puts result to specified output buffer.
     *
     * @param input  the input buffer whose remaining bytes are encrypted.
     * @param output the output buffer on which encrypted bytes are put.
     * @throws ShortBufferException if there is no enough remaining on the {@code output}.
     */
    public void encrypt(final ByteBuffer input, ByteBuffer output)
            throws ShortBufferException {
        if (input == null) {
            throw new NullPointerException("input is null");
        }
        if (output == null) {
            throw new NullPointerException("output is null");
        }
        if (false && BUFFER_POSITION != null && input.hasArray() && output.hasArray()) { // animal-sniffer
            final int bytes = encrypt(input.array(), input.arrayOffset() + input.position(),
                                      output.array(), output.arrayOffset() + output.position());
            input.position(input.position() + bytes);
            output.position(output.position() + bytes);
            return;
        }
        final int blocks = input.remaining() / BLOCK_BYTES;
        if (output.remaining() < blocks * BLOCK_BYTES) {
            throw new ShortBufferException("not enough remaining on the output");
        }
        final byte[] i = new byte[BLOCK_BYTES];
        final byte[] o = new byte[BLOCK_BYTES];
        for (int b = 0; b < blocks; b++) {
            input.get(i);
            encrypt(i, 0, o, 0);
            output.put(o);
        }
    }

    /**
     * Decrypts bytes of specified input, starting at specified index, sets result on specified output, starting at
     * specified index, and returns the number of bytes set.
     *
     * @param input        the input to encrypt.
     * @param inputOffset  the starting index of the {@code input}.
     * @param output       the output on which results are set.
     * @param outputOffset the starting index of the {@code output}.
     * @return the number of bytes set on the {@code output} which is same as the number of bytes processed in
     * {@code input}.
     * @throws ShortBufferException when there is not enough space on the {@code output}.
     */
    public int decrypt(final byte[] input, int inputOffset, final byte[] output, int outputOffset)
            throws ShortBufferException {
        check(input, inputOffset, output, outputOffset);
        final int blocks = (input.length - inputOffset) / BLOCK_BYTES;
        for (int b = 0; b < blocks; b++) {
            try {
                DECRYPT.invoke(engine, input, inputOffset, output, outputOffset);
                inputOffset += BLOCK_BYTES;
                outputOffset += BLOCK_BYTES;
            } catch (final IllegalAccessException iae) {
                throw new RuntimeException("unable to decrypt", iae);
            } catch (final InvocationTargetException ite) {
                throw new RuntimeException("unable to decrypt", ite.getCause());
            }
        }
        return blocks * BLOCK_BYTES;
    }

    /**
     * Decrypts remaining bytes on specified input buffer, and puts result to specified output buffer.
     *
     * @param input  the input buffer whose remaining bytes are decrypted.
     * @param output the output buffer on which decrypted bytes are put.
     * @throws ShortBufferException if there is no enough remaining on the {@code output}.
     */
    public void decrypt(final ByteBuffer input, ByteBuffer output)
            throws ShortBufferException {
        if (input == null) {
            throw new NullPointerException("input is null");
        }
        if (output == null) {
            throw new NullPointerException("output is null");
        }
        if (false && BUFFER_POSITION != null && input.hasArray() && output.hasArray()) { // animal-sniffer
            final int bytes = decrypt(input.array(), input.arrayOffset() + input.position(),
                                      output.array(), output.arrayOffset() + output.position());
            input.position(input.position() + bytes);
            output.position(output.position() + bytes);
            return;
        }
        final int blocks = input.remaining() / BLOCK_BYTES;
        if (output.remaining() < blocks * BLOCK_BYTES) {
            throw new ShortBufferException("not enough remaining on the output");
        }
        final byte[] i = new byte[BLOCK_BYTES];
        final byte[] o = new byte[BLOCK_BYTES];
        for (int b = 0; b < blocks; b++) {
            input.get(i);
            decrypt(i, 0, o, 0);
            output.put(o);
        }
    }

    // -----------------------------------------------------------------------------------------------------------------
    private final Object engine;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy