/* * 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.security.util.crypto; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; import javax.crypto.Cipher; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.security.util.EncryptionMethod; import org.apache.nifi.stream.io.StreamUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides a cipher initialized with the original NiFi key derivation process for password-based encryption (MD5 @ 1000 iterations). This is not a secure * {@link org.apache.nifi.security.util.KeyDerivationFunction} (KDF) and should no longer be used. * It is provided only for backward-compatibility with legacy data. A strong KDF should be selected for any future use. * * @see BcryptCipherProvider * @see ScryptCipherProvider * @see PBKDF2CipherProvider */ @Deprecated public class NiFiLegacyCipherProvider extends OpenSSLPKCS5CipherProvider implements PBECipherProvider { private static final Logger logger = LoggerFactory.getLogger(NiFiLegacyCipherProvider.class); // Legacy magic number value private static final int ITERATION_COUNT = 1000; /** * Returns an initialized cipher for the specified algorithm. The key (and IV if necessary) are derived using the NiFi legacy code, based on @see org.apache.nifi.crypto * .OpenSSLPKCS5CipherProvider#getCipher(java.lang.String, java.lang.String, java.lang.String, byte[], boolean) [essentially {@code MD5(password || salt) * 1000 }]. * * @param encryptionMethod the {@link EncryptionMethod} * @param password the secret input * @param salt the salt * @param keyLength the desired key length in bits (ignored because OpenSSL ciphers provide key length in algorithm name) * @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, String password, byte[] salt, int keyLength, boolean encryptMode) throws Exception { try { // This method is defined in the OpenSSL implementation and just uses a locally-overridden iteration count return getInitializedCipher(encryptionMethod, password, salt, encryptMode); } catch (IllegalArgumentException e) { throw e; } catch (Exception e) { throw new ProcessException("Error initializing the cipher", e); } } public byte[] generateSalt(EncryptionMethod encryptionMethod) { byte[] salt = new byte[calculateSaltLength(encryptionMethod)]; new SecureRandom().nextBytes(salt); return salt; } protected void validateSalt(EncryptionMethod encryptionMethod, byte[] salt) { final int saltLength = calculateSaltLength(encryptionMethod); if (salt.length != saltLength && salt.length != 0) { throw new IllegalArgumentException("Salt must be " + saltLength + " bytes or empty"); } } private int calculateSaltLength(EncryptionMethod encryptionMethod) { try { Cipher cipher = Cipher.getInstance(encryptionMethod.getAlgorithm(), encryptionMethod.getProvider()); return cipher.getBlockSize() > 0 ? cipher.getBlockSize() : getDefaultSaltLength(); } catch (Exception e) { logger.warn("Encountered exception determining salt length from encryption method {}", encryptionMethod.getAlgorithm(), e); final int defaultSaltLength = getDefaultSaltLength(); logger.warn("Returning default length: {} bytes", defaultSaltLength); return defaultSaltLength; } } @Override public byte[] readSalt(InputStream in) throws IOException, ProcessException { return readSalt(EncryptionMethod.AES_CBC, in); } /** * Returns the salt provided as part of the cipher stream, or throws an exception if one cannot be detected. * This method is only implemented by {@link NiFiLegacyCipherProvider} because the legacy salt generation was dependent on the cipher block size. * * @param encryptionMethod the encryption method * @param in the cipher InputStream * @return the salt */ public byte[] readSalt(EncryptionMethod encryptionMethod, InputStream in) throws IOException { if (in == null) { throw new IllegalArgumentException("Cannot read salt from null InputStream"); } // The first 8-16 bytes (depending on the cipher blocksize) of the input stream are the salt final int saltLength = calculateSaltLength(encryptionMethod); if (in.available() < saltLength) { throw new ProcessException("The cipher stream is too small to contain the salt"); } byte[] salt = new byte[saltLength]; StreamUtils.fillBuffer(in, salt); return salt; } @Override public void writeSalt(byte[] salt, OutputStream out) throws IOException { if (out == null) { throw new IllegalArgumentException("Cannot write salt to null OutputStream"); } out.write(salt); } @Override protected int getIterationCount() { return ITERATION_COUNT; } }