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:39 UTC

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

NIFI-1257 NIFI-1259

Added utility method to return the maximum acceptable password length for PBE ciphers on JVM with limited strength crypto because BC implementation is undocumented (based on empirical evidence).
Updated EncryptionMethod definitions to accurately reflect need for unlimited strength crypto according to algorithm key length.
Added processor logic to invoke keyed cipher.
Added EncryptContent processor property for raw hex key (always visible until NIFI-1121).
Added validations for KDF (keyed and PBE) and hex key.
Added utility method to return list of valid key lengths for algorithm.
Added description to allowable values for KDF and encryption method in EncryptContent processor.
Added IV read/write to KeyedCipherProvider and changed from interface to abstract class.
Added salt read/write logic to NifiLegacy and OpenSSL cipher providers.
Changed RandomIVPBECipherProvider from interface to abstract class.
Updated strong KDF implementations.
Renamed CipherFactory to CipherProviderFactory.
Added unit test for registered KDF resolution from factory.
Updated default iteration count for PBKDF2 cipher provider.
Implemented Scrypt cipher provider.
Added salt translator from mcrypt format to Java format.
Added unit tests for salt formatting and validation.
Added surefire block to groovy unit test profile to enforce 3072 MB heap for Scrypt test.
Added local Java implementation of Scrypt KDF (and underlying PBKDF2 KDF) from Will Glozer.
Defined interface for KeyedCipherProvider.
Implemented AES implementation for KeyedCipherProvider.
Added Ruby script to test/resources for external compatibility check.
Added key length check to PBKDF2 cipher provider.
Changed default PRF to SHA-512.
Added salt and key length check to PBKDF2 cipher provider.
Added utility method to check key length validity for cipher families.
Added Bcrypt implementation.
Implemented PBKDF2 cipher provider.
Added default constructor with strong choices for PBKDF2 cipher provider.
Implemented NiFiLegacyCipherProvider and added unit tests.
Added key length parameter to PBKDF2 cipher provider.
Added PRF resolution to PBKDF2 cipher provider.
Added RandomIVPBECipherProvider to allow for non-deterministic IVs.
Added new keyed encryption methods and added boolean field for compatibility with new KDFs.
Added CipherFactory.
Improved Javadoc in NiFi legacy cipher provider and OpenSSL cipher provider.
Added KeyedCipherProvider interface.
Added OpenSSL PKCS#5 v1.5 EVP_BytesToKey cipher provider and unit test.

This closes #201.

Signed-off-by: Aldrin Piri <al...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/498b5023
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/498b5023
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/498b5023

Branch: refs/heads/master
Commit: 498b5023ce4f99e67184f399de740b142fca510d
Parents: 0690aee
Author: Andy LoPresto <al...@gmail.com>
Authored: Tue Dec 22 10:12:02 2015 -0800
Committer: Aldrin Piri <al...@apache.org>
Committed: Thu Feb 4 19:40:55 2016 -0500

----------------------------------------------------------------------
 LICENSE                                         |  16 +
 nifi-assembly/LICENSE                           |  20 +-
 .../nifi/security/util/EncryptionMethod.java    |  70 +-
 .../security/util/KeyDerivationFunction.java    |  13 +-
 .../src/main/asciidoc/administration-guide.adoc | 197 ++++-
 .../main/asciidoc/images/allow-weak-crypto.png  | Bin 0 -> 130449 bytes
 .../src/main/asciidoc/images/bcrypt-salt.png    | Bin 0 -> 159570 bytes
 .../main/asciidoc/images/nifi-legacy-salt.png   | Bin 0 -> 112990 bytes
 .../src/main/asciidoc/images/openssl-salt.png   | Bin 0 -> 119807 bytes
 .../src/main/asciidoc/images/pbkdf2-salt.png    | Bin 0 -> 143367 bytes
 .../src/main/asciidoc/images/scrypt-salt.png    | Bin 0 -> 160684 bytes
 .../nifi-web/nifi-web-security/pom.xml          |   2 +-
 .../src/main/resources/META-INF/LICENSE         |  16 +
 .../nifi-standard-processors/pom.xml            |  31 +
 .../processors/standard/EncryptContent.java     | 423 +++++++---
 .../standard/util/OpenPGPKeyBasedEncryptor.java | 380 ---------
 .../util/OpenPGPPasswordBasedEncryptor.java     | 158 ----
 .../standard/util/PasswordBasedEncryptor.java   | 266 -------
 .../util/crypto/AESKeyedCipherProvider.java     | 153 ++++
 .../util/crypto/BcryptCipherProvider.java       | 198 +++++
 .../standard/util/crypto/CipherProvider.java    |  23 +
 .../util/crypto/CipherProviderFactory.java      |  57 ++
 .../standard/util/crypto/CipherUtility.java     | 320 ++++++++
 .../util/crypto/KeyedCipherProvider.java        |  73 ++
 .../standard/util/crypto/KeyedEncryptor.java    | 163 ++++
 .../util/crypto/NiFiLegacyCipherProvider.java   | 112 +++
 .../util/crypto/OpenPGPKeyBasedEncryptor.java   | 380 +++++++++
 .../crypto/OpenPGPPasswordBasedEncryptor.java   | 158 ++++
 .../util/crypto/OpenSSLPKCS5CipherProvider.java | 211 +++++
 .../standard/util/crypto/PBECipherProvider.java |  84 ++
 .../util/crypto/PBKDF2CipherProvider.java       | 218 +++++
 .../util/crypto/PasswordBasedEncryptor.java     | 182 +++++
 .../util/crypto/RandomIVPBECipherProvider.java  |  71 ++
 .../util/crypto/ScryptCipherProvider.java       | 306 +++++++
 .../standard/util/crypto/bcrypt/BCrypt.java     | 789 +++++++++++++++++++
 .../standard/util/crypto/scrypt/Scrypt.java     | 509 ++++++++++++
 .../standard/TestEncryptContentGroovy.groovy    | 479 +++++++++++
 .../AESKeyedCipherProviderGroovyTest.groovy     | 340 ++++++++
 .../BcryptCipherProviderGroovyTest.groovy       | 549 +++++++++++++
 .../CipherProviderFactoryGroovyTest.groovy      |  97 +++
 .../util/crypto/CipherUtilityGroovyTest.groovy  | 251 ++++++
 .../util/crypto/KeyedEncryptorGroovyTest.groovy | 122 +++
 .../NiFiLegacyCipherProviderGroovyTest.groovy   | 288 +++++++
 .../OpenSSLPKCS5CipherProviderGroovyTest.groovy | 319 ++++++++
 .../PBKDF2CipherProviderGroovyTest.groovy       | 540 +++++++++++++
 .../PasswordBasedEncryptorGroovyTest.groovy     | 164 ++++
 .../ScryptCipherProviderGroovyTest.groovy       | 608 ++++++++++++++
 .../util/crypto/scrypt/ScryptGroovyTest.groovy  | 399 ++++++++++
 .../processors/standard/TestEncryptContent.java |  35 +-
 .../util/OpenPGPKeyBasedEncryptorTest.java      | 132 ----
 .../util/OpenPGPPasswordBasedEncryptorTest.java | 123 ---
 .../crypto/OpenPGPKeyBasedEncryptorTest.java    | 132 ++++
 .../OpenPGPPasswordBasedEncryptorTest.java      | 123 +++
 .../TestEncryptContent/salted_128_raw.enc       |   1 +
 .../TestEncryptContent/unsalted_128_raw.enc     | Bin 0 -> 32 bytes
 .../src/test/resources/logback-test.xml         |  13 +-
 .../src/test/resources/openssl_aes.rb           |  46 ++
 .../src/test/resources/openssl_bcrypt.rb        |  62 ++
 .../src/test/resources/openssl_pbkdf2.rb        |  52 ++
 .../src/test/resources/openssl_scrypt.rb        |  58 ++
 60 files changed, 9302 insertions(+), 1230 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index f4be753..3ff4503 100644
--- a/LICENSE
+++ b/LICENSE
@@ -500,3 +500,19 @@ This product bundles HexViewJS available under an MIT License
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+This product bundles 'jBCrypt' which is available under a BSD license.
+For details see https://github.com/svenkubiak/jBCrypt/blob/0.4.1/LICENSE
+
+    Copyright (c) 2006 Damien Miller <dj...@mindrot.org>
+
+    Permission to use, copy, modify, and distribute this software for any
+    purpose with or without fee is hereby granted, provided that the above
+    copyright notice and this permission notice appear in all copies.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-assembly/LICENSE
----------------------------------------------------------------------
diff --git a/nifi-assembly/LICENSE b/nifi-assembly/LICENSE
index 6aef7ed..bb3d435 100644
--- a/nifi-assembly/LICENSE
+++ b/nifi-assembly/LICENSE
@@ -1086,7 +1086,6 @@ information can be found here: http://www.adobe.com/devnet/xmp/library/eula-xmp-
    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
    THE POSSIBILITY OF SUCH DAMAGE.
 
-
 This product bundles 'Jsoup' which is available under "The MIT license". More
 information can be found here: http://jsoup.org/license
 
@@ -1133,4 +1132,21 @@ information can be found here:
     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-    THE SOFTWARE.
\ No newline at end of file
+    THE SOFTWARE.
+
+This product bundles 'jBCrypt' which is available under a BSD license.
+For details see https://github.com/svenkubiak/jBCrypt/blob/0.4.1/LICENSE
+
+    Copyright (c) 2006 Damien Miller <dj...@mindrot.org>
+
+    Permission to use, copy, modify, and distribute this software for any
+    purpose with or without fee is hereby granted, provided that the above
+    copyright notice and this permission notice appear in all copies.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java
index ce0c50e..53664f1 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/EncryptionMethod.java
@@ -26,37 +26,43 @@ import org.apache.commons.lang3.builder.ToStringStyle;
  */
 public enum EncryptionMethod {
 
-    MD5_128AES("PBEWITHMD5AND128BITAES-CBC-OPENSSL", "BC", false),
-    MD5_192AES("PBEWITHMD5AND192BITAES-CBC-OPENSSL", "BC", false),
-    MD5_256AES("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC", false),
-    MD5_DES("PBEWITHMD5ANDDES", "BC", false),
-    MD5_RC2("PBEWITHMD5ANDRC2", "BC", false),
-    SHA1_RC2("PBEWITHSHA1ANDRC2", "BC", false),
-    SHA1_DES("PBEWITHSHA1ANDDES", "BC", false),
-    SHA_128AES("PBEWITHSHAAND128BITAES-CBC-BC", "BC", true),
-    SHA_192AES("PBEWITHSHAAND192BITAES-CBC-BC", "BC", true),
-    SHA_256AES("PBEWITHSHAAND256BITAES-CBC-BC", "BC", true),
-    SHA_40RC2("PBEWITHSHAAND40BITRC2-CBC", "BC", true),
-    SHA_128RC2("PBEWITHSHAAND128BITRC2-CBC", "BC", true),
-    SHA_40RC4("PBEWITHSHAAND40BITRC4", "BC", true),
-    SHA_128RC4("PBEWITHSHAAND128BITRC4", "BC", true),
-    SHA256_128AES("PBEWITHSHA256AND128BITAES-CBC-BC", "BC", true),
-    SHA256_192AES("PBEWITHSHA256AND192BITAES-CBC-BC", "BC", true),
-    SHA256_256AES("PBEWITHSHA256AND256BITAES-CBC-BC", "BC", true),
-    SHA_2KEYTRIPLEDES("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "BC", true),
-    SHA_3KEYTRIPLEDES("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "BC", true),
-    SHA_TWOFISH("PBEWITHSHAANDTWOFISH-CBC", "BC", true),
-    PGP("PGP", "BC", false),
-    PGP_ASCII_ARMOR("PGP-ASCII-ARMOR", "BC", false);
+    MD5_128AES("PBEWITHMD5AND128BITAES-CBC-OPENSSL", "BC", false, false),
+    MD5_192AES("PBEWITHMD5AND192BITAES-CBC-OPENSSL", "BC", true, false),
+    MD5_256AES("PBEWITHMD5AND256BITAES-CBC-OPENSSL", "BC", true, false),
+    MD5_DES("PBEWITHMD5ANDDES", "BC", false, false),
+    MD5_RC2("PBEWITHMD5ANDRC2", "BC", false, false),
+    SHA1_RC2("PBEWITHSHA1ANDRC2", "BC", false, false),
+    SHA1_DES("PBEWITHSHA1ANDDES", "BC", false, false),
+    SHA_128AES("PBEWITHSHAAND128BITAES-CBC-BC", "BC", false, false),
+    SHA_192AES("PBEWITHSHAAND192BITAES-CBC-BC", "BC", true, false),
+    SHA_256AES("PBEWITHSHAAND256BITAES-CBC-BC", "BC", true, false),
+    SHA_40RC2("PBEWITHSHAAND40BITRC2-CBC", "BC", false, false),
+    SHA_128RC2("PBEWITHSHAAND128BITRC2-CBC", "BC", false, false),
+    SHA_40RC4("PBEWITHSHAAND40BITRC4", "BC", false, false),
+    SHA_128RC4("PBEWITHSHAAND128BITRC4", "BC", false, false),
+    SHA256_128AES("PBEWITHSHA256AND128BITAES-CBC-BC", "BC", false, false),
+    SHA256_192AES("PBEWITHSHA256AND192BITAES-CBC-BC", "BC", true, false),
+    SHA256_256AES("PBEWITHSHA256AND256BITAES-CBC-BC", "BC", true, false),
+    SHA_2KEYTRIPLEDES("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", "BC", false, false),
+    SHA_3KEYTRIPLEDES("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "BC", false, false),
+    SHA_TWOFISH("PBEWITHSHAANDTWOFISH-CBC", "BC", false, false),
+    PGP("PGP", "BC", false, false),
+    PGP_ASCII_ARMOR("PGP-ASCII-ARMOR", "BC", false, false),
+    // New encryption methods which used keyed encryption
+    AES_CBC("AES/CBC/PKCS7Padding", "BC", false, true),
+    AES_CTR("AES/CTR/NoPadding", "BC", false, true),
+    AES_GCM("AES/GCM/NoPadding", "BC", false, true);
 
     private final String algorithm;
     private final String provider;
     private final boolean unlimitedStrength;
+    private final boolean compatibleWithStrongKDFs;
 
-    EncryptionMethod(String algorithm, String provider, boolean unlimitedStrength) {
+    EncryptionMethod(String algorithm, String provider, boolean unlimitedStrength, boolean compatibleWithStrongKDFs) {
         this.algorithm = algorithm;
         this.provider = provider;
         this.unlimitedStrength = unlimitedStrength;
+        this.compatibleWithStrongKDFs = compatibleWithStrongKDFs;
     }
 
     public String getProvider() {
@@ -74,13 +80,29 @@ public enum EncryptionMethod {
         return unlimitedStrength;
     }
 
+    /**
+     * @return true if algorithm is compatible with strong {@link KeyDerivationFunction}s
+     */
+    public boolean isCompatibleWithStrongKDFs() {
+        return compatibleWithStrongKDFs;
+    }
+
+    /**
+     * @return true if this algorithm does not rely on its own internal key derivation process
+     */
+    public boolean isKeyedCipher() {
+        return !algorithm.startsWith("PBE") && !algorithm.startsWith("PGP");
+    }
+
     @Override
     public String toString() {
         final ToStringBuilder builder = new ToStringBuilder(this);
         ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE);
-        builder.append("algorithm name", algorithm);
+        builder.append("Algorithm name", algorithm);
         builder.append("Requires unlimited strength JCE policy", unlimitedStrength);
         builder.append("Algorithm Provider", provider);
+        builder.append("Compatible with strong KDFs", compatibleWithStrongKDFs);
+        builder.append("Keyed cipher", isKeyedCipher());
         return builder.toString();
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyDerivationFunction.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyDerivationFunction.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyDerivationFunction.java
index f8b4731..48ea2d4 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyDerivationFunction.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyDerivationFunction.java
@@ -24,9 +24,12 @@ import org.apache.commons.lang3.builder.ToStringStyle;
  */
 public enum KeyDerivationFunction {
 
-    NIFI_LEGACY("NiFi legacy KDF", "MD5 @ 1000 iterations"),
-    OPENSSL_EVP_BYTES_TO_KEY("OpenSSL EVP_BytesToKey", "Single iteration MD5 compatible with PKCS#5 v1.5");
-    // TODO: Implement bcrypt, scrypt, and PBKDF2
+    NIFI_LEGACY("NiFi Legacy KDF", "MD5 @ 1000 iterations"),
+    OPENSSL_EVP_BYTES_TO_KEY("OpenSSL EVP_BytesToKey", "Single iteration MD5 compatible with PKCS#5 v1.5"),
+    BCRYPT("Bcrypt", "Bcrypt with configurable work factor. See Admin Guide"),
+    SCRYPT("Scrypt", "Scrypt with configurable cost parameters. See Admin Guide"),
+    PBKDF2("PBKDF2", "PBKDF2 with configurable hash function and iteration count. See Admin Guide"),
+    NONE("None", "The cipher is given a raw key conforming to the algorithm specifications");
 
     private final String name;
     private final String description;
@@ -44,6 +47,10 @@ public enum KeyDerivationFunction {
         return description;
     }
 
+    public boolean isStrongKDF() {
+        return (name.equals(BCRYPT.name) || name.equals(SCRYPT.name) || name.equals(PBKDF2.name));
+    }
+
     @Override
     public String toString() {
         final ToStringBuilder builder = new ToStringBuilder(this);

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/administration-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 830d542..5987934 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -366,6 +366,199 @@ a Remote Process Group. In that scenario, all the nodes
 in the remote cluster can be included in the same group. When the ADMIN wants to grant port access to the remote
 cluster, s/he can grant it to the group and avoid having to grant it individually to each node in the cluster.
 
+[[encryption]]
+Encryption Configuration
+------------------------
+
+This section provides an overview of the capabilities of NiFi to encrypt and decrypt data. 
+
+The `EncryptContent` processor allows for the encryption and decryption of data, both internal to NiFi and integrated with external systems, such as `openssl` and other data sources and consumers.
+
+[[key-derivation-functions]]
+Key Derivation Functions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Key Derivation Functions (KDF) are mechanisms by which human-readable information, usually a password or other secret information, is translated into a cryptographic key suitable for data protection. For further information, read https://en.wikipedia.org/wiki/Key_derivation_function[the Wikipedia entry on Key Derivation Functions].
+Currently, KDFs are ingested by `CipherProvider` implementations and return a fully-initialized `Cipher` object to be used for encryption or decryption. Due to the use of a `CipherProviderFactory`, the KDFs are not customizable at this time. Future enhancements will include the ability to provide custom cost parameters to the KDF at initialization time. As a work-around, `CipherProvider` instances can be initialized with custom cost parameters in the constructor but this is not currently supported by the `CipherProviderFactory`.
+Here are the KDFs currently supported by NiFi (primarily in the `EncryptContent` processor for password-based encryption (PBE)) and relevant notes:
+
+* NiFi Legacy KDF
+** The original KDF used by NiFi for internal key derivation for PBE, this is 1000 iterations of the MD5 digest over the concatenation of the password and 16 bytes of random salt. 
+** This KDF is *deprecated as of NiFi 0.5.0* and should only be used for backwards compatibility to decrypt data that was previously encrypted by a legacy version of NiFi.
+* OpenSSL PKCS#5 v1.5 EVP_BytesToKey
+** This KDF was added in v0.4.0.
+** This KDF is provided for compatibility with data encrypted using OpenSSL's default PBE, known as `EVP_BytesToKey`. This is a single iteration of MD5 over the concatenation of the password and 8 bytes of random ASCII salt. OpenSSL recommends using `PBKDF2` for key derivation but does not expose the library method necessary to the command-line tool, so this KDF is still the de facto default for command-line encryption.
+* Bcrypt
+** This KDF was added in v0.5.0.
+** https://en.wikipedia.org/wiki/Bcrypt[Bcrypt] is an adaptive function based on the https://en.wikipedia.org/wiki/Blowfish_(cipher)[Blowfish] cipher. This KDF is strongly recommended as it automatically incorporates a random 16 byte salt, configurable cost parameter (or "work factor"), and is hardened against brute-force attacks using https://en.wikipedia.org/wiki/General-purpose_computing_on_graphics_processing_units[GPGPU] (which share memory between cores) by requiring access to "large" blocks of memory during the key derivation. It is less resistant to https://en.wikipedia.org/wiki/Field-programmable_gate_array[FPGA] brute-force attacks where the gate arrays have access to individual embedded RAM blocks.
+** Because the length of a Bcrypt-derived key is always 184 bits, the complete output is then fed to a `SHA-512` digest and truncated to the desired key length. This provides the benefit of the avalanche effect on the formatted input.
+** The recommended minimum work factor is 12 (2^12^ key derivation rounds) (as of 2/1/2016 on commodity hardware) and should be increased to the threshold at which legitimate systems will encounter detrimental delays (see schedule below or use `BcryptCipherProviderGroovyTest#testDefaultConstructorShouldProvideStrongWorkFactor()` to calculate safe minimums).
+** The salt format is `$2a$10$ABCDEFGHIJKLMNOPQRSTUV`. The salt is delimited by `$` and the three sections are as follows:
+*** `2a` - the version of the format. An extensive explanation can be found http://blog.ircmaxell.com/2012/12/seven-ways-to-screw-up-bcrypt.html[here]. NiFi currently uses `2a` for all salts generated internally.
+*** `10` - the work factor. This is actually the log~2~ value, so the total iteration count would be 2^10^ in this case.
+*** `ABCDEFGHIJKLMNOPQRSTUV` - the 22 character, Base64-encoded, unpadded, raw salt value. This decodes to a 16 byte salt used in the key derivation.
+* Scrypt
+** This KDF was added in v0.5.0.
+** https://en.wikipedia.org/wiki/Scrypt[Scrypt] is an adaptive function designed in response to `bcrypt`. This KDF is recommended as it requires relatively large amounts of memory for each derivation, making it resistant to hardware brute-force attacks.
+** The recommended minimum cost is `N`=2^14^, `r`=8, `p`=1 (as of 2/1/2016 on commodity hardware) and should be increased to the threshold at which legitimate systems will encounter detrimental delays (see schedule below or use `ScryptCipherProviderGroovyTest#testDefaultConstructorShouldProvideStrongParameters()` to calculate safe minimums).
+** The salt format is `$s0$e0101$ABCDEFGHIJKLMNOPQRSTUV`. The salt is delimited by `$` and the three sections are as follows:
+*** `s0` - the version of the format. NiFi currently uses `s0` for all salts generated internally.
+*** `e0101` - the cost parameters. This is actually a hexadecimal encoding of `N`, `r`, `p` using shifts. This can be formed/parsed using `Scrypt#encodeParams()` and `Scrypt#parseParameters()`.
+**** Some external libraries encode `N`, `r`, and `p` separately in the form `$400$1$1$`. A utility method is available at `ScryptCipherProvider#translateSalt()` which will convert the external form to the internal form.
+*** `ABCDEFGHIJKLMNOPQRSTUV` - the 11-44 character, Base64-encoded, unpadded, raw salt value. This decodes to a 8-32 byte salt used in the key derivation.
+* PBKDF2
+** This KDF was added in v0.5.0.
+** https://en.wikipedia.org/wiki/PBKDF2[Password-Based Key Derivation Function 2] is an adaptive derivation function which uses an internal pseudorandom function (PRF) and iterates it many times over a password and salt (at least 16 bytes).
+** The PRF is recommended to be `HMAC/SHA-256` or `HMAC/SHA-512`. The use of an HMAC cryptographic hash function mitigates a length extension attack.
+** The recommended minimum number of iterations is 160,000 (as of 2/1/2016 on commodity hardware). This number should be doubled every two years (see schedule below or use `PBKDF2CipherProviderGroovyTest#testDefaultConstructorShouldProvideStrongIterationCount()` to calculate safe minimums).
+** This KDF is not memory-hard (can be parallelized massively with commodity hardware) but is still recommended as sufficient by http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf[NIST SP 800-132 (PDF)] and many cryptographers (when used with a proper iteration count and HMAC cryptographic hash function).
+* None
+** This KDF was added in v0.5.0.
+** This KDF performs no operation on the input and is a marker to indicate the raw key is provided to the cipher. The key must be provided in hexadecimal encoding and be of a valid length for the associated cipher/algorithm.
+
+Additional Resources
+^^^^^^^^^^^^^^^^^^^^
+
+* http://stackoverflow.com/a/30308723/70465[Explanation of optimal scrypt cost parameters and relationships]
+* http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf[NIST Special Publication 800-132]
+* https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet#Work_Factor[OWASP Password Storage Work Factor Calculations]
+* http://security.stackexchange.com/a/3993/16485[PBKDF2 rounds calculations]
+* http://blog.ircmaxell.com/2014/03/why-i-dont-recommend-scrypt.html[Scrypt as KDF vs password storage vulnerabilities]
+* http://security.stackexchange.com/a/26253/16485[Scrypt vs. Bcrypt (as of 2010)]
+* http://security.stackexchange.com/a/6415/16485[Bcrypt vs PBKDF2]
+* http://wildlyinaccurate.com/bcrypt-choosing-a-work-factor/[Choosing a work factor for Bcrypt]
+* https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/crypto/bcrypt/BCrypt.html[Spring Security Bcrypt]
+* https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html[OpenSSL EVP BytesToKey PKCS#1v1.5]
+* https://www.openssl.org/docs/manmaster/crypto/PKCS5_PBKDF2_HMAC.html[OpenSSL PBKDF2 KDF]
+* http://security.stackexchange.com/a/29139/16485[OpenSSL KDF flaws description]
+
+Salt and IV Encoding
+~~~~~~~~~~~~~~~~~~~~
+
+Initially, the `EncryptContent` processor had a single method of deriving the encryption key from a user-provided password. This is now referred to as `NiFiLegacy` mode, effectively `MD5 digest, 1000 iterations`. In v0.4.0, another method of deriving the key, `OpenSSL PKCS#5 v1.5 EVP_BytesToKey` was added for compatibility with content encrypted outside of NiFi using the `openssl` command-line tool. Both of these <<key-derivation-functions, Key Derivation Functions>> (KDF) had hard-coded digest functions and iteration counts, and the salt format was also hard-coded. With v0.5.0, additional KDFs are introduced with variable iteration counts, work factors, and salt formats. In addition, _raw keyed encryption_ was also introduced. This required the capacity to encode arbitrary salts and Initialization Vectors (IV) into the cipher stream in order to be recovered by NiFi or a follow-on system to decrypt these messages.
+
+For the existing KDFs, the salt format has not changed.
+
+NiFi Legacy
+^^^^^^^^^^^
+
+The first 16 bytes of the input are the salt. On decryption, the salt is read in and combined with the password to derive the encryption key and IV.
+
+image:nifi-legacy-salt.png["NiFi Legacy Salt Encoding"]
+
+OpenSSL PKCS#5 v1.5 EVP_BytesToKey
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+OpenSSL allows for salted or unsalted key derivation. _*Unsalted key derivation is a security risk and is not recommended.*_ If a salt is present, the first 8 bytes of the input are the ASCII string "`Salted__`" (`0x53 61 6C 74 65 64 5F 5F`) and the next 8 bytes are the ASCII-encoded salt. On decryption, the salt is read in and combined with the password to derive the encryption key and IV. If there is no salt header, the entire input is considered to be the cipher text.
+
+image:openssl-salt.png["OpenSSL Salt Encoding"]
+
+For new KDFs, each of which allow for non-deterministic IVs, the IV must be stored alongside the cipher text. This is not a vulnerability, as the IV is not required to be secret, but simply to be unique for messages encrypted using the same key to reduce the success of cryptographic attacks. For these KDFs, the output consists of the salt, followed by the salt delimiter, UTF-8 string "`NiFiSALT`" (`0x4E 69 46 69 53 41 4C 54`) and then the IV, followed by the IV delimiter, UTF-8 string "`NiFiIV`" (`0x4E 69 46 69 49 56`), followed by the cipher text.
+
+Bcrypt, Scrypt, PBKDF2
+^^^^^^^^^^^^^^^^^^^^^^
+
+image:bcrypt-salt.png["Bcrypt Salt & IV Encoding"]
+
+image:scrypt-salt.png["Scrypt Salt & IV Encoding"]
+
+image:pbkdf2-salt.png["PBKDF2 Salt & IV Encoding"]
+
+Java Cryptography Extension (JCE) Limited Strength Jurisdiction Policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Because of US export regulations, default JVMs have http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#importlimits[limits imposed on the strength of cryptographic operations] available to them. For example, AES operations are limited to `128 bit keys` by default. While `AES-128` is cryptographically safe, this can have unintended consequences, specifically on Password-based Encryption (PBE).
+
+PBE is the process of deriving a cryptographic key for encryption or decryption from _user-provided secret material_, usually a password. Rather than a human remembering a (random-appearing) 32 or 64 character hexadecimal string, a password or passphrase is used.
+
+A number of PBE algorithms provided by NiFi impose strict limits on the length of the password due to the underlying key length checks. Below is a table listing the maximum password length on a JVM with limited cryptographic strength.
+
+.Maximum Password Length on Limited Cryptographic Strength JVM
+|===
+|Algorithm |Max Password Length
+
+|`PBEWITHMD5AND128BITAES-CBC-OPENSSL`
+|16
+
+|`PBEWITHMD5AND192BITAES-CBC-OPENSSL`
+|16
+
+|`PBEWITHMD5AND256BITAES-CBC-OPENSSL`
+|16
+
+|`PBEWITHMD5ANDDES`
+|16
+
+|`PBEWITHMD5ANDRC2`
+|16
+
+|`PBEWITHSHA1ANDRC2`
+|16
+
+|`PBEWITHSHA1ANDDES`
+|16
+
+|`PBEWITHSHAAND128BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHAAND192BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHAAND256BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHAAND40BITRC2-CBC`
+|7
+
+|`PBEWITHSHAAND128BITRC2-CBC`
+|7
+
+|`PBEWITHSHAAND40BITRC4`
+|7
+
+|`PBEWITHSHAAND128BITRC4`
+|7
+
+|`PBEWITHSHA256AND128BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHA256AND192BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHA256AND256BITAES-CBC-BC`
+|7
+
+|`PBEWITHSHAAND2-KEYTRIPLEDES-CBC`
+|7
+
+|`PBEWITHSHAAND3-KEYTRIPLEDES-CBC`
+|7
+
+|`PBEWITHSHAANDTWOFISH-CBC`
+|7
+|===
+
+Allow Insecure Cryptographic Modes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the `Allow Insecure Cryptographic Modes` property in `EncryptContent` processor settings is set to `not-allowed`. This means that if a password of fewer than `10` characters is provided, a validation error will occur. 10 characters is a conservative estimate and does not take into consideration full entropy calculations, patterns, etc.
+
+image:allow-weak-crypto.png["Allow Insecure Cryptographic Modes", width=940]
+
+On a JVM with limited strength cryptography, some PBE algorithms limit the maximum password length to 7, and in this case it will not be possible to provide a "safe" password. It is recommended to install the JCE Unlimited Strength Jurisdiction Policy files for the JVM to mitigate this issue.
+
+* http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[JCE Unlimited Strength Jurisdiction Policy files for Java 7]
+* http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[JCE Unlimited Strength Jurisdiction Policy files for Java 8]
+
+If on a system where the unlimited strength policies cannot be installed, it is recommended to switch to an algorithm that supports longer passwords (see table above). 
+
+[WARNING]
+.Allowing Weak Crypto
+=====================
+If it is not possible to install the unlimited strength jurisdiction policies, the `Allow Weak Crypto` setting can be changed to `allowed`, but *this is _not_ recommended*. Changing this setting explicitly acknowledges the inherent risk in using weak cryptographic configurations.
+=====================
+
+It is preferable to request upstream/downstream systems to switch to https://cwiki.apache.org/confluence/display/NIFI/Encryption+Information[keyed encryption] or use a "strong" https://cwiki.apache.org/confluence/display/NIFI/Key+Derivation+Function+Explanations[Key Derivation Function (KDF) supported by NiFi].
 
 [[clustering]]
 Clustering Configuration
@@ -806,8 +999,8 @@ Security Configuration section of this Administrator's Guide.
 
 |====
 |*Property*|*Description*
-|nifi.sensitive.props.key|This is the password used to encrypt any sensitive property values that are configured in processors. By default, it is blank, but the system administrator should provide a value for it. It can be a string of any length. Be aware that once this password is set and one or more sensitive processor properties have been configured, this password should not be changed.
-|nifi.sensitive.props.algorithm|The algorithm used to encrypt sensitive properties. The default value is PBEWITHMD5AND256BITAES-CBC-OPENSSL.
+|nifi.sensitive.props.key|This is the password used to encrypt any sensitive property values that are configured in processors. By default, it is blank, but the system administrator should provide a value for it. It can be a string of any length, although the recommended minimum length is 10 characters. Be aware that once this password is set and one or more sensitive processor properties have been configured, this password should not be changed.
+|nifi.sensitive.props.algorithm|The algorithm used to encrypt sensitive properties. The default value is `PBEWITHMD5AND256BITAES-CBC-OPENSSL`.
 |nifi.sensitive.props.provider|The sensitive property provider. The default value is BC.
 |nifi.security.keystore*|The full path and name of the keystore. It is blank by default.
 |nifi.security.keystoreType|The keystore type. It is blank by default.

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/allow-weak-crypto.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/allow-weak-crypto.png b/nifi-docs/src/main/asciidoc/images/allow-weak-crypto.png
new file mode 100644
index 0000000..755f12f
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/allow-weak-crypto.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/bcrypt-salt.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/bcrypt-salt.png b/nifi-docs/src/main/asciidoc/images/bcrypt-salt.png
new file mode 100644
index 0000000..c93b4c1
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/bcrypt-salt.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/nifi-legacy-salt.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/nifi-legacy-salt.png b/nifi-docs/src/main/asciidoc/images/nifi-legacy-salt.png
new file mode 100644
index 0000000..9d2ee36
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/nifi-legacy-salt.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/openssl-salt.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/openssl-salt.png b/nifi-docs/src/main/asciidoc/images/openssl-salt.png
new file mode 100644
index 0000000..a10156a
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/openssl-salt.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/pbkdf2-salt.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/pbkdf2-salt.png b/nifi-docs/src/main/asciidoc/images/pbkdf2-salt.png
new file mode 100644
index 0000000..f47bd8c
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/pbkdf2-salt.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-docs/src/main/asciidoc/images/scrypt-salt.png
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/images/scrypt-salt.png b/nifi-docs/src/main/asciidoc/images/scrypt-salt.png
new file mode 100644
index 0000000..d8f3356
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/scrypt-salt.png differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
index 5496f15..613ad41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
@@ -53,7 +53,7 @@
                 <configuration>
                     <excludes>**/authentication/generated/*.java,</excludes>
                 </configuration>
-            </plugin>            
+            </plugin>
         </plugins>
     </build>
     <dependencies>

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/LICENSE
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/LICENSE b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/LICENSE
index 45a1bcc..bd97dda 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/LICENSE
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-nar/src/main/resources/META-INF/LICENSE
@@ -319,3 +319,19 @@ For details see http://asm.ow2.org/asmdex-license.html
     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     THE POSSIBILITY OF SUCH DAMAGE.
+
+The binary distribution of this product bundles 'jBCrypt' which is available under a BSD license. For details see https://github.com/svenkubiak/jBCrypt/blob/0.4.1/LICENSE
+
+    Copyright (c) 2006 Damien Miller <dj...@mindrot.org>
+
+    Permission to use, copy, modify, and distribute this software for any
+    purpose with or without fee is hereby granted, provided that the above
+    copyright notice and this permission notice appear in all copies.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

http://git-wip-us.apache.org/repos/asf/nifi/blob/498b5023/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
index 2522fee..f841b53 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
@@ -181,6 +181,11 @@ language governing permissions and limitations under the License. -->
             <type>jar</type>
         </dependency>
         <dependency>
+            <groupId>de.svenkubiak</groupId>
+            <artifactId>jBcrypt</artifactId>
+            <version>0.4.1</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-mock</artifactId>
             <scope>test</scope>
@@ -315,9 +320,35 @@ language governing permissions and limitations under the License. -->
                         <exclude>src/test/resources/TestEncryptContent/plain.txt</exclude>
                         <exclude>src/test/resources/TestEncryptContent/salted_raw.enc</exclude>
                         <exclude>src/test/resources/TestEncryptContent/unsalted_raw.enc</exclude>
+                        <!-- This file is copied from https://github.com/jeremyh/jBCrypt because the binary is compiled for Java 8 and we must support Java 7 -->
+                        <exclude>src/main/java/org/apache/nifi/processors/standard/util/crypto/bcrypt/BCrypt.java</exclude>
                     </excludes>
                 </configuration>
             </plugin>
         </plugins>
     </build>
+    <profiles>
+        <profile>
+            <!-- Custom profile for tests requiring large heap operations -->
+            <id>expensive-heap-tests</id>
+            <activation>
+                <property>
+                    <name>heap</name>
+                    <value>expensive</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>2.18</version>
+                        <configuration>
+                            <argLine>-Xmx3072M</argLine>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>

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/EncryptContent.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncryptContent.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncryptContent.java
index 8cad3cb..dceaa8d 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncryptContent.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncryptContent.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.processors.standard;
 
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.annotation.behavior.EventDriven;
 import org.apache.nifi.annotation.behavior.InputRequirement;
@@ -24,6 +26,7 @@ import org.apache.nifi.annotation.behavior.SideEffectFree;
 import org.apache.nifi.annotation.behavior.SupportsBatching;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
@@ -38,14 +41,17 @@ import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.io.StreamCallback;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processors.standard.util.OpenPGPKeyBasedEncryptor;
-import org.apache.nifi.processors.standard.util.OpenPGPPasswordBasedEncryptor;
-import org.apache.nifi.processors.standard.util.PasswordBasedEncryptor;
+import org.apache.nifi.processors.standard.util.crypto.CipherUtility;
+import org.apache.nifi.processors.standard.util.crypto.KeyedEncryptor;
+import org.apache.nifi.processors.standard.util.crypto.OpenPGPKeyBasedEncryptor;
+import org.apache.nifi.processors.standard.util.crypto.OpenPGPPasswordBasedEncryptor;
+import org.apache.nifi.processors.standard.util.crypto.PasswordBasedEncryptor;
 import org.apache.nifi.security.util.EncryptionMethod;
 import org.apache.nifi.security.util.KeyDerivationFunction;
 import org.apache.nifi.util.StopWatch;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
+import java.nio.charset.StandardCharsets;
 import java.security.Security;
 import java.text.Normalizer;
 import java.util.ArrayList;
@@ -67,71 +73,91 @@ public class EncryptContent extends AbstractProcessor {
     public static final String ENCRYPT_MODE = "Encrypt";
     public static final String DECRYPT_MODE = "Decrypt";
 
+    private static final String WEAK_CRYPTO_ALLOWED_NAME = "allowed";
+    private static final String WEAK_CRYPTO_NOT_ALLOWED_NAME = "not-allowed";
+
     public static final PropertyDescriptor MODE = new PropertyDescriptor.Builder()
-        .name("Mode")
-        .description("Specifies whether the content should be encrypted or decrypted")
-        .required(true)
-        .allowableValues(ENCRYPT_MODE, DECRYPT_MODE)
-        .defaultValue(ENCRYPT_MODE)
-        .build();
+            .name("Mode")
+            .description("Specifies whether the content should be encrypted or decrypted")
+            .required(true)
+            .allowableValues(ENCRYPT_MODE, DECRYPT_MODE)
+            .defaultValue(ENCRYPT_MODE)
+            .build();
     public static final PropertyDescriptor KEY_DERIVATION_FUNCTION = new PropertyDescriptor.Builder()
-        .name("key-derivation-function")
-        .displayName("Key Derivation Function")
-        .description("Specifies the key derivation function to generate the key from the password (and salt)")
-        .required(true)
-        .allowableValues(KeyDerivationFunction.values())
-        .defaultValue(KeyDerivationFunction.NIFI_LEGACY.name())
-        .build();
+            .name("key-derivation-function")
+            .displayName("Key Derivation Function")
+            .description("Specifies the key derivation function to generate the key from the password (and salt)")
+            .required(true)
+            .allowableValues(buildKeyDerivationFunctionAllowableValues())
+            .defaultValue(KeyDerivationFunction.NIFI_LEGACY.name())
+            .build();
     public static final PropertyDescriptor ENCRYPTION_ALGORITHM = new PropertyDescriptor.Builder()
-        .name("Encryption Algorithm")
-        .description("The Encryption Algorithm to use")
-        .required(true)
-        .allowableValues(EncryptionMethod.values())
-        .defaultValue(EncryptionMethod.MD5_128AES.name())
-        .build();
+            .name("Encryption Algorithm")
+            .description("The Encryption Algorithm to use")
+            .required(true)
+            .allowableValues(buildEncryptionMethodAllowableValues())
+            .defaultValue(EncryptionMethod.MD5_128AES.name())
+            .build();
     public static final PropertyDescriptor PASSWORD = new PropertyDescriptor.Builder()
-        .name("Password")
-        .description("The Password to use for encrypting or decrypting the data")
-        .required(false)
-        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
-        .sensitive(true)
-        .build();
+            .name("Password")
+            .description("The Password to use for encrypting or decrypting the data")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .sensitive(true)
+            .build();
     public static final PropertyDescriptor PUBLIC_KEYRING = new PropertyDescriptor.Builder()
-        .name("public-keyring-file")
-        .displayName("Public Keyring File")
-        .description("In a PGP encrypt mode, this keyring contains the public key of the recipient")
-        .required(false)
-        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
-        .build();
+            .name("public-keyring-file")
+            .displayName("Public Keyring File")
+            .description("In a PGP encrypt mode, this keyring contains the public key of the recipient")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
     public static final PropertyDescriptor PUBLIC_KEY_USERID = new PropertyDescriptor.Builder()
-        .name("public-key-user-id")
-        .displayName("Public Key User Id")
-        .description("In a PGP encrypt mode, this user id of the recipient")
-        .required(false)
-        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
-        .build();
+            .name("public-key-user-id")
+            .displayName("Public Key User Id")
+            .description("In a PGP encrypt mode, this user id of the recipient")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
     public static final PropertyDescriptor PRIVATE_KEYRING = new PropertyDescriptor.Builder()
-        .name("private-keyring-file")
-        .displayName("Private Keyring File")
-        .description("In a PGP decrypt mode, this keyring contains the private key of the recipient")
-        .required(false)
-        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
-        .build();
+            .name("private-keyring-file")
+            .displayName("Private Keyring File")
+            .description("In a PGP decrypt mode, this keyring contains the private key of the recipient")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
     public static final PropertyDescriptor PRIVATE_KEYRING_PASSPHRASE = new PropertyDescriptor.Builder()
-        .name("private-keyring-passphrase")
-        .displayName("Private Keyring Passphrase")
-        .description("In a PGP decrypt mode, this is the private keyring passphrase")
-        .required(false)
-        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
-        .sensitive(true)
-        .build();
+            .name("private-keyring-passphrase")
+            .displayName("Private Keyring Passphrase")
+            .description("In a PGP decrypt mode, this is the private keyring passphrase")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .sensitive(true)
+            .build();
+    public static final PropertyDescriptor RAW_KEY_HEX = new PropertyDescriptor.Builder()
+            .name("raw-key-hex")
+            .displayName("Raw Key (hexadecimal)")
+            .description("In keyed encryption, this is the raw key, encoded in hexadecimal")
+            .required(false)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .sensitive(true)
+            .build();
+    public static final PropertyDescriptor ALLOW_WEAK_CRYPTO = new PropertyDescriptor.Builder()
+            .name("allow-weak-crypto")
+            .displayName("Allow insecure cryptographic modes")
+            .description("Overrides the default behavior to prevent unsafe combinations of encryption algorithms and short passwords on JVMs with limited strength cryptographic jurisdiction policies")
+            .required(true)
+            .allowableValues(buildWeakCryptoAllowableValues())
+            .defaultValue(buildDefaultWeakCryptoAllowableValue().getValue())
+            .build();
 
     public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success")
-        .description("Any FlowFile that is successfully encrypted or decrypted will be routed to success").build();
-    public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure")
-        .description("Any FlowFile that cannot be encrypted or decrypted will be routed to failure").build();
+            .description("Any FlowFile that is successfully encrypted or decrypted will be routed to success").build();
 
+    public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure")
+            .description("Any FlowFile that cannot be encrypted or decrypted will be routed to failure").build();
     private List<PropertyDescriptor> properties;
+
     private Set<Relationship> relationships;
 
     static {
@@ -139,13 +165,48 @@ public class EncryptContent extends AbstractProcessor {
         Security.addProvider(new BouncyCastleProvider());
     }
 
+    private static AllowableValue[] buildKeyDerivationFunctionAllowableValues() {
+        final KeyDerivationFunction[] keyDerivationFunctions = KeyDerivationFunction.values();
+        List<AllowableValue> allowableValues = new ArrayList<>(keyDerivationFunctions.length);
+        for (KeyDerivationFunction kdf : keyDerivationFunctions) {
+            allowableValues.add(new AllowableValue(kdf.name(), kdf.getName(), kdf.getDescription()));
+        }
+
+        return allowableValues.toArray(new AllowableValue[0]);
+    }
+
+    private static AllowableValue[] buildEncryptionMethodAllowableValues() {
+        final EncryptionMethod[] encryptionMethods = EncryptionMethod.values();
+        List<AllowableValue> allowableValues = new ArrayList<>(encryptionMethods.length);
+        for (EncryptionMethod em : encryptionMethods) {
+            allowableValues.add(new AllowableValue(em.name(), em.name(), em.toString()));
+        }
+
+        return allowableValues.toArray(new AllowableValue[0]);
+    }
+
+    private static AllowableValue[] buildWeakCryptoAllowableValues() {
+        List<AllowableValue> allowableValues = new ArrayList<>();
+        allowableValues.add(new AllowableValue(WEAK_CRYPTO_ALLOWED_NAME, "Allowed", "Operation will not be blocked and no alerts will be presented " +
+                "when unsafe combinations of encryption algorithms and passwords are provided"));
+        allowableValues.add(buildDefaultWeakCryptoAllowableValue());
+        return allowableValues.toArray(new AllowableValue[0]);
+    }
+
+    private static AllowableValue buildDefaultWeakCryptoAllowableValue() {
+        return new AllowableValue(WEAK_CRYPTO_NOT_ALLOWED_NAME, "Not Allowed", "When set, operation will be blocked and alerts will be presented to the user " +
+                "if unsafe combinations of encryption algorithms and passwords are provided on a JVM with limited strength crypto. To fix this, see the Admin Guide.");
+    }
+
     @Override
     protected void init(final ProcessorInitializationContext context) {
         final List<PropertyDescriptor> properties = new ArrayList<>();
         properties.add(MODE);
         properties.add(KEY_DERIVATION_FUNCTION);
         properties.add(ENCRYPTION_ALGORITHM);
+        properties.add(ALLOW_WEAK_CRYPTO);
         properties.add(PASSWORD);
+        properties.add(RAW_KEY_HEX);
         properties.add(PUBLIC_KEYRING);
         properties.add(PUBLIC_KEY_USERID);
         properties.add(PRIVATE_KEYRING);
@@ -180,97 +241,212 @@ public class EncryptContent extends AbstractProcessor {
     protected Collection<ValidationResult> customValidate(final ValidationContext context) {
         final List<ValidationResult> validationResults = new ArrayList<>(super.customValidate(context));
         final String methodValue = context.getProperty(ENCRYPTION_ALGORITHM).getValue();
-        final EncryptionMethod method = EncryptionMethod.valueOf(methodValue);
-        final String algorithm = method.getAlgorithm();
+        final EncryptionMethod encryptionMethod = EncryptionMethod.valueOf(methodValue);
+        final String algorithm = encryptionMethod.getAlgorithm();
         final String password = context.getProperty(PASSWORD).getValue();
-        final String kdf = context.getProperty(KEY_DERIVATION_FUNCTION).getValue();
+        final KeyDerivationFunction kdf = KeyDerivationFunction.valueOf(context.getProperty(KEY_DERIVATION_FUNCTION).getValue());
+        final String keyHex = context.getProperty(RAW_KEY_HEX).getValue();
         if (isPGPAlgorithm(algorithm)) {
-            if (password == null) {
-                final boolean encrypt = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE);
-                if (encrypt) {
-                    // need both public-keyring-file and public-key-user-id set
-                    final String publicKeyring = context.getProperty(PUBLIC_KEYRING).getValue();
-                    final String publicUserId = context.getProperty(PUBLIC_KEY_USERID).getValue();
-                    if (publicKeyring == null || publicUserId == null) {
-                        validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
-                            .explanation(algorithm + " encryption without a " + PASSWORD.getDisplayName() + " requires both "
-                                + PUBLIC_KEYRING.getDisplayName() + " and " + PUBLIC_KEY_USERID.getDisplayName())
+            final boolean encrypt = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE);
+            final String publicKeyring = context.getProperty(PUBLIC_KEYRING).getValue();
+            final String publicUserId = context.getProperty(PUBLIC_KEY_USERID).getValue();
+            final String privateKeyring = context.getProperty(PRIVATE_KEYRING).getValue();
+            final String privateKeyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).getValue();
+            validationResults.addAll(validatePGP(encryptionMethod, password, encrypt, publicKeyring, publicUserId, privateKeyring, privateKeyringPassphrase));
+        } else { // Not PGP
+            if (encryptionMethod.isKeyedCipher()) { // Raw key
+                validationResults.addAll(validateKeyed(encryptionMethod, kdf, keyHex));
+            } else { // PBE
+                boolean allowWeakCrypto = context.getProperty(ALLOW_WEAK_CRYPTO).getValue().equalsIgnoreCase(WEAK_CRYPTO_ALLOWED_NAME);
+                validationResults.addAll(validatePBE(encryptionMethod, kdf, password, allowWeakCrypto));
+            }
+        }
+        return validationResults;
+    }
+
+    private List<ValidationResult> validatePGP(EncryptionMethod encryptionMethod, String password, boolean encrypt, String publicKeyring, String publicUserId, String privateKeyring,
+                                               String privateKeyringPassphrase) {
+        List<ValidationResult> validationResults = new ArrayList<>();
+
+        if (password == null) {
+            if (encrypt) {
+                // If encrypting without a password, require both public-keyring-file and public-key-user-id
+                if (publicKeyring == null || publicUserId == null) {
+                    validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
+                            .explanation(encryptionMethod.getAlgorithm() + " encryption without a " + PASSWORD.getDisplayName() + " requires both "
+                                    + PUBLIC_KEYRING.getDisplayName() + " and " + PUBLIC_KEY_USERID.getDisplayName())
                             .build());
-                    } else {
-                        // verify the public keyring contains the user id
-                        try {
-                            if (OpenPGPKeyBasedEncryptor.getPublicKey(publicUserId, publicKeyring) == null) {
-                                validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
+                } else {
+                    // Verify the public keyring contains the user id
+                    try {
+                        if (OpenPGPKeyBasedEncryptor.getPublicKey(publicUserId, publicKeyring) == null) {
+                            validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
                                     .explanation(PUBLIC_KEYRING.getDisplayName() + " " + publicKeyring
-                                        + " does not contain user id " + publicUserId)
+                                            + " does not contain user id " + publicUserId)
                                     .build());
-                            }
-                        } catch (final Exception e) {
-                            validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
+                        }
+                    } catch (final Exception e) {
+                        validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
                                 .explanation("Invalid " + PUBLIC_KEYRING.getDisplayName() + " " + publicKeyring
-                                    + " because " + e.toString())
+                                        + " because " + e.toString())
                                 .build());
-                        }
                     }
-                } else {
-                    // need both private-keyring-file and private-keyring-passphrase set
-                    final String privateKeyring = context.getProperty(PRIVATE_KEYRING).getValue();
-                    final String keyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).getValue();
-                    if (privateKeyring == null || keyringPassphrase == null) {
-                        validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getName())
-                            .explanation(algorithm + " decryption without a " + PASSWORD.getDisplayName() + " requires both "
-                                + PRIVATE_KEYRING.getDisplayName() + " and " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
+                }
+            } else { // Decrypt
+                // Require both private-keyring-file and private-keyring-passphrase
+                if (privateKeyring == null || privateKeyringPassphrase == null) {
+                    validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getName())
+                            .explanation(encryptionMethod.getAlgorithm() + " decryption without a " + PASSWORD.getDisplayName() + " requires both "
+                                    + PRIVATE_KEYRING.getDisplayName() + " and " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
                             .build());
-                    } else {
-                        final String providerName = EncryptionMethod.valueOf(methodValue).getProvider();
-                        // verify the passphrase works on the private keyring
-                        try {
-                            if (!OpenPGPKeyBasedEncryptor.validateKeyring(providerName, privateKeyring, keyringPassphrase.toCharArray())) {
-                                validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
+                } else {
+                    final String providerName = encryptionMethod.getProvider();
+                    // Verify the passphrase works on the private keyring
+                    try {
+                        if (!OpenPGPKeyBasedEncryptor.validateKeyring(providerName, privateKeyring, privateKeyringPassphrase.toCharArray())) {
+                            validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
                                     .explanation(PRIVATE_KEYRING.getDisplayName() + " " + privateKeyring
-                                        + " could not be opened with the provided " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
+                                            + " could not be opened with the provided " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
                                     .build());
-                            }
-                        } catch (final Exception e) {
-                            validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
+                        }
+                    } catch (final Exception e) {
+                        validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
                                 .explanation("Invalid " + PRIVATE_KEYRING.getDisplayName() + " " + privateKeyring
-                                    + " because " + e.toString())
+                                        + " because " + e.toString())
                                 .build());
-                        }
                     }
                 }
             }
-        } else { // PBE
-            if (!PasswordBasedEncryptor.supportsUnlimitedStrength()) {
-                if (method.isUnlimitedStrength()) {
-                    validationResults.add(new ValidationResult.Builder().subject(ENCRYPTION_ALGORITHM.getName())
-                        .explanation(methodValue + " (" + algorithm + ") is not supported by this JVM due to lacking JCE Unlimited " +
-                            "Strength Jurisdiction Policy files.").build());
-                }
+        }
+
+        return validationResults;
+    }
+
+    private List<ValidationResult> validatePBE(EncryptionMethod encryptionMethod, KeyDerivationFunction kdf, String password, boolean allowWeakCrypto) {
+        List<ValidationResult> validationResults = new ArrayList<>();
+        boolean limitedStrengthCrypto = !PasswordBasedEncryptor.supportsUnlimitedStrength();
+
+        // Password required (short circuits validation because other conditions depend on password presence)
+        if (StringUtils.isEmpty(password)) {
+            validationResults.add(new ValidationResult.Builder().subject(PASSWORD.getName())
+                    .explanation(PASSWORD.getDisplayName() + " is required when using algorithm " + encryptionMethod.getAlgorithm()).build());
+            return validationResults;
+        }
+
+        // If weak crypto is not explicitly allowed via override, check the password length and algorithm
+        final int passwordBytesLength = password.getBytes(StandardCharsets.UTF_8).length;
+        if (!allowWeakCrypto) {
+            final int minimumSafePasswordLength = PasswordBasedEncryptor.getMinimumSafePasswordLength();
+            if (passwordBytesLength < minimumSafePasswordLength) {
+                validationResults.add(new ValidationResult.Builder().subject(PASSWORD.getName())
+                        .explanation("Password length less than " + minimumSafePasswordLength + " characters is potentially unsafe. See Admin Guide.").build());
             }
-            int allowedKeyLength = PasswordBasedEncryptor.getMaxAllowedKeyLength(ENCRYPTION_ALGORITHM.getName());
+        }
 
-            if (StringUtils.isEmpty(password)) {
+        // Multiple checks on machine with limited strength crypto
+        if (limitedStrengthCrypto) {
+            // Cannot use unlimited strength ciphers on machine that lacks policies
+            if (encryptionMethod.isUnlimitedStrength()) {
+                validationResults.add(new ValidationResult.Builder().subject(ENCRYPTION_ALGORITHM.getName())
+                        .explanation(encryptionMethod.name() + " (" + encryptionMethod.getAlgorithm() + ") is not supported by this JVM due to lacking JCE Unlimited " +
+                                "Strength Jurisdiction Policy files. See Admin Guide.").build());
+            }
+
+            // Check if the password exceeds the limit
+            final boolean passwordLongerThanLimit = !CipherUtility.passwordLengthIsValidForAlgorithmOnLimitedStrengthCrypto(passwordBytesLength, encryptionMethod);
+            if (passwordLongerThanLimit) {
+                int maxPasswordLength = CipherUtility.getMaximumPasswordLengthForAlgorithmOnLimitedStrengthCrypto(encryptionMethod);
                 validationResults.add(new ValidationResult.Builder().subject(PASSWORD.getName())
-                    .explanation(PASSWORD.getDisplayName() + " is required when using algorithm " + algorithm).build());
-            } else {
-                if (password.getBytes().length * 8 > allowedKeyLength) {
-                    validationResults.add(new ValidationResult.Builder().subject(PASSWORD.getName())
-                        .explanation("Password length greater than " + allowedKeyLength + " bits is not supported by this JVM" +
-                            " due to lacking JCE Unlimited Strength Jurisdiction Policy files.").build());
-                }
+                        .explanation("Password length greater than " + maxPasswordLength + " characters is not supported by this JVM" +
+                                " due to lacking JCE Unlimited Strength Jurisdiction Policy files. See Admin Guide.").build());
             }
+        }
 
-            // Perform some analysis on the selected encryption algorithm to ensure the JVM can support it and the associated key
+        // Check the KDF for compatibility with this algorithm
+        List<String> kdfsForPBECipher = getKDFsForPBECipher(encryptionMethod);
+        if (kdf == null || !kdfsForPBECipher.contains(kdf.name())) {
+            final String displayName = KEY_DERIVATION_FUNCTION.getDisplayName();
+            validationResults.add(new ValidationResult.Builder().subject(displayName)
+                    .explanation(displayName + " is required to be " + StringUtils.join(kdfsForPBECipher,
+                            ", ") + " when using algorithm " + encryptionMethod.getAlgorithm() + ". See Admin Guide.").build());
+        }
 
-            if (StringUtils.isEmpty(kdf)) {
-                validationResults.add(new ValidationResult.Builder().subject(KEY_DERIVATION_FUNCTION.getName())
-                    .explanation(KEY_DERIVATION_FUNCTION.getDisplayName() + " is required when using algorithm " + algorithm).build());
+        return validationResults;
+    }
+
+    private List<ValidationResult> validateKeyed(EncryptionMethod encryptionMethod, KeyDerivationFunction kdf, String keyHex) {
+        List<ValidationResult> validationResults = new ArrayList<>();
+        boolean limitedStrengthCrypto = !PasswordBasedEncryptor.supportsUnlimitedStrength();
+
+        if (limitedStrengthCrypto) {
+            if (encryptionMethod.isUnlimitedStrength()) {
+                validationResults.add(new ValidationResult.Builder().subject(ENCRYPTION_ALGORITHM.getName())
+                        .explanation(encryptionMethod.name() + " (" + encryptionMethod.getAlgorithm() + ") is not supported by this JVM due to lacking JCE Unlimited " +
+                                "Strength Jurisdiction Policy files. See Admin Guide.").build());
+            }
+        }
+        int allowedKeyLength = PasswordBasedEncryptor.getMaxAllowedKeyLength(ENCRYPTION_ALGORITHM.getName());
+
+        if (StringUtils.isEmpty(keyHex)) {
+            validationResults.add(new ValidationResult.Builder().subject(RAW_KEY_HEX.getName())
+                    .explanation(RAW_KEY_HEX.getDisplayName() + " is required when using algorithm " + encryptionMethod.getAlgorithm() + ". See Admin Guide.").build());
+        } else {
+            byte[] keyBytes = new byte[0];
+            try {
+                keyBytes = Hex.decodeHex(keyHex.toCharArray());
+            } catch (DecoderException e) {
+                validationResults.add(new ValidationResult.Builder().subject(RAW_KEY_HEX.getName())
+                        .explanation("Key must be valid hexadecimal string. See Admin Guide.").build());
             }
+            if (keyBytes.length * 8 > allowedKeyLength) {
+                validationResults.add(new ValidationResult.Builder().subject(RAW_KEY_HEX.getName())
+                        .explanation("Key length greater than " + allowedKeyLength + " bits is not supported by this JVM" +
+                                " due to lacking JCE Unlimited Strength Jurisdiction Policy files. See Admin Guide.").build());
+            }
+            if (!CipherUtility.isValidKeyLengthForAlgorithm(keyBytes.length * 8, encryptionMethod.getAlgorithm())) {
+                List<Integer> validKeyLengths = CipherUtility.getValidKeyLengthsForAlgorithm(encryptionMethod.getAlgorithm());
+                validationResults.add(new ValidationResult.Builder().subject(RAW_KEY_HEX.getName())
+                        .explanation("Key must be valid length [" + StringUtils.join(validKeyLengths, ", ") + "]. See Admin Guide.").build());
+            }
+        }
+
+        // Perform some analysis on the selected encryption algorithm to ensure the JVM can support it and the associated key
+
+        List<String> kdfsForKeyedCipher = getKDFsForKeyedCipher();
+        if (kdf == null || !kdfsForKeyedCipher.contains(kdf.name())) {
+            validationResults.add(new ValidationResult.Builder().subject(KEY_DERIVATION_FUNCTION.getName())
+                    .explanation(KEY_DERIVATION_FUNCTION.getDisplayName() + " is required to be " + StringUtils.join(kdfsForKeyedCipher, ", ") + " when using algorithm " +
+                            encryptionMethod.getAlgorithm()).build());
         }
+
         return validationResults;
     }
 
+    private List<String> getKDFsForKeyedCipher() {
+        List<String> kdfsForKeyedCipher = new ArrayList<>();
+        kdfsForKeyedCipher.add(KeyDerivationFunction.NONE.name());
+        for (KeyDerivationFunction k : KeyDerivationFunction.values()) {
+            if (k.isStrongKDF()) {
+                kdfsForKeyedCipher.add(k.name());
+            }
+        }
+        return kdfsForKeyedCipher;
+    }
+
+    private List<String> getKDFsForPBECipher(EncryptionMethod encryptionMethod) {
+        List<String> kdfsForPBECipher = new ArrayList<>();
+        for (KeyDerivationFunction k : KeyDerivationFunction.values()) {
+            // Add all weak (legacy) KDFs except NONE
+            if (!k.isStrongKDF() && !k.equals(KeyDerivationFunction.NONE)) {
+                kdfsForPBECipher.add(k.name());
+                // If this algorithm supports strong KDFs, add them as well
+            } else if ((encryptionMethod.isCompatibleWithStrongKDFs() && k.isStrongKDF())) {
+                kdfsForPBECipher.add(k.name());
+            }
+        }
+        return kdfsForPBECipher;
+    }
+
     @Override
     public void onTrigger(final ProcessContext context, final ProcessSession session) {
         FlowFile flowFile = session.get();
@@ -300,14 +476,17 @@ public class EncryptContent extends AbstractProcessor {
                 } else if (!encrypt && privateKeyring != null) {
                     final char[] keyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).getValue().toCharArray();
                     encryptor = new OpenPGPKeyBasedEncryptor(algorithm, providerName, privateKeyring, null, keyringPassphrase,
-                        filename);
+                            filename);
                 } else {
                     final char[] passphrase = Normalizer.normalize(password, Normalizer.Form.NFC).toCharArray();
                     encryptor = new OpenPGPPasswordBasedEncryptor(algorithm, providerName, passphrase, filename);
                 }
+            } else if (kdf.equals(KeyDerivationFunction.NONE)) { // Raw key
+                final String keyHex = context.getProperty(RAW_KEY_HEX).getValue();
+                encryptor = new KeyedEncryptor(encryptionMethod, Hex.decodeHex(keyHex.toCharArray()));
             } else { // PBE
                 final char[] passphrase = Normalizer.normalize(password, Normalizer.Form.NFC).toCharArray();
-                encryptor = new PasswordBasedEncryptor(algorithm, providerName, passphrase, kdf);
+                encryptor = new PasswordBasedEncryptor(encryptionMethod, passphrase, kdf);
             }
 
             if (encrypt) {