You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by al...@apache.org on 2016/02/05 01:41:38 UTC

[6/7] nifi git commit: NIFI-1257 NIFI-1259

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPKeyBasedEncryptor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPKeyBasedEncryptor.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPKeyBasedEncryptor.java
deleted file mode 100644
index 0364f1c..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPKeyBasedEncryptor.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * 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;
-
-import org.apache.nifi.processor.exception.ProcessException;
-import org.apache.nifi.processor.io.StreamCallback;
-import org.apache.nifi.processors.standard.EncryptContent;
-import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
-import org.bouncycastle.bcpg.ArmoredOutputStream;
-import org.bouncycastle.openpgp.PGPCompressedData;
-import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
-import org.bouncycastle.openpgp.PGPEncryptedData;
-import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
-import org.bouncycastle.openpgp.PGPEncryptedDataList;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPLiteralData;
-import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
-import org.bouncycastle.openpgp.PGPObjectFactory;
-import org.bouncycastle.openpgp.PGPOnePassSignatureList;
-import org.bouncycastle.openpgp.PGPPrivateKey;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
-import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
-import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
-import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
-import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
-import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
-import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.zip.Deflater;
-
-import static org.apache.nifi.processors.standard.util.PGPUtil.BLOCK_SIZE;
-import static org.apache.nifi.processors.standard.util.PGPUtil.BUFFER_SIZE;
-
-public class OpenPGPKeyBasedEncryptor implements Encryptor {
-    private static final Logger logger = LoggerFactory.getLogger(OpenPGPPasswordBasedEncryptor.class);
-
-    private String algorithm;
-    private String provider;
-    // TODO: This can hold either the secret or public keyring path
-    private String keyring;
-    private String userId;
-    private char[] passphrase;
-    private String filename;
-
-    public OpenPGPKeyBasedEncryptor(final String algorithm, final String provider, final String keyring, final String userId, final char[] passphrase, final String filename) {
-        this.algorithm = algorithm;
-        this.provider = provider;
-        this.keyring = keyring;
-        this.userId = userId;
-        this.passphrase = passphrase;
-        this.filename = filename;
-    }
-
-    @Override
-    public StreamCallback getEncryptionCallback() throws Exception {
-        return new OpenPGPEncryptCallback(algorithm, provider, keyring, userId, filename);
-    }
-
-    @Override
-    public StreamCallback getDecryptionCallback() throws Exception {
-        return new OpenPGPDecryptCallback(provider, keyring, passphrase);
-    }
-
-    /**
-     * Returns true if the passphrase is valid.
-     * <p>
-     * This is used in the EncryptContent custom validation to check if the passphrase can extract a private key from the secret key ring. After BC was upgraded from 1.46 to 1.53, the API changed
-     * so this is performed differently but the functionality is equivalent.
-     *
-     * @param provider the provider name
-     * @param secretKeyringFile the file path to the keyring
-     * @param passphrase        the passphrase
-     * @return true if the passphrase can successfully extract any private key
-     * @throws IOException             if there is a problem reading the keyring file
-     * @throws PGPException            if there is a problem parsing/extracting the private key
-     * @throws NoSuchProviderException if the provider is not available
-     */
-    public static boolean validateKeyring(String provider, String secretKeyringFile, char[] passphrase) throws IOException, PGPException, NoSuchProviderException {
-        try {
-            getDecryptedPrivateKey(provider, secretKeyringFile, passphrase);
-            return true;
-        } catch (Exception e) {
-            // If this point is reached, no private key could be extracted with the given passphrase
-            return false;
-        }
-    }
-
-    private static PGPPrivateKey getDecryptedPrivateKey(String provider, String secretKeyringFile, char[] passphrase) throws IOException, PGPException {
-        // TODO: Verify that key IDs cannot be 0
-        return getDecryptedPrivateKey(provider, secretKeyringFile, 0L, passphrase);
-    }
-
-    private static PGPPrivateKey getDecryptedPrivateKey(String provider, String secretKeyringFile, long keyId, char[] passphrase) throws IOException, PGPException {
-        // TODO: Reevaluate the mechanism for executing this task as performance can suffer here and only a specific key needs to be validated
-
-        // Read in from the secret keyring file
-        try (FileInputStream keyInputStream = new FileInputStream(secretKeyringFile)) {
-
-            // Form the SecretKeyRing collection (1.53 way with fingerprint calculator)
-            PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(keyInputStream, new BcKeyFingerprintCalculator());
-
-            // The decryptor is identical for all keys
-            final PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(provider).build(passphrase);
-
-            // Iterate over all secret keyrings
-            Iterator<PGPSecretKeyRing> keyringIterator = pgpSecretKeyRingCollection.getKeyRings();
-            PGPSecretKeyRing keyRing;
-            PGPSecretKey secretKey;
-
-            while (keyringIterator.hasNext()) {
-                keyRing = keyringIterator.next();
-
-                // If keyId exists, get a specific secret key; else, iterate over all
-                if (keyId != 0) {
-                    secretKey = keyRing.getSecretKey(keyId);
-                    try {
-                        return secretKey.extractPrivateKey(decryptor);
-                    } catch (Exception e) {
-                        throw new PGPException("No private key available using passphrase", e);
-                    }
-                } else {
-                    Iterator<PGPSecretKey> keyIterator = keyRing.getSecretKeys();
-
-                    while (keyIterator.hasNext()) {
-                        secretKey = keyIterator.next();
-                        try {
-                            return secretKey.extractPrivateKey(decryptor);
-                        } catch (Exception e) {
-                            // TODO: Log (expected) failures?
-                        }
-                    }
-                }
-            }
-        }
-
-        // If this point is reached, no private key could be extracted with the given passphrase
-        throw new PGPException("No private key available using passphrase");
-    }
-
-    /*
-     * Get the public key for a specific user id from a keyring.
-     */
-    @SuppressWarnings("rawtypes")
-    public static PGPPublicKey getPublicKey(String userId, String publicKeyringFile) throws IOException, PGPException {
-        // TODO: Reevaluate the mechanism for executing this task as performance can suffer here and only a specific key needs to be validated
-
-        // Read in from the public keyring file
-        try (FileInputStream keyInputStream = new FileInputStream(publicKeyringFile)) {
-
-            // Form the PublicKeyRing collection (1.53 way with fingerprint calculator)
-            PGPPublicKeyRingCollection pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(keyInputStream, new BcKeyFingerprintCalculator());
-
-            // Iterate over all public keyrings
-            Iterator<PGPPublicKeyRing> iter = pgpPublicKeyRingCollection.getKeyRings();
-            PGPPublicKeyRing keyRing;
-            while (iter.hasNext()) {
-                keyRing = iter.next();
-
-                // Iterate over each public key in this keyring
-                Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
-                while (keyIter.hasNext()) {
-                    PGPPublicKey publicKey = keyIter.next();
-
-                    // Iterate over each userId attached to the public key
-                    Iterator userIdIterator = publicKey.getUserIDs();
-                    while (userIdIterator.hasNext()) {
-                        String id = (String) userIdIterator.next();
-                        if (userId.equalsIgnoreCase(id)) {
-                            return publicKey;
-                        }
-                    }
-                }
-            }
-        }
-
-        // If this point is reached, no public key could be extracted with the given userId
-        throw new PGPException("Could not find a public key with the given userId");
-    }
-
-    private static class OpenPGPDecryptCallback implements StreamCallback {
-
-        private String provider;
-        private String secretKeyringFile;
-        private char[] passphrase;
-
-        OpenPGPDecryptCallback(final String provider, final String secretKeyringFile, final char[] passphrase) {
-            this.provider = provider;
-            this.secretKeyringFile = secretKeyringFile;
-            this.passphrase = passphrase;
-        }
-
-        @Override
-        public void process(InputStream in, OutputStream out) throws IOException {
-            try (InputStream pgpin = PGPUtil.getDecoderStream(in)) {
-                PGPObjectFactory pgpFactory = new PGPObjectFactory(pgpin, new BcKeyFingerprintCalculator());
-
-                Object obj = pgpFactory.nextObject();
-                if (!(obj instanceof PGPEncryptedDataList)) {
-                    obj = pgpFactory.nextObject();
-                    if (!(obj instanceof PGPEncryptedDataList)) {
-                        throw new ProcessException("Invalid OpenPGP data");
-                    }
-                }
-                PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
-
-                try {
-                    PGPPrivateKey privateKey = null;
-                    PGPPublicKeyEncryptedData encData = null;
-
-                    // Find the secret key in the encrypted data
-                    Iterator it = encList.getEncryptedDataObjects();
-                    while (privateKey == null && it.hasNext()) {
-                        obj = it.next();
-                        if (!(obj instanceof PGPPublicKeyEncryptedData)) {
-                            throw new ProcessException("Invalid OpenPGP data");
-                        }
-                        encData = (PGPPublicKeyEncryptedData) obj;
-
-                        // Check each encrypted data object to see if it contains the key ID for the secret key -> private key
-                        try {
-                            privateKey = getDecryptedPrivateKey(provider, secretKeyringFile, encData.getKeyID(), passphrase);
-                        } catch (PGPException e) {
-                            // TODO: Log (expected) exception?
-                        }
-                    }
-                    if (privateKey == null) {
-                        throw new ProcessException("Secret keyring does not contain the key required to decrypt");
-                    }
-
-                    // Read in the encrypted data stream and decrypt it
-                    final PublicKeyDataDecryptorFactory dataDecryptor = new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(provider).build(privateKey);
-                    try (InputStream clear = encData.getDataStream(dataDecryptor)) {
-                        // Create a plain object factory
-                        JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
-
-                        Object message = plainFact.nextObject();
-
-                        // Check the message type and act accordingly
-
-                        // If compressed, decompress
-                        if (message instanceof PGPCompressedData) {
-                            PGPCompressedData cData = (PGPCompressedData) message;
-                            JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
-
-                            message = pgpFact.nextObject();
-                        }
-
-                        // If the message is literal data, read it and process to the out stream
-                        if (message instanceof PGPLiteralData) {
-                            PGPLiteralData literalData = (PGPLiteralData) message;
-
-                            try (InputStream lis = literalData.getInputStream()) {
-                                final byte[] buffer = new byte[BLOCK_SIZE];
-                                int len;
-                                while ((len = lis.read(buffer)) >= 0) {
-                                    out.write(buffer, 0, len);
-                                }
-                            }
-                        } else if (message instanceof PGPOnePassSignatureList) {
-                            // TODO: This is legacy code but should verify signature list here
-                            throw new PGPException("encrypted message contains a signed message - not literal data.");
-                        } else {
-                            throw new PGPException("message is not a simple encrypted file - type unknown.");
-                        }
-
-                        if (encData.isIntegrityProtected()) {
-                            if (!encData.verify()) {
-                                throw new PGPException("Failed message integrity check");
-                            }
-                        } else {
-                            logger.warn("No message integrity check");
-                        }
-                    }
-                } catch (Exception e) {
-                    throw new ProcessException(e.getMessage());
-                }
-            }
-        }
-
-    }
-
-    private static class OpenPGPEncryptCallback implements StreamCallback {
-
-        private String algorithm;
-        private String provider;
-        private String publicKeyring;
-        private String userId;
-        private String filename;
-
-        OpenPGPEncryptCallback(final String algorithm, final String provider, final String keyring, final String userId, final String filename) {
-            this.algorithm = algorithm;
-            this.provider = provider;
-            this.publicKeyring = keyring;
-            this.userId = userId;
-            this.filename = filename;
-        }
-
-        @Override
-        public void process(InputStream in, OutputStream out) throws IOException {
-            PGPPublicKey publicKey;
-            final boolean isArmored = EncryptContent.isPGPArmoredAlgorithm(algorithm);
-
-            try {
-                publicKey = getPublicKey(userId, publicKeyring);
-            } catch (Exception e) {
-                throw new ProcessException("Invalid public keyring - " + e.getMessage());
-            }
-
-            try {
-                OutputStream output = out;
-                if (isArmored) {
-                    output = new ArmoredOutputStream(out);
-                }
-
-                try {
-                    // TODO: Refactor internal symmetric encryption algorithm to be customizable
-                    PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(
-                            new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom()).setProvider(provider));
-
-                    encryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(publicKey).setProvider(provider));
-
-                    // TODO: Refactor shared encryption code to utility
-                    try (OutputStream encryptedOut = encryptedDataGenerator.open(output, new byte[BUFFER_SIZE])) {
-                        PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP, Deflater.BEST_SPEED);
-                        try (OutputStream compressedOut = compressedDataGenerator.open(encryptedOut, new byte[BUFFER_SIZE])) {
-                            PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
-                            try (OutputStream literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, filename, new Date(), new byte[BUFFER_SIZE])) {
-
-                                final byte[] buffer = new byte[BLOCK_SIZE];
-                                int len;
-                                while ((len = in.read(buffer)) >= 0) {
-                                    literalOut.write(buffer, 0, len);
-                                }
-                            }
-                        }
-                    }
-                } finally {
-                    if (isArmored) {
-                        output.close();
-                    }
-                }
-            } catch (Exception e) {
-                throw new ProcessException(e.getMessage());
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPPasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPPasswordBasedEncryptor.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPPasswordBasedEncryptor.java
deleted file mode 100644
index 1ffe9e4..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/OpenPGPPasswordBasedEncryptor.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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;
-
-import org.apache.nifi.processor.exception.ProcessException;
-import org.apache.nifi.processor.io.StreamCallback;
-import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
-import org.bouncycastle.openpgp.PGPCompressedData;
-import org.bouncycastle.openpgp.PGPEncryptedData;
-import org.bouncycastle.openpgp.PGPEncryptedDataList;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPLiteralData;
-import org.bouncycastle.openpgp.PGPPBEEncryptedData;
-import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
-import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
-import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
-import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
-import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import static org.bouncycastle.openpgp.PGPUtil.getDecoderStream;
-
-public class OpenPGPPasswordBasedEncryptor implements Encryptor {
-    private static final Logger logger = LoggerFactory.getLogger(OpenPGPPasswordBasedEncryptor.class);
-
-    private String algorithm;
-    private String provider;
-    private char[] password;
-    private String filename;
-
-    public OpenPGPPasswordBasedEncryptor(final String algorithm, final String provider, final char[] passphrase, final String filename) {
-        this.algorithm = algorithm;
-        this.provider = provider;
-        this.password = passphrase;
-        this.filename = filename;
-    }
-
-    @Override
-    public StreamCallback getEncryptionCallback() throws Exception {
-        return new OpenPGPEncryptCallback(algorithm, provider, password, filename);
-    }
-
-    @Override
-    public StreamCallback getDecryptionCallback() throws Exception {
-        return new OpenPGPDecryptCallback(provider, password);
-    }
-
-    private static class OpenPGPDecryptCallback implements StreamCallback {
-
-        private String provider;
-        private char[] password;
-
-        OpenPGPDecryptCallback(final String provider, final char[] password) {
-            this.provider = provider;
-            this.password = password;
-        }
-
-        @Override
-        public void process(InputStream in, OutputStream out) throws IOException {
-            InputStream pgpin = getDecoderStream(in);
-            JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(pgpin);
-
-            Object obj = pgpFactory.nextObject();
-            if (!(obj instanceof PGPEncryptedDataList)) {
-                obj = pgpFactory.nextObject();
-                if (!(obj instanceof PGPEncryptedDataList)) {
-                    throw new ProcessException("Invalid OpenPGP data");
-                }
-            }
-            PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
-
-            obj = encList.get(0);
-            if (!(obj instanceof PGPPBEEncryptedData)) {
-                throw new ProcessException("Invalid OpenPGP data");
-            }
-            PGPPBEEncryptedData encryptedData = (PGPPBEEncryptedData) obj;
-
-            try {
-                final PGPDigestCalculatorProvider digestCalculatorProvider = new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build();
-                final PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(digestCalculatorProvider).setProvider(provider).build(password);
-                InputStream clear = encryptedData.getDataStream(decryptorFactory);
-
-                JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(clear);
-
-                obj = pgpObjectFactory.nextObject();
-                if (obj instanceof PGPCompressedData) {
-                    PGPCompressedData compressedData = (PGPCompressedData) obj;
-                    pgpObjectFactory = new JcaPGPObjectFactory(compressedData.getDataStream());
-                    obj = pgpObjectFactory.nextObject();
-                }
-
-                PGPLiteralData literalData = (PGPLiteralData) obj;
-                InputStream plainIn = literalData.getInputStream();
-                final byte[] buffer = new byte[org.apache.nifi.processors.standard.util.PGPUtil.BLOCK_SIZE];
-                int len;
-                while ((len = plainIn.read(buffer)) >= 0) {
-                    out.write(buffer, 0, len);
-                }
-
-                if (encryptedData.isIntegrityProtected()) {
-                    if (!encryptedData.verify()) {
-                        throw new PGPException("Integrity check failed");
-                    }
-                } else {
-                    logger.warn("No message integrity check");
-                }
-            } catch (Exception e) {
-                throw new ProcessException(e.getMessage());
-            }
-        }
-    }
-
-    private static class OpenPGPEncryptCallback implements StreamCallback {
-
-        private String algorithm;
-        private String provider;
-        private char[] password;
-        private String filename;
-
-        OpenPGPEncryptCallback(final String algorithm, final String provider, final char[] password, final String filename) {
-            this.algorithm = algorithm;
-            this.provider = provider;
-            this.password = password;
-            this.filename = filename;
-        }
-
-        @Override
-        public void process(InputStream in, OutputStream out) throws IOException {
-            try {
-                PGPKeyEncryptionMethodGenerator encryptionMethodGenerator = new JcePBEKeyEncryptionMethodGenerator(password).setProvider(provider);
-                org.apache.nifi.processors.standard.util.PGPUtil.encrypt(in, out, algorithm, provider, PGPEncryptedData.AES_128, filename, encryptionMethodGenerator);
-            } catch (Exception e) {
-                throw new ProcessException(e.getMessage());
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/PasswordBasedEncryptor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/PasswordBasedEncryptor.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/PasswordBasedEncryptor.java
deleted file mode 100644
index d3d50b8..0000000
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/PasswordBasedEncryptor.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.processor.exception.ProcessException;
-import org.apache.nifi.processor.io.StreamCallback;
-import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
-import org.apache.nifi.security.util.KeyDerivationFunction;
-import org.apache.nifi.stream.io.StreamUtils;
-
-public class PasswordBasedEncryptor implements Encryptor {
-
-    private Cipher cipher;
-    private int saltSize;
-    private SecretKey secretKey;
-    private KeyDerivationFunction kdf;
-    private int iterationsCount = LEGACY_KDF_ITERATIONS;
-
-    @Deprecated
-    private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
-    private static final int DEFAULT_SALT_SIZE = 8;
-    // TODO: Eventually KDF-specific values should be refactored into injectable interface impls
-    private static final int LEGACY_KDF_ITERATIONS = 1000;
-    private static final int OPENSSL_EVP_HEADER_SIZE = 8;
-    private static final int OPENSSL_EVP_SALT_SIZE = 8;
-    private static final String OPENSSL_EVP_HEADER_MARKER = "Salted__";
-    private static final int OPENSSL_EVP_KDF_ITERATIONS = 0;
-    private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
-
-    private static boolean isUnlimitedStrengthCryptographyEnabled;
-
-    // Evaluate an unlimited strength algorithm to determine if we support the capability we have on the system
-    static {
-        try {
-            isUnlimitedStrengthCryptographyEnabled = (Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH);
-        } catch (NoSuchAlgorithmException e) {
-            // if there are issues with this, we default back to the value established
-            isUnlimitedStrengthCryptographyEnabled = false;
-        }
-    }
-
-    public PasswordBasedEncryptor(final String algorithm, final String providerName, final char[] password, KeyDerivationFunction kdf) {
-        super();
-        try {
-            // initialize cipher
-            this.cipher = Cipher.getInstance(algorithm, providerName);
-            this.kdf = kdf;
-
-            if (isOpenSSLKDF()) {
-                this.saltSize = OPENSSL_EVP_SALT_SIZE;
-                this.iterationsCount = OPENSSL_EVP_KDF_ITERATIONS;
-            } else {
-                int algorithmBlockSize = cipher.getBlockSize();
-                this.saltSize = (algorithmBlockSize > 0) ? algorithmBlockSize : DEFAULT_SALT_SIZE;
-            }
-
-            // initialize SecretKey from password
-            final PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
-            final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, providerName);
-            this.secretKey = factory.generateSecret(pbeKeySpec);
-        } catch (Exception e) {
-            throw new ProcessException(e);
-        }
-    }
-
-    public static int getMaxAllowedKeyLength(final String algorithm) {
-        if (StringUtils.isEmpty(algorithm)) {
-            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
-        }
-        String parsedCipher = parseCipherFromAlgorithm(algorithm);
-        try {
-            return Cipher.getMaxAllowedKeyLength(parsedCipher);
-        } catch (NoSuchAlgorithmException e) {
-            // Default algorithm max key length on unmodified JRE
-            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
-        }
-    }
-
-    private static String parseCipherFromAlgorithm(final String algorithm) {
-        // This is not optimal but the algorithms do not have a standard format
-        final String AES = "AES";
-        final String TDES = "TRIPLEDES";
-        final String DES = "DES";
-        final String RC4 = "RC4";
-        final String RC2 = "RC2";
-        final String TWOFISH = "TWOFISH";
-        final List<String> SYMMETRIC_CIPHERS = Arrays.asList(AES, TDES, DES, RC4, RC2, TWOFISH);
-
-        // The algorithms contain "TRIPLEDES" but the cipher name is "DESede"
-        final String ACTUAL_TDES_CIPHER = "DESede";
-
-        for (String cipher : SYMMETRIC_CIPHERS) {
-            if (algorithm.contains(cipher)) {
-                if (cipher.equals(TDES)) {
-                    return ACTUAL_TDES_CIPHER;
-                } else {
-                    return cipher;
-                }
-            }
-        }
-
-        return algorithm;
-    }
-
-    public static boolean supportsUnlimitedStrength() {
-        return isUnlimitedStrengthCryptographyEnabled;
-    }
-
-    @Override
-    public StreamCallback getEncryptionCallback() throws ProcessException {
-        try {
-            byte[] salt = new byte[saltSize];
-            SecureRandom secureRandom = new SecureRandom();
-            secureRandom.nextBytes(salt);
-            return new EncryptCallback(salt);
-        } catch (Exception e) {
-            throw new ProcessException(e);
-        }
-    }
-
-    @Override
-    public StreamCallback getDecryptionCallback() throws ProcessException {
-        return new DecryptCallback();
-    }
-
-    private int getIterationsCount() {
-        return iterationsCount;
-    }
-
-    private boolean isOpenSSLKDF() {
-        return KeyDerivationFunction.OPENSSL_EVP_BYTES_TO_KEY.equals(kdf);
-    }
-
-    private class DecryptCallback implements StreamCallback {
-
-        public DecryptCallback() {
-        }
-        @Override
-        public void process(final InputStream in, final OutputStream out) throws IOException {
-            byte[] salt = new byte[saltSize];
-
-            try {
-                // If the KDF is OpenSSL, try to read the salt from the input stream
-                if (isOpenSSLKDF()) {
-                    // The header and salt format is "Salted__salt x8b" in ASCII
-
-                    // Try to read the header and salt from the input
-                    byte[] header = new byte[PasswordBasedEncryptor.OPENSSL_EVP_HEADER_SIZE];
-
-                    // Mark the stream in case there is no salt
-                    in.mark(OPENSSL_EVP_HEADER_SIZE + 1);
-                    StreamUtils.fillBuffer(in, header);
-
-                    final byte[] headerMarkerBytes = OPENSSL_EVP_HEADER_MARKER.getBytes(StandardCharsets.US_ASCII);
-
-                    if (!Arrays.equals(headerMarkerBytes, header)) {
-                        // No salt present
-                        salt = new byte[0];
-                        // Reset the stream because we skipped 8 bytes of cipher text
-                        in.reset();
-                    }
-                }
-
-                StreamUtils.fillBuffer(in, salt);
-            } catch (final EOFException e) {
-                throw new ProcessException("Cannot decrypt because file size is smaller than salt size", e);
-            }
-
-            final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, getIterationsCount());
-            try {
-                cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
-            } catch (final Exception e) {
-                throw new ProcessException(e);
-            }
-
-            final byte[] buffer = new byte[65536];
-            int len;
-            while ((len = in.read(buffer)) > 0) {
-                final byte[] decryptedBytes = cipher.update(buffer, 0, len);
-                if (decryptedBytes != null) {
-                    out.write(decryptedBytes);
-                }
-            }
-
-            try {
-                out.write(cipher.doFinal());
-            } catch (final Exception e) {
-                throw new ProcessException(e);
-            }
-        }
-    }
-
-    private class EncryptCallback implements StreamCallback {
-
-        private final byte[] salt;
-
-        public EncryptCallback(final byte[] salt) {
-            this.salt = salt;
-        }
-
-        @Override
-        public void process(final InputStream in, final OutputStream out) throws IOException {
-            final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, getIterationsCount());
-            try {
-                cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
-            } catch (final Exception e) {
-                throw new ProcessException(e);
-            }
-
-            // If this is OpenSSL EVP, the salt must be preceded by the header
-            if (isOpenSSLKDF()) {
-                out.write(OPENSSL_EVP_HEADER_MARKER.getBytes(StandardCharsets.US_ASCII));
-            }
-
-            out.write(salt);
-
-            final byte[] buffer = new byte[65536];
-            int len;
-            while ((len = in.read(buffer)) > 0) {
-                final byte[] encryptedBytes = cipher.update(buffer, 0, len);
-                if (encryptedBytes != null) {
-                    out.write(encryptedBytes);
-                }
-            }
-
-            try {
-                out.write(cipher.doFinal());
-            } catch (final IllegalBlockSizeException | BadPaddingException e) {
-                throw new ProcessException(e);
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProvider.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProvider.java
new file mode 100644
index 0000000..907aed2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/AESKeyedCipherProvider.java
@@ -0,0 +1,153 @@
+/*
+ * 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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProvider.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProvider.java
new file mode 100644
index 0000000..8c5f464
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/BcryptCipherProvider.java
@@ -0,0 +1,198 @@
+/*
+ * 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.processors.standard.util.crypto.bcrypt.BCrypt;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BcryptCipherProvider extends RandomIVPBECipherProvider {
+    private static final Logger logger = LoggerFactory.getLogger(BcryptCipherProvider.class);
+
+    private final int workFactor;
+    /**
+     * This can be calculated automatically using the code {@see BcryptCipherProviderGroovyTest#calculateMinimumWorkFactor} or manually updated by a maintainer
+     */
+    private static final int DEFAULT_WORK_FACTOR = 12;
+    private static final int DEFAULT_SALT_LENGTH = 16;
+
+    private static final Pattern BCRYPT_SALT_FORMAT = Pattern.compile("^\\$\\d\\w\\$\\d{2}\\$[\\w\\/\\.]{22}");
+
+    /**
+     * Instantiates a Bcrypt cipher provider with the default work factor 12 (2^12 key expansion rounds).
+     */
+    public BcryptCipherProvider() {
+        this(DEFAULT_WORK_FACTOR);
+    }
+
+    /**
+     * Instantiates a Bcrypt cipher provider with the specified work factor w (2^w key expansion rounds).
+     *
+     * @param workFactor the (log) number of key expansion rounds [4..30]
+     */
+    public BcryptCipherProvider(int workFactor) {
+        this.workFactor = workFactor;
+        if (workFactor < DEFAULT_WORK_FACTOR) {
+            logger.warn("The provided work factor {} is below the recommended minimum {}", workFactor, DEFAULT_WORK_FACTOR);
+        }
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key is derived by the KDF of the implementation. 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 password         the secret input
+     * @param salt             the complete salt (e.g. {@code "$2a$10$gUVbkVzp79H8YaCOsCVZNu".getBytes(StandardCharsets.UTF_8)})
+     * @param iv               the IV
+     * @param keyLength        the desired key length in bits
+     * @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, byte[] iv, int keyLength, boolean encryptMode) throws Exception {
+        try {
+            return getInitializedCipher(encryptionMethod, password, salt, iv, keyLength, encryptMode);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProcessException("Error initializing the cipher", e);
+        }
+    }
+
+    @Override
+    Logger getLogger() {
+        return logger;
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and IV if necessary) are derived by the KDF of the implementation.
+     * <p>
+     * This method is deprecated because while Bcrypt could generate a random salt to use, it would not be returned to the caller of this method and future derivations would fail. Provide a valid
+     * salt generated by {@link BcryptCipherProvider#generateSalt()}.
+     * </p>
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param keyLength        the desired key length in bits
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     * @deprecated Provide a salt parameter using {@link BcryptCipherProvider#getCipher(EncryptionMethod, String, byte[], int, boolean)}
+     */
+    @Deprecated
+    @Override
+    public Cipher getCipher(EncryptionMethod encryptionMethod, String password, int keyLength, boolean encryptMode) throws Exception {
+        throw new UnsupportedOperationException("The cipher cannot be initialized without a valid salt. Use BcryptCipherProvider#generateSalt() to generate a valid salt");
+    }
+
+    /**
+     * Returns an initialized cipher for the specified algorithm. The key (and IV if necessary) are derived by the KDF of the implementation.
+     *
+     * The IV can be retrieved by the calling method using {@link Cipher#getIV()}.
+     *
+     * @param encryptionMethod the {@link EncryptionMethod}
+     * @param password         the secret input
+     * @param salt             the complete salt (e.g. {@code "$2a$10$gUVbkVzp79H8YaCOsCVZNu".getBytes(StandardCharsets.UTF_8)})
+     * @param keyLength        the desired key length in bits
+     * @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 {
+        return getCipher(encryptionMethod, password, salt, new byte[0], keyLength, encryptMode);
+    }
+
+    protected Cipher getInitializedCipher(EncryptionMethod encryptionMethod, String password, byte[] salt, byte[] iv, int keyLength, boolean encryptMode) throws Exception {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("The encryption method must be specified");
+        }
+        if (!encryptionMethod.isCompatibleWithStrongKDFs()) {
+            throw new IllegalArgumentException(encryptionMethod.name() + " is not compatible with Bcrypt");
+        }
+
+        if (StringUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("Encryption with an empty password is not supported");
+        }
+
+        String algorithm = encryptionMethod.getAlgorithm();
+        String provider = encryptionMethod.getProvider();
+
+        final String cipherName = CipherUtility.parseCipherFromAlgorithm(algorithm);
+        if (!CipherUtility.isValidKeyLength(keyLength, cipherName)) {
+            throw new IllegalArgumentException(String.valueOf(keyLength) + " is not a valid key length for " + cipherName);
+        }
+
+        String bcryptSalt = formatSaltForBcrypt(salt);
+
+        String hash = BCrypt.hashpw(password, bcryptSalt);
+
+        /* The SHA-512 hash is required in order to derive a key longer than 184 bits (the resulting size of the Bcrypt hash) and ensuring the avalanche effect causes higher key entropy (if all
+        derived keys follow a consistent pattern, it weakens the strength of the encryption) */
+        MessageDigest digest = MessageDigest.getInstance("SHA-512", provider);
+        byte[] dk = digest.digest(hash.getBytes(StandardCharsets.UTF_8));
+        dk = Arrays.copyOf(dk, keyLength / 8);
+        SecretKey tempKey = new SecretKeySpec(dk, algorithm);
+
+        KeyedCipherProvider keyedCipherProvider = new AESKeyedCipherProvider();
+        return keyedCipherProvider.getCipher(encryptionMethod, tempKey, iv, encryptMode);
+    }
+
+    private String formatSaltForBcrypt(byte[] salt) {
+        if (salt == null || salt.length == 0) {
+            throw new IllegalArgumentException("The salt cannot be empty. To generate a salt, use BcryptCipherProvider#generateSalt()");
+        }
+
+        String rawSalt = new String(salt, StandardCharsets.UTF_8);
+        Matcher matcher = BCRYPT_SALT_FORMAT.matcher(rawSalt);
+
+        if (matcher.find()) {
+            return rawSalt;
+        } else {
+            throw new IllegalArgumentException("The salt must be of the format $2a$10$gUVbkVzp79H8YaCOsCVZNu. To generate a salt, use BcryptCipherProvider#generateSalt()");
+        }
+    }
+
+    @Override
+    public byte[] generateSalt() {
+        return BCrypt.gensalt(workFactor).getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public int getDefaultSaltLength() {
+        return DEFAULT_SALT_LENGTH;
+    }
+
+    protected int getWorkFactor() {
+        return workFactor;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProvider.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProvider.java
new file mode 100644
index 0000000..46e815f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProvider.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * Marker interface for cipher providers.
+ */
+public interface CipherProvider {
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactory.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactory.java
new file mode 100644
index 0000000..e04a7b4
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherProviderFactory.java
@@ -0,0 +1,57 @@
+/*
+ * 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.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.KeyDerivationFunction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CipherProviderFactory {
+    private static final Logger logger = LoggerFactory.getLogger(CipherProviderFactory.class);
+
+    private static Map<KeyDerivationFunction, Class<? extends CipherProvider>> registeredCipherProviders;
+
+    static {
+        registeredCipherProviders = new HashMap<>();
+        registeredCipherProviders.put(KeyDerivationFunction.NIFI_LEGACY, NiFiLegacyCipherProvider.class);
+        registeredCipherProviders.put(KeyDerivationFunction.OPENSSL_EVP_BYTES_TO_KEY, OpenSSLPKCS5CipherProvider.class);
+        registeredCipherProviders.put(KeyDerivationFunction.PBKDF2, PBKDF2CipherProvider.class);
+        registeredCipherProviders.put(KeyDerivationFunction.BCRYPT, BcryptCipherProvider.class);
+        registeredCipherProviders.put(KeyDerivationFunction.SCRYPT, ScryptCipherProvider.class);
+        registeredCipherProviders.put(KeyDerivationFunction.NONE, AESKeyedCipherProvider.class);
+    }
+
+    public static CipherProvider getCipherProvider(KeyDerivationFunction kdf) {
+        logger.debug("{} KDFs registered", registeredCipherProviders.size());
+
+        if (registeredCipherProviders.containsKey(kdf)) {
+            Class<? extends CipherProvider> clazz = registeredCipherProviders.get(kdf);
+            try {
+                return clazz.newInstance();
+            } catch (Exception e) {
+               logger.error("Error instantiating new {} with default parameters for {}", clazz.getName(), kdf.getName());
+                throw new ProcessException("Error instantiating cipher provider");
+            }
+        }
+
+        throw new IllegalArgumentException("No cipher provider registered for " + kdf.getName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherUtility.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherUtility.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherUtility.java
new file mode 100644
index 0000000..fbd5b4e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/CipherUtility.java
@@ -0,0 +1,320 @@
+/*
+ * 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.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.stream.io.ByteArrayOutputStream;
+import org.apache.nifi.stream.io.StreamUtils;
+
+import javax.crypto.Cipher;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CipherUtility {
+
+    public static final int BUFFER_SIZE = 65536;
+    private static final Pattern KEY_LENGTH_PATTERN = Pattern.compile("([\\d]+)BIT");
+
+    private static final Map<String, Integer> MAX_PASSWORD_LENGTH_BY_ALGORITHM;
+
+    static {
+        Map<String, Integer> aMap = new HashMap<>();
+        /**
+         * These values were determined empirically by running {@link NiFiLegacyCipherProviderGroovyTest#testShouldDetermineDependenceOnUnlimitedStrengthCrypto()}
+         *, which evaluates each algorithm in a try/catch harness with increasing password size until it throws an exception.
+         * This was performed on a JVM without the Unlimited Strength Jurisdiction cryptographic policy files installed.
+         */
+        aMap.put("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
+        aMap.put("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
+        aMap.put("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
+        aMap.put("PBEWITHMD5ANDDES", 16);
+        aMap.put("PBEWITHMD5ANDRC2", 16);
+        aMap.put("PBEWITHSHA1ANDRC2", 16);
+        aMap.put("PBEWITHSHA1ANDDES", 16);
+        aMap.put("PBEWITHSHAAND128BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHAAND192BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHAAND256BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHAAND40BITRC2-CBC", 7);
+        aMap.put("PBEWITHSHAAND128BITRC2-CBC", 7);
+        aMap.put("PBEWITHSHAAND40BITRC4", 7);
+        aMap.put("PBEWITHSHAAND128BITRC4", 7);
+        aMap.put("PBEWITHSHA256AND128BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHA256AND192BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHA256AND256BITAES-CBC-BC", 7);
+        aMap.put("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 7);
+        aMap.put("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 7);
+        aMap.put("PBEWITHSHAANDTWOFISH-CBC", 7);
+        MAX_PASSWORD_LENGTH_BY_ALGORITHM = Collections.unmodifiableMap(aMap);
+    }
+
+    /**
+     * Returns the cipher algorithm from the full algorithm name. Useful for getting key lengths, etc.
+     * <p/>
+     * Ex: PBEWITHMD5AND128BITAES-CBC-OPENSSL -> AES
+     *
+     * @param algorithm the full algorithm name
+     * @return the generic cipher name or the full algorithm if one cannot be extracted
+     */
+    public static String parseCipherFromAlgorithm(final String algorithm) {
+        if (StringUtils.isEmpty(algorithm)) {
+            return algorithm;
+        }
+        String formattedAlgorithm = algorithm.toUpperCase();
+
+        // This is not optimal but the algorithms do not have a standard format
+        final String AES = "AES";
+        final String TDES = "TRIPLEDES";
+        final String TDES_ALTERNATE = "DESEDE";
+        final String DES = "DES";
+        final String RC4 = "RC4";
+        final String RC2 = "RC2";
+        final String TWOFISH = "TWOFISH";
+        final List<String> SYMMETRIC_CIPHERS = Arrays.asList(AES, TDES, TDES_ALTERNATE, DES, RC4, RC2, TWOFISH);
+
+        // The algorithms contain "TRIPLEDES" but the cipher name is "DESede"
+        final String ACTUAL_TDES_CIPHER = "DESede";
+
+        for (String cipher : SYMMETRIC_CIPHERS) {
+            if (formattedAlgorithm.contains(cipher)) {
+                if (cipher.equals(TDES) || cipher.equals(TDES_ALTERNATE)) {
+                    return ACTUAL_TDES_CIPHER;
+                } else {
+                    return cipher;
+                }
+            }
+        }
+
+        return algorithm;
+    }
+
+    /**
+     * Returns the cipher key length from the full algorithm name. Useful for getting key lengths, etc.
+     * <p/>
+     * Ex: PBEWITHMD5AND128BITAES-CBC-OPENSSL -> 128
+     *
+     * @param algorithm the full algorithm name
+     * @return the key length or -1 if one cannot be extracted
+     */
+    public static int parseKeyLengthFromAlgorithm(final String algorithm) {
+        int keyLength = parseActualKeyLengthFromAlgorithm(algorithm);
+        if (keyLength != -1) {
+            return keyLength;
+        } else {
+            // Key length not explicitly named in algorithm
+            String cipher = parseCipherFromAlgorithm(algorithm);
+            return getDefaultKeyLengthForCipher(cipher);
+        }
+    }
+
+    private static int parseActualKeyLengthFromAlgorithm(final String algorithm) {
+        Matcher matcher = KEY_LENGTH_PATTERN.matcher(algorithm);
+        if (matcher.find()) {
+            return Integer.parseInt(matcher.group(1));
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Returns true if the provided key length is a valid key length for the provided cipher family. Does not reflect if the Unlimited Strength Cryptography Jurisdiction Policies are installed.
+     * Does not reflect if the key length is correct for a specific combination of cipher and PBE-derived key length.
+     * <p/>
+     * Ex:
+     * <p/>
+     * 256 is valid for {@code AES/CBC/PKCS7Padding} but not {@code PBEWITHMD5AND128BITAES-CBC-OPENSSL}. However, this method will return {@code true} for both because it only gets the cipher
+     * family, {@code AES}.
+     * <p/>
+     * 64, AES -> false
+     * [128, 192, 256], AES -> true
+     *
+     * @param keyLength the key length in bits
+     * @param cipher    the cipher family
+     * @return true if this key length is valid
+     */
+    public static boolean isValidKeyLength(int keyLength, final String cipher) {
+        if (StringUtils.isEmpty(cipher)) {
+            return false;
+        }
+        return getValidKeyLengthsForAlgorithm(cipher).contains(keyLength);
+    }
+
+    /**
+     * Returns true if the provided key length is a valid key length for the provided algorithm. Does not reflect if the Unlimited Strength Cryptography Jurisdiction Policies are installed.
+     * <p/>
+     * Ex:
+     * <p/>
+     * 256 is valid for {@code AES/CBC/PKCS7Padding} but not {@code PBEWITHMD5AND128BITAES-CBC-OPENSSL}.
+     * <p/>
+     * 64, AES/CBC/PKCS7Padding -> false
+     * [128, 192, 256], AES/CBC/PKCS7Padding -> true
+     * <p/>
+     * 128, PBEWITHMD5AND128BITAES-CBC-OPENSSL -> true
+     * [192, 256], PBEWITHMD5AND128BITAES-CBC-OPENSSL -> false
+     *
+     * @param keyLength the key length in bits
+     * @param algorithm the specific algorithm
+     * @return true if this key length is valid
+     */
+    public static boolean isValidKeyLengthForAlgorithm(int keyLength, final String algorithm) {
+        if (StringUtils.isEmpty(algorithm)) {
+            return false;
+        }
+        return getValidKeyLengthsForAlgorithm(algorithm).contains(keyLength);
+    }
+
+    public static List<Integer> getValidKeyLengthsForAlgorithm(String algorithm) {
+        List<Integer> validKeyLengths = new ArrayList<>();
+        if (StringUtils.isEmpty(algorithm)) {
+            return validKeyLengths;
+        }
+
+        // Some algorithms specify a single key size
+        int keyLength = parseActualKeyLengthFromAlgorithm(algorithm);
+        if (keyLength != -1) {
+            validKeyLengths.add(keyLength);
+            return validKeyLengths;
+        }
+
+        // The algorithm does not specify a key size
+        String cipher = parseCipherFromAlgorithm(algorithm);
+        switch (cipher.toUpperCase()) {
+            case "DESEDE":
+                // 3DES keys have the cryptographic strength of 7/8 because of parity bits, but are often represented with n*8 bytes
+                return Arrays.asList(56, 64, 112, 128, 168, 192);
+            case "DES":
+                return Arrays.asList(56, 64);
+            case "RC2":
+            case "RC4":
+            case "RC5":
+                /** These ciphers can have arbitrary length keys but that's a really bad idea, {@see http://crypto.stackexchange.com/a/9963/12569}.
+                 * Also, RC* is deprecated and should be considered insecure */
+                for (int i = 40; i <= 2048; i++) {
+                    validKeyLengths.add(i);
+                }
+                return validKeyLengths;
+            case "AES":
+            case "TWOFISH":
+                return Arrays.asList(128, 192, 256);
+            default:
+                return validKeyLengths;
+        }
+    }
+
+    private static int getDefaultKeyLengthForCipher(String cipher) {
+        if (StringUtils.isEmpty(cipher)) {
+            return -1;
+        }
+        cipher = cipher.toUpperCase();
+        switch (cipher) {
+            case "DESEDE":
+                return 112;
+            case "DES":
+                return 64;
+            case "RC2":
+            case "RC4":
+            case "RC5":
+            default:
+                return 128;
+        }
+    }
+
+    public static void processStreams(Cipher cipher, InputStream in, OutputStream out) {
+        try {
+            final byte[] buffer = new byte[BUFFER_SIZE];
+            int len;
+            while ((len = in.read(buffer)) > 0) {
+                final byte[] decryptedBytes = cipher.update(buffer, 0, len);
+                if (decryptedBytes != null) {
+                    out.write(decryptedBytes);
+                }
+            }
+
+            out.write(cipher.doFinal());
+        } catch (Exception e) {
+            throw new ProcessException(e);
+        }
+    }
+
+    public static byte[] readBytesFromInputStream(InputStream in, String label, int limit, byte[] delimiter) throws IOException, ProcessException {
+        if (in == null) {
+            throw new IllegalArgumentException("Cannot read " + label + " from null InputStream");
+        }
+
+        // If the value is not detected within the first n bytes, throw an exception
+        in.mark(limit);
+
+        // The first n bytes of the input stream contain the value up to the custom delimiter
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        byte[] stoppedBy = StreamUtils.copyExclusive(in, bytesOut, limit + delimiter.length, delimiter);
+
+        if (stoppedBy != null) {
+            byte[] bytes = bytesOut.toByteArray();
+            return bytes;
+        }
+
+        // If no delimiter was found, reset the cursor
+        in.reset();
+        return null;
+    }
+
+    public static void writeBytesToOutputStream(OutputStream out, byte[] value, String label, byte[] delimiter) throws IOException {
+        if (out == null) {
+            throw new IllegalArgumentException("Cannot write " + label + " to null OutputStream");
+        }
+        out.write(value);
+        out.write(delimiter);
+    }
+
+    public static String encodeBase64NoPadding(final byte[] bytes) {
+        String base64UrlNoPadding = Base64.encodeBase64URLSafeString(bytes);
+        base64UrlNoPadding = base64UrlNoPadding.replaceAll("-", "+");
+        base64UrlNoPadding = base64UrlNoPadding.replaceAll("_", "/");
+        return base64UrlNoPadding;
+    }
+
+    public static boolean passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(final int passwordLength, EncryptionMethod encryptionMethod) {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("Cannot evaluate an empty encryption method algorithm");
+        }
+
+            return passwordLength <= getMaximumPasswordLengthForAlgorithmOnLimitedStrengthCrypto(encryptionMethod);
+    }
+
+    public static int getMaximumPasswordLengthForAlgorithmOnLimitedStrengthCrypto(EncryptionMethod encryptionMethod) {
+        if (encryptionMethod == null) {
+            throw new IllegalArgumentException("Cannot evaluate an empty encryption method algorithm");
+        }
+
+        if (MAX_PASSWORD_LENGTH_BY_ALGORITHM.containsKey(encryptionMethod.getAlgorithm())) {
+            return MAX_PASSWORD_LENGTH_BY_ALGORITHM.get(encryptionMethod.getAlgorithm());
+        } else {
+            return -1;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedCipherProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedCipherProvider.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedCipherProvider.java
new file mode 100644
index 0000000..f0fa4fc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedCipherProvider.java
@@ -0,0 +1,73 @@
+/*
+ * 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.nifi.processor.exception.ProcessException;
+import org.apache.nifi.security.util.EncryptionMethod;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+public abstract class KeyedCipherProvider implements CipherProvider {
+    static final byte[] IV_DELIMITER = "NiFiIV".getBytes(StandardCharsets.UTF_8);
+    // This is 16 bytes for AES but can vary for other ciphers
+    static final int MAX_IV_LIMIT = 16;
+
+    /**
+     * 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
+     * @param encryptMode      true for encrypt, false for decrypt
+     * @return the initialized cipher
+     * @throws Exception if there is a problem initializing the cipher
+     */
+    abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, byte[] iv, boolean encryptMode) throws Exception;
+
+    /**
+     * 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
+     */
+    abstract Cipher getCipher(EncryptionMethod encryptionMethod, SecretKey key, boolean encryptMode) throws Exception;
+
+    /**
+     * Generates a new random IV of the correct length.
+     *
+     * @return the IV
+     */
+    abstract byte[] generateIV();
+
+    public byte[] readIV(InputStream in) throws IOException, ProcessException {
+        return CipherUtility.readBytesFromInputStream(in, "IV", MAX_IV_LIMIT, IV_DELIMITER);
+    }
+
+    public void writeIV(byte[] iv, OutputStream out) throws IOException {
+        CipherUtility.writeBytesToOutputStream(out, iv, "IV", IV_DELIMITER);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptor.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptor.java
new file mode 100644
index 0000000..c573d3d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/crypto/KeyedEncryptor.java
@@ -0,0 +1,163 @@
+/*
+ * 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.processor.io.StreamCallback;
+import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
+import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.security.util.KeyDerivationFunction;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+
+public class KeyedEncryptor implements Encryptor {
+
+    private EncryptionMethod encryptionMethod;
+    private SecretKey key;
+    private byte[] iv;
+
+    private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
+
+    private static boolean isUnlimitedStrengthCryptographyEnabled;
+
+    // Evaluate an unlimited strength algorithm to determine if we support the capability we have on the system
+    static {
+        try {
+            isUnlimitedStrengthCryptographyEnabled = (Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH);
+        } catch (NoSuchAlgorithmException e) {
+            // if there are issues with this, we default back to the value established
+            isUnlimitedStrengthCryptographyEnabled = false;
+        }
+    }
+
+    public KeyedEncryptor(final EncryptionMethod encryptionMethod, final SecretKey key) {
+        this(encryptionMethod, key == null ? new byte[0] : key.getEncoded(), new byte[0]);
+    }
+
+    public KeyedEncryptor(final EncryptionMethod encryptionMethod, final SecretKey key, final byte[] iv) {
+        this(encryptionMethod, key == null ? new byte[0] : key.getEncoded(), iv);
+    }
+
+    public KeyedEncryptor(final EncryptionMethod encryptionMethod, final byte[] keyBytes) {
+        this(encryptionMethod, keyBytes, new byte[0]);
+    }
+
+    public KeyedEncryptor(final EncryptionMethod encryptionMethod, final byte[] keyBytes, final byte[] iv) {
+        super();
+        try {
+            if (encryptionMethod == null) {
+                throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with null encryption method");
+            }
+            if (!encryptionMethod.isKeyedCipher()) {
+                throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with encryption method " + encryptionMethod.name());
+            }
+            this.encryptionMethod = encryptionMethod;
+            if (keyBytes == null || keyBytes.length == 0) {
+                throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with empty key");
+            }
+            if (!CipherUtility.isValidKeyLengthForAlgorithm(keyBytes.length * 8, encryptionMethod.getAlgorithm())) {
+                throw new IllegalArgumentException("Cannot instantiate a keyed encryptor with key of length " + keyBytes.length);
+            }
+            String cipherName = CipherUtility.parseCipherFromAlgorithm(encryptionMethod.getAlgorithm());
+            this.key = new SecretKeySpec(keyBytes, cipherName);
+
+            this.iv = iv;
+        } catch (Exception e) {
+            throw new ProcessException(e);
+        }
+    }
+
+    public static int getMaxAllowedKeyLength(final String algorithm) {
+        if (StringUtils.isEmpty(algorithm)) {
+            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
+        }
+        String parsedCipher = CipherUtility.parseCipherFromAlgorithm(algorithm);
+        try {
+            return Cipher.getMaxAllowedKeyLength(parsedCipher);
+        } catch (NoSuchAlgorithmException e) {
+            // Default algorithm max key length on unmodified JRE
+            return DEFAULT_MAX_ALLOWED_KEY_LENGTH;
+        }
+    }
+
+    public static boolean supportsUnlimitedStrength() {
+        return isUnlimitedStrengthCryptographyEnabled;
+    }
+
+    @Override
+    public StreamCallback getEncryptionCallback() throws ProcessException {
+        return new EncryptCallback();
+    }
+
+    @Override
+    public StreamCallback getDecryptionCallback() throws ProcessException {
+        return new DecryptCallback();
+    }
+
+    private class DecryptCallback implements StreamCallback {
+
+        public DecryptCallback() {
+        }
+
+        @Override
+        public void process(final InputStream in, final OutputStream out) throws IOException {
+            // Initialize cipher provider
+            KeyedCipherProvider cipherProvider = (KeyedCipherProvider) CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NONE);
+
+            // Generate cipher
+            try {
+                Cipher cipher;
+                // The IV could have been set by the constructor, but if not, read from the cipher stream
+                if (iv.length == 0) {
+                    iv = cipherProvider.readIV(in);
+                }
+                cipher = cipherProvider.getCipher(encryptionMethod, key, iv, false);
+                CipherUtility.processStreams(cipher, in, out);
+            } catch (Exception e) {
+                throw new ProcessException(e);
+            }
+        }
+    }
+
+    private class EncryptCallback implements StreamCallback {
+
+        public EncryptCallback() {
+        }
+
+        @Override
+        public void process(final InputStream in, final OutputStream out) throws IOException {
+            // Initialize cipher provider
+            KeyedCipherProvider cipherProvider = (KeyedCipherProvider) CipherProviderFactory.getCipherProvider(KeyDerivationFunction.NONE);
+
+            // Generate cipher
+            try {
+                Cipher cipher = cipherProvider.getCipher(encryptionMethod, key, iv, true);
+                cipherProvider.writeIV(cipher.getIV(), out);
+                CipherUtility.processStreams(cipher, in, out);
+            } catch (Exception e) {
+                throw new ProcessException(e);
+            }
+        }
+    }
+}
\ No newline at end of file