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

org.apache.hadoop.crypto.OpensslCipher Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/**
 * 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 org.apache.hadoop.crypto;

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.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.util.NativeCodeLoader;

import org.apache.hadoop.thirdparty.com.google.common.base.Preconditions;
import org.apache.hadoop.util.PerformanceAdvisory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * OpenSSL cipher using JNI.
 * Currently only AES-CTR is supported. It's flexible to add 
 * other crypto algorithms/modes.
 */
@InterfaceAudience.Private
public final class OpensslCipher {
  private static final Logger LOG =
      LoggerFactory.getLogger(OpensslCipher.class.getName());
  public static final int ENCRYPT_MODE = 1;
  public static final int DECRYPT_MODE = 0;
  
  /** Currently only support AES/CTR/NoPadding. */
  private enum AlgMode {
    AES_CTR;
    
    static int get(String algorithm, String mode) 
        throws NoSuchAlgorithmException {
      try {
        return AlgMode.valueOf(algorithm + "_" + mode).ordinal();
      } catch (Exception e) {
        throw new NoSuchAlgorithmException("Doesn't support algorithm: " + 
            algorithm + " and mode: " + mode);
      }
    }
  }
  
  private enum Padding {
    NoPadding;
    
    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 alg;
  private final int padding;
  
  private static final String loadingFailureReason;

  static {
    String loadingFailure = null;
    try {
      if (!NativeCodeLoader.buildSupportsOpenssl()) {
        PerformanceAdvisory.LOG.debug("Build does not support openssl");
        loadingFailure = "build does not support openssl.";
      } else {
        initIDs();
      }
    } catch (Throwable t) {
      loadingFailure = t.getMessage();
      LOG.debug("Failed to load OpenSSL Cipher.", t);
    } finally {
      loadingFailureReason = loadingFailure;
    }
  }
  
  public static String getLoadingFailureReason() {
    return loadingFailureReason;
  }
  
  private OpensslCipher(long context, int alg, int padding) {
    this.context = context;
    this.alg = alg;
    this.padding = padding;
  }
  
  /**
   * Return an OpensslCipher object that implements the specified
   * transformation.
   * 
   * @param transformation the name of the transformation, e.g., 
   * AES/CTR/NoPadding.
   * @return OpensslCipher an OpensslCipher object
   * @throws NoSuchAlgorithmException if transformation is null, 
   * empty, in an invalid format, or if Openssl doesn't implement the 
   * specified algorithm.
   * @throws NoSuchPaddingException if transformation contains 
   * a padding scheme that is not available.
   */
  public static final OpensslCipher getInstance(String transformation) 
      throws NoSuchAlgorithmException, NoSuchPaddingException {
    Transform transform = tokenizeTransformation(transformation);
    int algMode = AlgMode.get(transform.alg, transform.mode);
    int padding = Padding.get(transform.padding);
    long context = initContext(algMode, padding);
    return new OpensslCipher(context, algMode, padding);
  }
  
  /** Nested class for algorithm, mode and padding. */
  private static class Transform {
    final String alg;
    final String mode;
    final String padding;
    
    public Transform(String alg, String mode, String padding) {
      this.alg = alg;
      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 = init(context, mode, alg, padding, key, iv);
  }
  
  /**
   * Continues a multiple-part encryption or decryption operation. The data
   * is encrypted or decrypted, depending on how this cipher was initialized.
   * 

* * All input.remaining() bytes starting at * input.position() are processed. The result is stored in * the output buffer. *

* * 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. *

* * If output.remaining() bytes are insufficient to hold the * result, a ShortBufferException is thrown. * * @param input the input ByteBuffer * @param output the output ByteBuffer * @return int number of bytes stored in output * @throws ShortBufferException if there is insufficient space in the * output buffer */ public int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException { checkState(); Preconditions.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required."); int len = update(context, input, input.position(), input.remaining(), output, output.position(), output.remaining()); input.position(input.limit()); output.position(output.position() + len); return len; } /** * Finishes a multiple-part operation. The data is encrypted or decrypted, * depending on how this cipher was initialized. *

* * 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. *

* * If output.remaining() bytes are insufficient to hold the result, * a ShortBufferException is thrown. *

* * 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. *

* * 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 output * @throws ShortBufferException * @throws IllegalBlockSizeException * @throws BadPaddingException */ public int doFinal(ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { checkState(); Preconditions.checkArgument(output.isDirect(), "Direct buffer is required."); int len = doFinal(context, output, output.position(), output.remaining()); output.position(output.position() + len); return len; } /** Forcibly clean the context. */ public void clean() { if (context != 0) { clean(context); context = 0; } } /** Check whether context is initialized. */ private void checkState() { Preconditions.checkState(context != 0); } @Override protected void finalize() throws Throwable { clean(); } private native static void initIDs(); private native static long initContext(int alg, int padding); private native long init(long context, int mode, int alg, int padding, byte[] key, byte[] iv); private native int update(long context, ByteBuffer input, int inputOffset, int inputLength, ByteBuffer output, int outputOffset, int maxOutputLength); private native int doFinal(long context, ByteBuffer output, int offset, int maxOutputLength); private native void clean(long context); public native static String getLibraryName(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy