org.apache.nifi.processors.standard.util.crypto.AESKeyedCipherProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.processors.standard.util.crypto.AESKeyedCipherProvider.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 org.apache.nifi.processors.standard.util.crypto;

import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.security.util.EncryptionMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.List;

/**
 * This is a standard implementation of {@link KeyedCipherProvider} which supports {@code AES} cipher families with arbitrary modes of operation (currently only {@code CBC}, {@code CTR}, and {@code
 * GCM} are supported as {@link EncryptionMethod}s.
 */
public class AESKeyedCipherProvider extends KeyedCipherProvider {
    private static final Logger logger = LoggerFactory.getLogger(AESKeyedCipherProvider.class);
    private static final int IV_LENGTH = 16;
    private static final List<Integer> VALID_KEY_LENGTHS = Arrays.asList(128, 192, 256);

    /**
     * Returns an initialized cipher for the specified algorithm. The IV is provided externally to allow for non-deterministic IVs, as IVs
     * deterministically derived from the password are a potential vulnerability and compromise semantic security. See
     * <a href="http://crypto.stackexchange.com/a/3970/12569">Ilmari Karonen's answer on Crypto Stack Exchange</a>
     *
     * @param encryptionMethod the {@link EncryptionMethod}
     * @param key              the key
     * @param iv               the IV or nonce (cannot be all 0x00)
     * @param encryptMode      true for encrypt, false for decrypt
     * @return the initialized cipher
     * @throws Exception if there is a problem initializing the cipher
     */
    @Override
    public Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, byte[] iv, boolean encryptMode)
            throws Exception {
        try {
            return getInitializedCipher(encryptionMethod, key, iv, encryptMode);
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new ProcessException("Error initializing the cipher", e);
        }
    }

    /**
     * Returns an initialized cipher for the specified algorithm. The IV will be generated internally (for encryption). If decryption is requested, it will throw an exception.
     *
     * @param encryptionMethod the {@link EncryptionMethod}
     * @param key              the key
     * @param encryptMode      true for encrypt, false for decrypt
     * @return the initialized cipher
     * @throws Exception if there is a problem initializing the cipher or if decryption is requested
     */
    @Override
    public Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, boolean encryptMode)
            throws Exception {
        return getCipher(encryptionMethod, key, new byte[0], encryptMode);
    }

    protected Cipher getInitializedCipher(EncryptionMethod encryptionMethod, SecretKey key, byte[] iv,
            boolean encryptMode) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException,
            NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException,
            UnsupportedEncodingException {
        if (encryptionMethod == null) {
            throw new IllegalArgumentException("The encryption method must be specified");
        }

        if (!encryptionMethod.isKeyedCipher()) {
            throw new IllegalArgumentException(encryptionMethod.name() + " requires a PBECipherProvider");
        }

        String algorithm = encryptionMethod.getAlgorithm();
        String provider = encryptionMethod.getProvider();

        if (key == null) {
            throw new IllegalArgumentException("The key must be specified");
        }

        if (!isValidKeyLength(key)) {
            throw new IllegalArgumentException(
                    "The key must be of length [" + StringUtils.join(VALID_KEY_LENGTHS, ", ") + "]");
        }

        Cipher cipher = Cipher.getInstance(algorithm, provider);
        final String operation = encryptMode ? "encrypt" : "decrypt";

        boolean ivIsInvalid = false;

        // If an IV was not provided already, generate a random IV and inject it in the cipher
        int ivLength = cipher.getBlockSize();
        if (iv.length != ivLength) {
            logger.warn("An IV was provided of length {} bytes for {}ion but should be {} bytes", iv.length,
                    operation, ivLength);
            ivIsInvalid = true;
        }

        final byte[] emptyIv = new byte[ivLength];
        if (Arrays.equals(iv, emptyIv)) {
            logger.warn("An empty IV was provided of length {} for {}ion", iv.length, operation);
            ivIsInvalid = true;
        }

        if (ivIsInvalid) {
            if (encryptMode) {
                logger.warn(
                        "Generating new IV. The value can be obtained in the calling code by invoking 'cipher.getIV()';");
                iv = generateIV();
            } else {
                // Can't decrypt without an IV
                throw new IllegalArgumentException("Cannot decrypt without a valid IV");
            }
        }
        cipher.init(encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

        return cipher;
    }

    private boolean isValidKeyLength(SecretKey key) {
        return VALID_KEY_LENGTHS.contains(key.getEncoded().length * 8);
    }

    /**
     * Generates a new random IV of 16 bytes using {@link java.security.SecureRandom}.
     *
     * @return the IV
     */
    public byte[] generateIV() {
        byte[] iv = new byte[IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        return iv;
    }
}