com.intel.chimera.cipher.Openssl.java Source code

Java tutorial

Introduction

Here is the source code for com.intel.chimera.cipher.Openssl.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intel.chimera.cipher;

import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.StringTokenizer;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.intel.chimera.utils.NativeCodeLoader;
import com.intel.chimera.utils.Utils;

/**
 * OpenSSL cryptographic wrapper using JNI.
 * Currently only AES-CTR is supported. It's flexible to add
 * other crypto algorithms/modes.
 */
public final class Openssl {
    private static final Log LOG = LogFactory.getLog(Openssl.class.getName());

    // Mode constant defined by Openssl JNI
    public static final int ENCRYPT_MODE = 1;
    public static final int DECRYPT_MODE = 0;

    /** Currently only support AES/CTR/NoPadding. */
    private static enum AlgorithmMode {
        AES_CTR, AES_CBC;

        static int get(String algorithm, String mode) throws NoSuchAlgorithmException {
            try {
                return AlgorithmMode.valueOf(algorithm + "_" + mode).ordinal();
            } catch (Exception e) {
                throw new NoSuchAlgorithmException(
                        "Doesn't support algorithm: " + algorithm + " and mode: " + mode);
            }
        }
    }

    private static enum Padding {
        NoPadding, PKCS5Padding;

        static int get(String padding) throws NoSuchPaddingException {
            try {
                return Padding.valueOf(padding).ordinal();
            } catch (Exception e) {
                throw new NoSuchPaddingException("Doesn't support padding: " + padding);
            }
        }
    }

    private long context = 0;
    private final int algorithm;
    private final int padding;

    private static final String loadingFailureReason;

    static {
        String loadingFailure = null;
        try {
            if (NativeCodeLoader.isNativeCodeLoaded()) {
                OpensslNative.initIDs();
            }
        } catch (Throwable t) {
            loadingFailure = t.getMessage();
            LOG.debug("Failed to load OpenSSL Cipher.", t);
        } finally {
            loadingFailureReason = loadingFailure;
        }
    }

    /**
     * Gets the failure reason when loading Openssl native.
     * @return the failure reason.
     */
    public static String getLoadingFailureReason() {
        return loadingFailureReason;
    }

    private Openssl(long context, int algorithm, int padding) {
        this.context = context;
        this.algorithm = algorithm;
        this.padding = padding;
    }

    /**
     * Return an <code>OpensslCipher<code> object that implements the specified
     * transformation.
     *
     * @param transformation the name of the transformation, e.g.,
     * AES/CTR/NoPadding.
     * @return OpensslCipher an <code>OpensslCipher<code> object
     * @throws NoSuchAlgorithmException if <code>transformation</code> is null,
     * empty, in an invalid format, or if Openssl doesn't implement the
     * specified algorithm.
     * @throws NoSuchPaddingException if <code>transformation</code> contains
     * a padding scheme that is not available.
     */
    public static final Openssl getInstance(String transformation)
            throws NoSuchAlgorithmException, NoSuchPaddingException {
        Transform transform = tokenizeTransformation(transformation);
        int algorithmMode = AlgorithmMode.get(transform.algorithm, transform.mode);
        int padding = Padding.get(transform.padding);
        long context = OpensslNative.initContext(algorithmMode, padding);
        return new Openssl(context, algorithmMode, padding);
    }

    /** Nested class for algorithm, mode and padding. */
    private static class Transform {
        final String algorithm;
        final String mode;
        final String padding;

        public Transform(String algorithm, String mode, String padding) {
            this.algorithm = algorithm;
            this.mode = mode;
            this.padding = padding;
        }
    }

    private static Transform tokenizeTransformation(String transformation) throws NoSuchAlgorithmException {
        if (transformation == null) {
            throw new NoSuchAlgorithmException("No transformation given.");
        }

        /*
         * Array containing the components of a Cipher transformation:
         *
         * index 0: algorithm (e.g., AES)
         * index 1: mode (e.g., CTR)
         * index 2: padding (e.g., NoPadding)
         */
        String[] parts = new String[3];
        int count = 0;
        StringTokenizer parser = new StringTokenizer(transformation, "/");
        while (parser.hasMoreTokens() && count < 3) {
            parts[count++] = parser.nextToken().trim();
        }
        if (count != 3 || parser.hasMoreTokens()) {
            throw new NoSuchAlgorithmException("Invalid transformation format: " + transformation);
        }
        return new Transform(parts[0], parts[1], parts[2]);
    }

    /**
     * Initialize this cipher with a key and IV.
     *
     * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
     * @param key crypto key
     * @param iv crypto iv
     */
    public void init(int mode, byte[] key, byte[] iv) {
        context = OpensslNative.init(context, mode, algorithm, padding, key, iv);
    }

    /**
     * Continues a multiple-part encryption or decryption operation. The data
     * is encrypted or decrypted, depending on how this cipher was initialized.
     * <p/>
     *
     * All <code>input.remaining()</code> bytes starting at
     * <code>input.position()</code> are processed. The result is stored in
     * the output buffer.
     * <p/>
     *
     * Upon return, the input buffer's position will be equal to its limit;
     * its limit will not have changed. The output buffer's position will have
     * advanced by n, when n is the value returned by this method; the output
     * buffer's limit will not have changed.
     * <p/>
     *
     * If <code>output.remaining()</code> bytes are insufficient to hold the
     * result, a <code>ShortBufferException</code> is thrown.
     *
     * @param input the input ByteBuffer
     * @param output the output ByteBuffer
     * @return int number of bytes stored in <code>output</code>
     * @throws ShortBufferException if there is insufficient space in the
     * output buffer
     */
    public int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException {
        checkState();
        Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required.");
        int len = OpensslNative.update(context, input, input.position(), input.remaining(), output,
                output.position(), output.remaining());
        input.position(input.limit());
        output.position(output.position() + len);
        return len;
    }

    /**
     * Continues a multiple-part encryption/decryption operation. The data
     * is encrypted or decrypted, depending on how this cipher was initialized.
     *
     * @param input the input byte array
     * @param inputOffset the offset in input where the input starts
     * @param inputLen the input length
     * @param output the byte array for the result
     * @param outputOffset the offset in output where the result is stored
     * @return the number of bytes stored in output
     * @throws ShortBufferException if there is insufficient space in the output byte array
     */
    public int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
            throws ShortBufferException {
        checkState();
        return OpensslNative.updateByteArray(context, input, inputOffset, inputLen, output, outputOffset,
                output.length - outputOffset);
    }

    /**
     * Finishes a multiple-part operation. The data is encrypted or decrypted,
     * depending on how this cipher was initialized.
     * <p/>
     *
     * The result is stored in the output buffer. Upon return, the output buffer's
     * position will have advanced by n, where n is the value returned by this
     * method; the output buffer's limit will not have changed.
     * <p/>
     *
     * If <code>output.remaining()</code> bytes are insufficient to hold the result,
     * a <code>ShortBufferException</code> is thrown.
     * <p/>
     *
     * Upon finishing, this method resets this cipher object to the state it was
     * in when previously initialized. That is, the object is available to encrypt
     * or decrypt more data.
     * <p/>
     *
     * If any exception is thrown, this cipher object need to be reset before it
     * can be used again.
     *
     * @param output the output ByteBuffer
     * @return int number of bytes stored in <code>output</code>
     * @throws ShortBufferException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public int doFinal(ByteBuffer output)
            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        checkState();
        Utils.checkArgument(output.isDirect(), "Direct buffer is required.");
        int len = OpensslNative.doFinal(context, output, output.position(), output.remaining());
        output.position(output.position() + len);
        return len;
    }

    /**
     * Encrypts or decrypts data in a single-part operation, or finishes a
     * multiple-part operation.
     *
     * @param output the byte array for the result
     * @param outputOffset the offset in output where the result is stored
     * @return the number of bytes stored in output
     * @throws ShortBufferException if the given output byte array is too small
     * to hold the result
     * @throws BadPaddingException if this cipher is in decryption mode,
     * and (un)padding has been requested, but the decrypted data is not
     * bounded by the appropriate padding bytes
     * @throws IllegalBlockSizeException if this cipher is a block cipher,
     * no padding has been requested (only in encryption mode), and the total
     * input length of the data processed by this cipher is not a multiple of
     * block size; or if this encryption algorithm is unable to
     * process the input data provided.
     */
    public int doFinal(byte[] output, int outputOffset)
            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        checkState();
        return OpensslNative.doFinalByteArray(context, output, outputOffset, output.length - outputOffset);
    }

    /** Forcibly clean the context. */
    public void clean() {
        if (context != 0) {
            OpensslNative.clean(context);
            context = 0;
        }
    }

    /** Checks whether context is initialized. */
    private void checkState() {
        Utils.checkState(context != 0);
    }

    @Override
    protected void finalize() throws Throwable {
        clean();
    }

}