You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by gr...@apache.org on 2023/01/26 22:56:44 UTC

[nifi] branch main updated: NIFI-11022 Added DecryptContent Compatibility Processors

This is an automated email from the ASF dual-hosted git repository.

greyp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 0c676b9633 NIFI-11022 Added DecryptContent Compatibility Processors
0c676b9633 is described below

commit 0c676b96337a0afb760d78de11e7fb1747794f9d
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Tue Dec 27 11:01:58 2022 -0600

    NIFI-11022 Added DecryptContent Compatibility Processors
    
    - Added nifi-cipher-bundle with nifi-cipher-nar for new Processors
    - Added DecryptContentCompatibilityMode Processor supporting PKCS5 and PKCS12 Password-Based Encryption Schemes
    - Added DecryptContentEncoded Processor supporting NiFi Key Derivation Functions and associated formatting
    - Added nifi-security-crypt-key module with Key Derivation Functions and Parameter Readers
    - Added Additional Details documentation for Processors
    
    This closes #6821
    Signed-off-by: Paul Grey <gr...@apache.org>
---
 nifi-assembly/pom.xml                              |   6 +
 nifi-commons/nifi-security-crypto-key/pom.xml      |  31 ++
 .../nifi/security/crypto/key/DerivedKey.java       |  31 ++
 .../crypto/key/DerivedKeyParameterSpec.java        |  29 ++
 .../crypto/key/DerivedKeyParameterSpecReader.java  |  30 ++
 .../security/crypto/key/DerivedKeyProvider.java    |  30 ++
 .../nifi/security/crypto/key/DerivedKeySpec.java   |  50 +++
 .../nifi/security/crypto/key/DerivedSecretKey.java |  48 +++
 .../crypto/key/StandardDerivedKeySpec.java         |  64 ++++
 .../key/argon2/Argon2DerivedKeyParameterSpec.java  |  69 ++++
 .../Argon2DerivedKeyParameterSpecReader.java       |  80 +++++
 .../key/argon2/Argon2DerivedKeyProvider.java       |  85 +++++
 .../crypto/key/bcrypt/BcryptBase64Decoder.java     |  76 +++++
 .../key/bcrypt/BcryptDerivedKeyParameterSpec.java  |  51 +++
 .../BcryptDerivedKeyParameterSpecReader.java       |  65 ++++
 .../key/bcrypt/BcryptDerivedKeyProvider.java       |  83 +++++
 .../DetectedDerivedKeyParameterSpecReader.java     |  76 +++++
 .../key/detection/DetectedDerivedKeyProvider.java  |  76 +++++
 .../security/crypto/key/io/ByteBufferSearch.java   |  51 +++
 .../key/pbkdf2/Pbkdf2DerivedKeyParameterSpec.java  |  51 +++
 .../Pbkdf2DerivedKeyParameterSpecReader.java       |  40 +++
 .../key/pbkdf2/Pbkdf2DerivedKeyProvider.java       |  71 ++++
 .../key/scrypt/ScryptDerivedKeyParameterSpec.java  |  69 ++++
 .../ScryptDerivedKeyParameterSpecReader.java       |  82 +++++
 .../key/scrypt/ScryptDerivedKeyProvider.java       |  96 ++++++
 .../Argon2DerivedKeyParameterSpecReaderTest.java   |  86 +++++
 .../key/argon2/Argon2DerivedKeyProviderTest.java   |  81 +++++
 .../BcryptDerivedKeyParameterSpecReaderTest.java   |  79 +++++
 .../key/bcrypt/BcryptDerivedKeyProviderTest.java   |  97 ++++++
 .../DetectedDerivedKeyParameterSpecReaderTest.java |  75 +++++
 .../detection/DetectedDerivedKeyProviderTest.java  | 112 +++++++
 .../Pbkdf2DerivedKeyParameterSpecReaderTest.java   |  48 +++
 .../key/pbkdf2/Pbkdf2DerivedKeyProviderTest.java   |  73 +++++
 .../ScryptDerivedKeyParameterSpecReaderTest.java   |  88 +++++
 .../key/scrypt/ScryptDerivedKeyProviderTest.java   |  80 +++++
 nifi-commons/pom.xml                               |   1 +
 .../nifi-cipher-bundle/nifi-cipher-nar/pom.xml     |  45 +++
 .../nifi-cipher-processors/pom.xml                 |  51 +++
 .../nifi/processors/cipher/CipherAttributeKey.java |  29 ++
 .../nifi/processors/cipher/CipherException.java    |  32 ++
 .../nifi/processors/cipher/DecryptContent.java     | 364 +++++++++++++++++++++
 .../cipher/DecryptContentCompatibility.java        | 295 +++++++++++++++++
 .../cipher/algorithm/CipherAlgorithmMode.java      |  51 +++
 .../cipher/algorithm/CipherAlgorithmPadding.java   |  51 +++
 .../cipher/algorithm/DigestAlgorithm.java          |  54 +++
 .../cipher/algorithm/SymmetricCipher.java          |  60 ++++
 .../CompatibilityModeEncryptionScheme.java         | 216 ++++++++++++
 .../CompatibilityModeKeyDerivationStrategy.java    |  99 ++++++
 .../cipher/encoded/EncodedDelimiter.java           |  61 ++++
 .../cipher/encoded/KeySpecificationFormat.java     |  49 +++
 .../cipher/io/DecryptStreamCallback.java           | 137 ++++++++
 .../services/org.apache.nifi.processor.Processor   |  16 +
 .../additionalDetails.html                         |  67 ++++
 .../additionalDetails.html                         | 144 ++++++++
 .../cipher/DecryptContentCompatibilityTest.java    | 255 +++++++++++++++
 .../nifi/processors/cipher/DecryptContentTest.java | 236 +++++++++++++
 nifi-nar-bundles/nifi-cipher-bundle/pom.xml        |  32 ++
 nifi-nar-bundles/pom.xml                           |   1 +
 58 files changed, 4605 insertions(+)

diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 9cdb654ac4..9214536eec 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -235,6 +235,12 @@ language governing permissions and limitations under the License. -->
             <version>1.20.0-SNAPSHOT</version>
             <type>nar</type>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-cipher-nar</artifactId>
+            <version>1.20.0-SNAPSHOT</version>
+            <type>nar</type>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-distributed-cache-services-nar</artifactId>
diff --git a/nifi-commons/nifi-security-crypto-key/pom.xml b/nifi-commons/nifi-security-crypto-key/pom.xml
new file mode 100644
index 0000000000..c569e48fca
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/pom.xml
@@ -0,0 +1,31 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+      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.
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-commons</artifactId>
+        <version>1.20.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-security-crypto-key</artifactId>
+    <description>Cryptographic key derivation function components with minimal dependencies</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKey.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKey.java
new file mode 100644
index 0000000000..bb2a6d1afc
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKey.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+import java.security.Key;
+
+/**
+ * Derived Key extension of standard Key supporting serialized representation
+ */
+public interface DerivedKey extends Key {
+    /**
+     * Get serialized representation of derived key including parameters when applicable to a particular function algorithm
+     *
+     * @return Serialized representation of derived key
+     */
+    String getSerialized();
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpec.java
new file mode 100644
index 0000000000..4a9061fe8d
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpec.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+/**
+ * Derived Key Parameter Specification with properties for implementing algorithms
+ */
+public interface DerivedKeyParameterSpec {
+    /**
+     * Get cryptographic salt that algorithms use for key derivation
+     *
+     * @return Cryptographic salt bytes
+     */
+    byte[] getSalt();
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..c198643c96
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyParameterSpecReader.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+/**
+ * Abstraction for reading Derived Key Parameter Specifications from serialized parameters
+ */
+public interface DerivedKeyParameterSpecReader<T extends DerivedKeyParameterSpec> {
+    /**
+     * Read serialized parameters and return Derived Key Parameter Specification
+     *
+     * @param serializedParameters Serialized parameters
+     * @return Derived Key Parameter Specification
+     */
+    T read(byte[] serializedParameters);
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyProvider.java
new file mode 100644
index 0000000000..705f2b5bfc
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeyProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+/**
+ * Provider abstraction for Key Derivation Function implementations
+ */
+public interface DerivedKeyProvider<T extends DerivedKeyParameterSpec> {
+    /**
+     * Get Derived Key using configured properties
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Key
+     */
+    DerivedKey getDerivedKey(DerivedKeySpec<T> derivedKeySpec);
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeySpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeySpec.java
new file mode 100644
index 0000000000..4567f7a733
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedKeySpec.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+/**
+ * Derived Key Specification with password and parameters for implementing algorithms
+ */
+public interface DerivedKeySpec<T extends DerivedKeyParameterSpec> {
+    /**
+     * Get password characters that algorithms use for key derivation
+     *
+     * @return Password characters
+     */
+    char[] getPassword();
+
+    /**
+     * Get length requested for derived key in bytes
+     *
+     * @return Length of derived key in bytes
+     */
+    int getDerivedKeyLength();
+
+    /**
+     * Get cipher algorithm for which the derived key will be used
+     *
+     * @return Cipher algorithm
+     */
+    String getAlgorithm();
+
+    /**
+     * Get parameter specification
+     *
+     * @return Parameter specification
+     */
+    T getParameterSpec();
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedSecretKey.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedSecretKey.java
new file mode 100644
index 0000000000..e88becc315
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/DerivedSecretKey.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Derived Secret Key containing encoded key bytes and serialized representation of key with parameters
+ */
+public class DerivedSecretKey extends SecretKeySpec implements DerivedKey {
+    private final String serialized;
+
+    /**
+     * Derived Secret Key constructor with encoded key and serialized representation
+     *
+     * @param key Encoded key bytes
+     * @param algorithm Cipher algorithm for which the key will be used
+     * @param serialized Serialized representation of the key with parameters depending on derivation function
+     */
+    public DerivedSecretKey(final byte[] key, final String algorithm, final String serialized) {
+        super(key, algorithm);
+        this.serialized = serialized;
+    }
+
+    /**
+     * Get serialized key with parameters
+     *
+     * @return Serialized key with parameters
+     */
+    @Override
+    public String getSerialized() {
+        return serialized;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/StandardDerivedKeySpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/StandardDerivedKeySpec.java
new file mode 100644
index 0000000000..9e501c0735
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/StandardDerivedKeySpec.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key;
+
+import java.util.Objects;
+
+/**
+ * Standard implementation of Derived Key Specification with required properties
+ */
+public class StandardDerivedKeySpec<T extends DerivedKeyParameterSpec> implements DerivedKeySpec<T> {
+    private final char[] password;
+
+    private final int derivedKeyLength;
+
+    private final String algorithm;
+
+    private final T parameterSpec;
+
+    public StandardDerivedKeySpec(
+            final char[] password,
+            final int derivedKeyLength,
+            final String algorithm,
+            final T parameterSpec
+    ) {
+        this.password = Objects.requireNonNull(password, "Password required");
+        this.derivedKeyLength = derivedKeyLength;
+        this.algorithm = Objects.requireNonNull(algorithm, "Algorithm required");
+        this.parameterSpec = Objects.requireNonNull(parameterSpec, "Parameter Specification required");
+    }
+
+    @Override
+    public char[] getPassword() {
+        return password;
+    }
+
+    @Override
+    public int getDerivedKeyLength() {
+        return derivedKeyLength;
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public T getParameterSpec() {
+        return parameterSpec;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpec.java
new file mode 100644
index 0000000000..0561261f90
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpec.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.argon2;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+
+/**
+ * Argon2 key derivation function parameter specification
+ */
+public class Argon2DerivedKeyParameterSpec implements DerivedKeyParameterSpec {
+    private final int memory;
+
+    private final int iterations;
+
+    private final int parallelism;
+
+    private final byte[] salt;
+
+    /**
+     * Argon2 Parameter Specification constructor with required properties
+     *
+     * @param memory Size of memory in kilobytes for processing
+     * @param iterations Number of iterations to perform
+     * @param parallelism Number of threads for parallel processing
+     * @param salt Array of random salt bytes
+     */
+    public Argon2DerivedKeyParameterSpec(
+            final int memory,
+            final int iterations,
+            final int parallelism,
+            final byte[] salt
+    ) {
+        this.memory = memory;
+        this.iterations = iterations;
+        this.parallelism = parallelism;
+        this.salt = salt;
+    }
+
+    @Override
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public int getMemory() {
+        return memory;
+    }
+
+    public int getIterations() {
+        return iterations;
+    }
+
+    public int getParallelism() {
+        return parallelism;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..78ee04bdc6
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReader.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.argon2;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Argon2 implementation reads a US-ASCII string of bytes using the Password-Hashing-Competition String Format specification
+ */
+public class Argon2DerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<Argon2DerivedKeyParameterSpec> {
+    /** Argon2id hybrid with version 1.3 and 22 character Base64 encoded salt with optional trailing hash parameter */
+    private static final Pattern PHC_STRING_FORMAT = Pattern.compile("^\\$argon2id\\$v=19\\$m=(\\d+),t=(\\d+),p=(\\d+)\\$([\\w/+]{22})(\\$[\\w/+]+)?$");
+
+    private static final int MEMORY_GROUP = 1;
+
+    private static final int ITERATIONS_GROUP = 2;
+
+    private static final int PARALLELISM_GROUP = 3;
+
+    private static final int SALT_GROUP = 4;
+
+    private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    /**
+     * Read serialized parameters parsed from US-ASCII string of bytes
+     *
+     * @param serializedParameters Serialized parameters
+     * @return Argon2 Parameter Specification
+     */
+    @Override
+    public Argon2DerivedKeyParameterSpec read(final byte[] serializedParameters) {
+        Objects.requireNonNull(serializedParameters, "Parameters required");
+        final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
+
+        final Matcher matcher = PHC_STRING_FORMAT.matcher(parameters);
+        if (matcher.matches()) {
+            final String memoryGroup = matcher.group(MEMORY_GROUP);
+            final String iterationsGroup = matcher.group(ITERATIONS_GROUP);
+            final String parallelismGroup = matcher.group(PARALLELISM_GROUP);
+            final String saltGroup = matcher.group(SALT_GROUP);
+
+            final int memory = Integer.parseInt(memoryGroup);
+            final int iterations = Integer.parseInt(iterationsGroup);
+            final int parallelism = Integer.parseInt(parallelismGroup);
+            final byte[] salt = decoder.decode(saltGroup);
+            return new Argon2DerivedKeyParameterSpec(
+                    memory,
+                    iterations,
+                    parallelism,
+                    salt
+            );
+        } else {
+            final String message = String.format("Argon2 serialized parameters [%s] format not matched", parameters);
+            throw new IllegalArgumentException(message);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProvider.java
new file mode 100644
index 0000000000..81992e6dfb
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProvider.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.argon2;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.DerivedSecretKey;
+import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
+import org.bouncycastle.crypto.params.Argon2Parameters;
+
+import java.util.Base64;
+
+/**
+ * Argon2 implementation of Derived Key Provider based on Bouncy Castle Argon2 components described in RFC 9016
+ */
+public class Argon2DerivedKeyProvider implements DerivedKeyProvider<Argon2DerivedKeyParameterSpec> {
+    private static final int HYBRID_VERSION = Argon2Parameters.ARGON2_id;
+
+    private static final int CURRENT_VERSION = Argon2Parameters.ARGON2_VERSION_13;
+
+    private static final String SERIALIZED_FORMAT = "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s";
+
+    private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
+
+    /**
+     * Get Derived Key using Argon2id version 1.3 and provided specification
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Secret Key
+     */
+    @Override
+    public DerivedKey getDerivedKey(final DerivedKeySpec<Argon2DerivedKeyParameterSpec> derivedKeySpec) {
+        final Argon2DerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
+        final Argon2Parameters parameters = getParameters(parameterSpec);
+        final Argon2BytesGenerator generator = new Argon2BytesGenerator();
+        generator.init(parameters);
+
+        final int derivedKeyLength = derivedKeySpec.getDerivedKeyLength();
+        final byte[] derivedKeyBytes = new byte[derivedKeyLength];
+        final char[] password = derivedKeySpec.getPassword();
+        generator.generateBytes(password, derivedKeyBytes);
+
+        final String serialized = getSerialized(derivedKeyBytes, parameterSpec);
+        return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
+    }
+
+    private Argon2Parameters getParameters(final Argon2DerivedKeyParameterSpec parameterSpec) {
+        return new Argon2Parameters.Builder(HYBRID_VERSION)
+                .withVersion(CURRENT_VERSION)
+                .withMemoryAsKB(parameterSpec.getMemory())
+                .withIterations(parameterSpec.getIterations())
+                .withParallelism(parameterSpec.getParallelism())
+                .withSalt(parameterSpec.getSalt())
+                .build();
+    }
+
+    private String getSerialized(final byte[] derivedKeyBytes, final Argon2DerivedKeyParameterSpec parameterSpec) {
+        final String derivedKeyEncoded = encoder.encodeToString(derivedKeyBytes);
+        final String saltEncoded = encoder.encodeToString(parameterSpec.getSalt());
+        return String.format(
+                SERIALIZED_FORMAT,
+                CURRENT_VERSION,
+                parameterSpec.getMemory(),
+                parameterSpec.getIterations(),
+                parameterSpec.getParallelism(),
+                saltEncoded,
+                derivedKeyEncoded
+        );
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptBase64Decoder.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptBase64Decoder.java
new file mode 100644
index 0000000000..1c0a439a2f
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptBase64Decoder.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Base64 Decoder translates from bcrypt Base64 characters to RFC 4648 characters
+ */
+class BcryptBase64Decoder {
+    /** Alphabet of shared characters following common ordering */
+    private static final String SHARED_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+    /** bcrypt alphabet defined according to the OpenBSD bcrypt function beginning with control characters */
+    private static final String BCRYPT_ALPHABET = String.format("./%s", SHARED_ALPHABET);
+
+    /** Standard alphabet defined according to RFC 4648 ending with control characters */
+    private static final String STANDARD_ALPHABET = String.format("%s+/", SHARED_ALPHABET);
+
+    private static final Map<Character, Character> BCRYPT_STANDARD_CHARACTERS = new LinkedHashMap<>();
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    static {
+        final char[] bcryptCharacters = BCRYPT_ALPHABET.toCharArray();
+        final char[] standardCharacters = STANDARD_ALPHABET.toCharArray();
+
+        for (int i = 0; i < bcryptCharacters.length; i++) {
+            final char bcryptCharacter = bcryptCharacters[i];
+            final char standardCharacter = standardCharacters[i];
+            BCRYPT_STANDARD_CHARACTERS.put(bcryptCharacter, standardCharacter);
+        }
+    }
+
+    /**
+     * Decode bcrypt Base64 encoded ASCII characters to support reading salt and hash strings
+     *
+     * @param encoded ASCII string of characters encoded using bcrypt Base64 characters
+     * @return Decoded bytes
+     */
+    static byte[] decode(final String encoded) {
+        final int encodedLength = encoded.length();
+        final byte[] converted = new byte[encodedLength];
+        for (int i = 0; i < encodedLength; i++) {
+            final char encodedCharacter = encoded.charAt(i);
+            final char standardCharacter = getStandardCharacter(encodedCharacter);
+            converted[i] = (byte) standardCharacter;
+        }
+        return decoder.decode(converted);
+    }
+
+    private static char getStandardCharacter(final char encodedCharacter) {
+        final Character standardCharacter = BCRYPT_STANDARD_CHARACTERS.get(encodedCharacter);
+        if (standardCharacter == null) {
+            final String message = String.format("Encoded character [%c] not supported for bcrypt Base64 decoding", encodedCharacter);
+            throw new IllegalArgumentException(message);
+        }
+        return standardCharacter;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpec.java
new file mode 100644
index 0000000000..6d11deda2c
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpec.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+
+/**
+ * bcrypt key derivation function parameter specification
+ */
+public class BcryptDerivedKeyParameterSpec implements DerivedKeyParameterSpec {
+    private final int cost;
+
+    private final byte[] salt;
+
+    /**
+     * bcrypt Parameter Specification constructor with required properties
+     *
+     * @param cost Cost parameter
+     * @param salt Array of random salt bytes
+     */
+    public BcryptDerivedKeyParameterSpec(
+            final int cost,
+            final byte[] salt
+    ) {
+        this.cost = cost;
+        this.salt = salt;
+    }
+
+    @Override
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public int getCost() {
+        return cost;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..9ca69b4bef
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReader.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * bcrypt implementation reads a US-ASCII string of bytes using the Modular Crypt Format specification as added in NiFi 0.5.0
+ */
+public class BcryptDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<BcryptDerivedKeyParameterSpec> {
+    /** Modular Crypt Format containing the cost as a zero-padded number with a trailing bcrypt-Base64 encoded 16 byte salt and optional hash */
+    private static final Pattern MODULAR_CRYPT_FORMAT = Pattern.compile("^\\$2[abxy]\\$(\\d{2})\\$([\\w/.]{22})([\\w/.]{31})?$");
+
+    private static final int COST_GROUP = 1;
+
+    private static final int SALT_GROUP = 2;
+
+    private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
+
+    /**
+     * Read serialized parameters parsed from US-ASCII string of bytes
+     *
+     * @param serializedParameters Serialized parameters
+     * @return bcrypt Parameter Specification
+     */
+    @Override
+    public BcryptDerivedKeyParameterSpec read(final byte[] serializedParameters) {
+        Objects.requireNonNull(serializedParameters, "Parameters required");
+        final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
+
+        final Matcher matcher = MODULAR_CRYPT_FORMAT.matcher(parameters);
+        if (matcher.matches()) {
+            final String costGroup = matcher.group(COST_GROUP);
+            final String saltGroup = matcher.group(SALT_GROUP);
+
+            final int cost = Integer.parseInt(costGroup);
+            final byte[] salt = BcryptBase64Decoder.decode(saltGroup);
+
+            return new BcryptDerivedKeyParameterSpec(cost, salt);
+        } else {
+            final String message = String.format("bcrypt serialized parameters [%s] format not matched", parameters);
+            throw new IllegalArgumentException(message);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProvider.java
new file mode 100644
index 0000000000..94ed1cda54
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProvider.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.DerivedSecretKey;
+import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * bcrypt implementation of Derived Key Provider based on Bouncy Castle bcrypt components with SHA-512 digest for derived key
+ */
+public class BcryptDerivedKeyProvider implements DerivedKeyProvider<BcryptDerivedKeyParameterSpec> {
+    private static final Charset SERIALIZED_CHARACTER_SET = StandardCharsets.US_ASCII;
+
+    private static final int SERIALIZED_HASH_START_INDEX = 29;
+
+    private static final String DIGEST_ALGORITHM = "SHA-512";
+
+    private static final String BCRYPT_VERSION = "2a";
+
+    /**
+     * Get Derived Key using bcrypt version 2a and provided specification with SHA-512 digest of encoded raw hash bytes
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Secret Key
+     */
+    @Override
+    public DerivedKey getDerivedKey(final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec) {
+        final String serialized = getHashMessage(derivedKeySpec);
+        final byte[] hashMessage = serialized.getBytes(SERIALIZED_CHARACTER_SET);
+
+        final byte[] encodedRawHash = Arrays.copyOfRange(hashMessage, SERIALIZED_HASH_START_INDEX, hashMessage.length);
+        final byte[] derivedKeyBytes = getDerivedKeyBytes(encodedRawHash, derivedKeySpec.getDerivedKeyLength());
+        return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
+    }
+
+    private String getHashMessage(final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec) {
+        final BcryptDerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
+
+        final int cost = parameterSpec.getCost();
+        final byte[] salt = parameterSpec.getSalt();
+
+        final char[] password = derivedKeySpec.getPassword();
+
+        return OpenBSDBCrypt.generate(BCRYPT_VERSION, password, salt, cost);
+    }
+
+    private byte[] getDerivedKeyBytes(final byte[] hash, final int derivedKeyLength) {
+        final MessageDigest messageDigest = getMessageDigest();
+        final byte[] digested = messageDigest.digest(hash);
+        return Arrays.copyOf(digested, derivedKeyLength);
+    }
+
+    private MessageDigest getMessageDigest() {
+        try {
+            return MessageDigest.getInstance(DIGEST_ALGORITHM);
+        } catch (final NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(DIGEST_ALGORITHM, e);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..85c573a3ce
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReader.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.detection;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
+import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpecReader;
+import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpecReader;
+import org.apache.nifi.security.crypto.key.io.ByteBufferSearch;
+import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpecReader;
+import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpecReader;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Delegating implementation capable of selecting a reader based on detected header bytes of serialized parameters
+ */
+public class DetectedDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<DerivedKeyParameterSpec> {
+    /** Argon2id header as implemented in NiFi 1.12.0 */
+    private static final byte[] ARGON2_ID_DELIMITER = {'$', 'a', 'r', 'g', 'o', 'n', '2', 'i', 'd', '$'};
+
+    /** bcrypt 2a header as implemented in NiFi 0.5.0 */
+    private static final byte[] BCRYPT_2A_DELIMITER = {'$', '2', 'a', '$'};
+
+    /** scrypt header as implemented in NiFi 0.5.0 */
+    private static final byte[] SCRYPT_DELIMITER = {'$', 's', '0', '$'};
+
+    private static final Argon2DerivedKeyParameterSpecReader argon2Reader = new Argon2DerivedKeyParameterSpecReader();
+
+    private static final BcryptDerivedKeyParameterSpecReader bcryptReader = new BcryptDerivedKeyParameterSpecReader();
+
+    private static final ScryptDerivedKeyParameterSpecReader scryptReader = new ScryptDerivedKeyParameterSpecReader();
+
+    private static final Pbkdf2DerivedKeyParameterSpecReader pbkdf2Reader = new Pbkdf2DerivedKeyParameterSpecReader();
+
+    /**
+     * Read Parameter Specification selects a Reader based on header bytes defaulting to PBKDF2 in absence of a matched pattern
+     *
+     * @param serializedParameters Serialized parameters
+     * @return Derived Key Parameter Specification read from serialized parameters
+     */
+    @Override
+    public DerivedKeyParameterSpec read(final byte[] serializedParameters) {
+        final ByteBuffer buffer = ByteBuffer.wrap(serializedParameters);
+        final DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> reader = getReader(buffer);
+        return reader.read(serializedParameters);
+    }
+
+    private DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> getReader(final ByteBuffer buffer) {
+        final DerivedKeyParameterSpecReader<? extends DerivedKeyParameterSpec> reader;
+        if (ByteBufferSearch.indexOf(buffer, ARGON2_ID_DELIMITER) == 0) {
+            reader = argon2Reader;
+        } else if (ByteBufferSearch.indexOf(buffer, BCRYPT_2A_DELIMITER) == 0) {
+            reader = bcryptReader;
+        } else if (ByteBufferSearch.indexOf(buffer, SCRYPT_DELIMITER) == 0) {
+            reader = scryptReader;
+        } else {
+            reader = pbkdf2Reader;
+        }
+        return reader;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProvider.java
new file mode 100644
index 0000000000..d60c13eda4
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.detection;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyProvider;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Provider delegating to configured implementations based on Parameter Specification class
+ */
+public class DetectedDerivedKeyProvider implements DerivedKeyProvider<DerivedKeyParameterSpec> {
+    private static final Map<Class<? extends DerivedKeyParameterSpec>, DerivedKeyProvider<? extends DerivedKeyParameterSpec>> providers = new LinkedHashMap<>();
+
+    static {
+        providers.put(Argon2DerivedKeyParameterSpec.class, new Argon2DerivedKeyProvider());
+        providers.put(BcryptDerivedKeyParameterSpec.class, new BcryptDerivedKeyProvider());
+        providers.put(ScryptDerivedKeyParameterSpec.class, new ScryptDerivedKeyProvider());
+        providers.put(Pbkdf2DerivedKeyParameterSpec.class, new Pbkdf2DerivedKeyProvider());
+    }
+
+    /**
+     * Get Derived Key using implementation selected based on assignable Parameter Specification class mapped to Provider
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Key
+     */
+    @Override
+    public DerivedKey getDerivedKey(final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec) {
+        Objects.requireNonNull(derivedKeySpec, "Specification required");
+
+        final Class<? extends DerivedKeyParameterSpec> parameterSpecClass = derivedKeySpec.getParameterSpec().getClass();
+        final DerivedKeyProvider<DerivedKeyParameterSpec> derivedKeyProvider = findProvider(parameterSpecClass);
+
+        return derivedKeyProvider.getDerivedKey(derivedKeySpec);
+    }
+
+    @SuppressWarnings("unchecked")
+    private DerivedKeyProvider<DerivedKeyParameterSpec> findProvider(final Class<? extends DerivedKeyParameterSpec> parameterSpecClass) {
+        final Class<? extends DerivedKeyParameterSpec> foundSpecClass = providers.keySet()
+                .stream()
+                .filter(specClass -> specClass.isAssignableFrom(parameterSpecClass))
+                .findFirst()
+                .orElseThrow(() -> new UnsupportedOperationException(String.format("Parameter Specification [%s] not supported", parameterSpecClass)));
+
+        final DerivedKeyProvider<? extends DerivedKeyParameterSpec> derivedKeyProvider = providers.get(foundSpecClass);
+        return (DerivedKeyProvider<DerivedKeyParameterSpec>) derivedKeyProvider;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/io/ByteBufferSearch.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/io/ByteBufferSearch.java
new file mode 100644
index 0000000000..512adb9015
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/io/ByteBufferSearch.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Byte Buffer Search utilities
+ */
+public class ByteBufferSearch {
+
+    private static final int END_OF_FILE = -1;
+
+    /**
+     * Get starting index of delimiter in buffer
+     *
+     * @param buffer Byte Buffer to be searched
+     * @param delimiter Delimiter
+     * @return Starting index of delimiter or -1 when not found
+     */
+    public static int indexOf(final ByteBuffer buffer, final byte[] delimiter) {
+        final int bufferSearchLength = buffer.limit() - delimiter.length + 1;
+        bufferSearch:
+        for (int i = 0; i < bufferSearchLength; i++) {
+            for (int j = 0; j < delimiter.length; j++) {
+                final int bufferIndex = i + j;
+                final byte indexByte = buffer.get(bufferIndex);
+                final byte delimiterByte = delimiter[j];
+                if (indexByte != delimiterByte) {
+                    continue bufferSearch;
+                }
+            }
+            return i;
+        }
+        return END_OF_FILE;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpec.java
new file mode 100644
index 0000000000..02eaa4c48e
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpec.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.pbkdf2;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+
+/**
+ * PBKDF2 key derivation function parameter specification
+ */
+public class Pbkdf2DerivedKeyParameterSpec implements DerivedKeyParameterSpec {
+    private final int iterations;
+
+    private final byte[] salt;
+
+    /**
+     * PBKDF2 Parameter Specification constructor with required properties
+     *
+     * @param iterations Cost parameter
+     * @param salt Array of random salt bytes
+     */
+    public Pbkdf2DerivedKeyParameterSpec(
+            final int iterations,
+            final byte[] salt
+    ) {
+        this.iterations = iterations;
+        this.salt = salt;
+    }
+
+    @Override
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public int getIterations() {
+        return iterations;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..812ef4e5f7
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReader.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.pbkdf2;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
+
+import java.util.Objects;
+
+/**
+ * PBKDF2 implementation uses the serialized parameters as the salt with a hard-coded number of iterations as defined in NiFi 0.5.0
+ */
+public class Pbkdf2DerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<Pbkdf2DerivedKeyParameterSpec> {
+    protected static final int VERSION_0_5_0_ITERATIONS = 160000;
+
+    /**
+     * Read serialized parameters and return as salt bytes with 160,000 iterations as defined in NiFi 0.5.0
+     *
+     * @param serializedParameters Serialized parameters
+     * @return PBKDF2 Parameter Specification
+     */
+    @Override
+    public Pbkdf2DerivedKeyParameterSpec read(final byte[] serializedParameters) {
+        Objects.requireNonNull(serializedParameters, "Parameters required");
+        return new Pbkdf2DerivedKeyParameterSpec(VERSION_0_5_0_ITERATIONS, serializedParameters);
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProvider.java
new file mode 100644
index 0000000000..67a7827d9e
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.pbkdf2;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.DerivedSecretKey;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * PBKDF2 implementation of Derived Key Provider based on Bouncy Castle components with HMAC SHA-512 pseudorandom function
+ */
+public class Pbkdf2DerivedKeyProvider implements DerivedKeyProvider<Pbkdf2DerivedKeyParameterSpec> {
+    private static final Charset PASSWORD_CHARACTER_SET = StandardCharsets.UTF_8;
+
+    private static final int BITS = 8;
+
+    private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
+
+    /**
+     * Get Derived Key using PBKDF2 with HMAC SHA-512 and provided specification
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Secret Key
+     */
+    @Override
+    public DerivedKey getDerivedKey(final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec) {
+        final byte[] derivedKeyBytes = getDerivedKeyBytes(derivedKeySpec);
+        final String serialized = encoder.encodeToString(derivedKeyBytes);
+        return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
+    }
+
+    private byte[] getDerivedKeyBytes(final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec) {
+        final Digest digest = new SHA512Digest();
+        final PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(digest);
+
+        final byte[] password = new String(derivedKeySpec.getPassword()).getBytes(PASSWORD_CHARACTER_SET);
+        final Pbkdf2DerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
+        final byte[] salt = parameterSpec.getSalt();
+        final int iterations = parameterSpec.getIterations();
+        generator.init(password, salt, iterations);
+
+        final int derivedKeyLengthBits = derivedKeySpec.getDerivedKeyLength() * BITS;
+        final CipherParameters cipherParameters = generator.generateDerivedParameters(derivedKeyLengthBits);
+        final KeyParameter keyParameter = (KeyParameter) cipherParameters;
+        return keyParameter.getKey();
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpec.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpec.java
new file mode 100644
index 0000000000..b0f0d8f2f7
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpec.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.scrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+
+/**
+ * scrypt key derivation function parameter specification
+ */
+public class ScryptDerivedKeyParameterSpec implements DerivedKeyParameterSpec {
+    private final int cost;
+
+    private final int blockSize;
+
+    private final int parallelization;
+
+    private final byte[] salt;
+
+    /**
+     * scrypt Parameter Specification constructor with required properties
+     *
+     * @param cost CPU and memory cost parameter
+     * @param blockSize Block size parameter
+     * @param parallelization Parallelization parameter
+     * @param salt Array of random salt bytes
+     */
+    public ScryptDerivedKeyParameterSpec(
+            final int cost,
+            final int blockSize,
+            final int parallelization,
+            final byte[] salt
+    ) {
+        this.cost = cost;
+        this.blockSize = blockSize;
+        this.parallelization = parallelization;
+        this.salt = salt;
+    }
+
+    @Override
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public int getCost() {
+        return cost;
+    }
+
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    public int getParallelization() {
+        return parallelization;
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReader.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReader.java
new file mode 100644
index 0000000000..a59522b292
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReader.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.scrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpecReader;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * scrypt implementation reads a US-ASCII string of bytes using a Modular Crypt Format defined according to the com.lambdaworks:scrypt library
+ */
+public class ScryptDerivedKeyParameterSpecReader implements DerivedKeyParameterSpecReader<ScryptDerivedKeyParameterSpec> {
+    /** Modular Crypt Format containing the parameters encoded as a 32-bit hexadecimal number with a trailing Base64 encoded salt and optional hash */
+    private static final Pattern MODULAR_CRYPT_FORMAT = Pattern.compile("^\\$s0\\$([a-f0-9]{5,})\\$([\\w/=+]{11,64})\\$?([\\w/=+]{1,256})?$");
+
+    private static final int PARAMETERS_GROUP = 1;
+
+    private static final int SALT_GROUP = 2;
+
+    private static final Charset PARAMETERS_CHARACTER_SET = StandardCharsets.US_ASCII;
+
+    private static final int HEXADECIMAL_RADIX = 16;
+
+    private static final int LOG_BASE_2 = 2;
+
+    private static final int COST_BITS = 16;
+
+    private static final int SIZE_BITS = 8;
+
+    private static final int SIXTEEN_BIT_SHIFT = 0xffff;
+
+    private static final int EIGHT_BIT_SHIFT = 0xff;
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    @Override
+    public ScryptDerivedKeyParameterSpec read(final byte[] serializedParameters) {
+        Objects.requireNonNull(serializedParameters, "Parameters required");
+        final String parameters = new String(serializedParameters, PARAMETERS_CHARACTER_SET);
+
+        final Matcher matcher = MODULAR_CRYPT_FORMAT.matcher(parameters);
+        if (matcher.matches()) {
+            final String parametersGroup = matcher.group(PARAMETERS_GROUP);
+            final String saltGroup = matcher.group(SALT_GROUP);
+            return readParameters(parametersGroup, saltGroup);
+        } else {
+            final String message = String.format("scrypt serialized parameters [%s] format not matched", parameters);
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    private ScryptDerivedKeyParameterSpec readParameters(final String parametersEncoded, final String saltEncoded) {
+        final long parameters = Long.parseLong(parametersEncoded, HEXADECIMAL_RADIX);
+        final long costExponent = parameters >> COST_BITS & SIXTEEN_BIT_SHIFT;
+
+        final int cost = (int) Math.pow(LOG_BASE_2, costExponent);
+        final int blockSize = (int) parameters >> SIZE_BITS & EIGHT_BIT_SHIFT;
+        final int parallelization = (int) parameters & EIGHT_BIT_SHIFT;
+
+        final byte[] salt = decoder.decode(saltEncoded);
+        return new ScryptDerivedKeyParameterSpec(cost, blockSize, parallelization, salt);
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProvider.java b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProvider.java
new file mode 100644
index 0000000000..46dd20a731
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/main/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProvider.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.scrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.DerivedSecretKey;
+import org.bouncycastle.crypto.generators.SCrypt;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * scrypt implementation of Derived Key Provider based on Bouncy Castle scrypt components described in RFC 7914
+ */
+public class ScryptDerivedKeyProvider implements DerivedKeyProvider<ScryptDerivedKeyParameterSpec> {
+    private static final String SERIALIZED_FORMAT = "$s0$%s$%s$%s";
+
+    private static final int HEXADECIMAL_RADIX = 16;
+
+    private static final int LOG_BASE_2 = 2;
+
+    private static final int COST_BITS = 16;
+
+    private static final int SIZE_BITS = 8;
+
+    private static final Charset PASSWORD_CHARACTER_SET = StandardCharsets.UTF_8;
+
+    private static final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
+
+    /**
+     * Get Derived Key using scrypt and provided specification
+     *
+     * @param derivedKeySpec Derived Key Specification
+     * @return Derived Secret Key
+     */
+    @Override
+    public DerivedKey getDerivedKey(final DerivedKeySpec<ScryptDerivedKeyParameterSpec> derivedKeySpec) {
+        final byte[] password = new String(derivedKeySpec.getPassword()).getBytes(PASSWORD_CHARACTER_SET);
+
+        final ScryptDerivedKeyParameterSpec parameterSpec = derivedKeySpec.getParameterSpec();
+        final byte[] salt = parameterSpec.getSalt();
+        final int cost = parameterSpec.getCost();
+        final int blockSize = parameterSpec.getBlockSize();
+        final int parallelization = parameterSpec.getParallelization();
+        final int derivedKeyLength = derivedKeySpec.getDerivedKeyLength();
+
+        final byte[] derivedKeyBytes = SCrypt.generate(password, salt, cost, blockSize, parallelization, derivedKeyLength);
+
+        final String serialized = getSerialized(derivedKeyBytes, parameterSpec);
+        return new DerivedSecretKey(derivedKeyBytes, derivedKeySpec.getAlgorithm(), serialized);
+    }
+
+    private String getSerialized(final byte[] derivedKeyBytes, final ScryptDerivedKeyParameterSpec parameterSpec) {
+        final String parametersEncoded = getParametersEncoded(parameterSpec);
+        final String derivedKeyEncoded = encoder.encodeToString(derivedKeyBytes);
+        final String saltEncoded = encoder.encodeToString(parameterSpec.getSalt());
+        return String.format(
+                SERIALIZED_FORMAT,
+                parametersEncoded,
+                saltEncoded,
+                derivedKeyEncoded
+        );
+    }
+
+    private String getParametersEncoded(final ScryptDerivedKeyParameterSpec parameterSpec) {
+        final long cost = log2(parameterSpec.getCost()) << COST_BITS;
+        final long blockSize = parameterSpec.getBlockSize() << SIZE_BITS;
+        final long parallelization = parameterSpec.getParallelization();
+        final long parameters = cost | blockSize | parallelization;
+        return Long.toString(parameters, HEXADECIMAL_RADIX);
+    }
+
+    private long log2(final int number) {
+        final double log = Math.log(number);
+        final double logBase2 = Math.log(LOG_BASE_2);
+        final double log2 = log / logBase2;
+        return Math.round(log2);
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReaderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReaderTest.java
new file mode 100644
index 0000000000..7da2bdaa99
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyParameterSpecReaderTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.argon2;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class Argon2DerivedKeyParameterSpecReaderTest {
+    private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
+
+    private static final int MEMORY = 65536;
+
+    private static final int ITERATIONS = 2;
+
+    private static final int PARALLELISM = 1;
+
+    private static final String ARGON2_SALT_STRING = "QXJnb24yU2FsdFN0cmluZw";
+
+    private static final String PARAMETERS = String.format("$argon2id$v=19$m=%d,t=%d,p=%d$%s", MEMORY, ITERATIONS, PARALLELISM, ARGON2_SALT_STRING);
+
+    private static final String PARAMETERS_HASH = String.format("%s$6LOmoOXYJV0tXBJtxtD1Mg", PARAMETERS);
+
+    Base64.Decoder decoder = Base64.getDecoder();
+
+    Argon2DerivedKeyParameterSpecReader reader;
+
+    @BeforeEach
+    void setReader() {
+        reader = new Argon2DerivedKeyParameterSpecReader();
+    }
+
+    @Test
+    void testReadException() {
+        assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
+    }
+
+    @Test
+    void testRead() {
+        final byte[] serializedParameters = PARAMETERS.getBytes(StandardCharsets.US_ASCII);
+
+        final Argon2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    @Test
+    void testReadHash() {
+        final byte[] serializedParameters = PARAMETERS_HASH.getBytes(StandardCharsets.US_ASCII);
+
+        final Argon2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    private void assertParameterSpecEquals(final Argon2DerivedKeyParameterSpec parameterSpec) {
+        assertNotNull(parameterSpec);
+        assertEquals(MEMORY, parameterSpec.getMemory());
+        assertEquals(ITERATIONS, parameterSpec.getIterations());
+        assertEquals(PARALLELISM, parameterSpec.getParallelism());
+
+        final byte[] salt = decoder.decode(ARGON2_SALT_STRING);
+        assertArrayEquals(salt, parameterSpec.getSalt());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProviderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProviderTest.java
new file mode 100644
index 0000000000..9ed86b070f
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/argon2/Argon2DerivedKeyProviderTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.argon2;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class Argon2DerivedKeyProviderTest {
+    private static final char[] PASSWORD = String.class.getSimpleName().toCharArray();
+
+    private static final int DERIVED_KEY_LENGTH = 16;
+
+    private static final String ALGORITHM = "AES";
+
+    private static final int MEMORY = 65536;
+
+    private static final int ITERATIONS = 2;
+
+    private static final int PARALLELISM = 1;
+
+    private static final String ARGON2_SALT_STRING = "QXJnb24yU2FsdFN0cmluZw";
+
+    private static final String PARAMETERS = String.format("$argon2id$v=19$m=%d,t=%d,p=%d$%s", MEMORY, ITERATIONS, PARALLELISM, ARGON2_SALT_STRING);
+
+    private static final String HASH = "6LOmoOXYJV0tXBJtxtD1Mg";
+
+    private static final String SERIALIZED = String.format("%s$%s", PARAMETERS, HASH);
+
+    Base64.Decoder decoder = Base64.getDecoder();
+
+    Argon2DerivedKeyProvider provider;
+
+    @BeforeEach
+    void setReader() {
+        provider = new Argon2DerivedKeyProvider();
+    }
+
+    @Test
+    void testGetDerivedKey() {
+        final byte[] salt = decoder.decode(ARGON2_SALT_STRING);
+        final Argon2DerivedKeyParameterSpec parameterSpec = new Argon2DerivedKeyParameterSpec(MEMORY, ITERATIONS, PARALLELISM, salt);
+        final DerivedKeySpec<Argon2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
+                PASSWORD,
+                DERIVED_KEY_LENGTH,
+                ALGORITHM,
+                parameterSpec
+        );
+
+        final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
+
+        assertNotNull(derivedKey);
+        assertEquals(ALGORITHM, derivedKey.getAlgorithm());
+        assertEquals(SERIALIZED, derivedKey.getSerialized());
+
+        final byte[] hashBytes = decoder.decode(HASH);
+        assertArrayEquals(hashBytes, derivedKey.getEncoded());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReaderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReaderTest.java
new file mode 100644
index 0000000000..3241fe521f
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyParameterSpecReaderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class BcryptDerivedKeyParameterSpecReaderTest {
+    private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
+
+    private static final int COST = 12;
+
+    private static final String SALT_ENCODED = "R9h/cIPz0gi.URNNX3kh2O";
+
+    private static final String HASH = "PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW";
+
+    private static final String SERIALIZED = String.format("$2a$%d$%s", COST, SALT_ENCODED);
+
+    private static final String SERIALIZED_HASH = String.format("%s%s", SERIALIZED, HASH);
+
+    BcryptDerivedKeyParameterSpecReader reader;
+
+    @BeforeEach
+    void setReader() {
+        reader = new BcryptDerivedKeyParameterSpecReader();
+    }
+
+    @Test
+    void testReadException() {
+        assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
+    }
+
+    @Test
+    void testRead() {
+        final byte[] serializedParameters = SERIALIZED.getBytes(StandardCharsets.US_ASCII);
+
+        final BcryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    @Test
+    void testReadHash() {
+        final byte[] serializedParameters = SERIALIZED_HASH.getBytes(StandardCharsets.US_ASCII);
+
+        final BcryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    private void assertParameterSpecEquals(final BcryptDerivedKeyParameterSpec parameterSpec) {
+        assertNotNull(parameterSpec);
+        assertEquals(COST, parameterSpec.getCost());
+
+        final byte[] salt = BcryptBase64Decoder.decode(SALT_ENCODED);
+        assertArrayEquals(salt, parameterSpec.getSalt());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProviderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProviderTest.java
new file mode 100644
index 0000000000..225c0872aa
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/bcrypt/BcryptDerivedKeyProviderTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.bcrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class BcryptDerivedKeyProviderTest {
+    private static final String PASSWORD = "abc123xyz";
+
+    private static final int COST = 12;
+
+    private static final String SALT_ENCODED = "R9h/cIPz0gi.URNNX3kh2O";
+
+    private static final String HASH = "PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW";
+
+    private static final String SERIALIZED = String.format("$2a$%d$%s%s", COST, SALT_ENCODED, HASH);
+
+    private static final Charset SERIALIZED_CHARACTER_SET = StandardCharsets.US_ASCII;
+
+    private static final int DERIVED_KEY_LENGTH = 16;
+
+    private static final String ALGORITHM = "AES";
+
+    private static final String DIGEST_ALGORITHM = "SHA-512";
+
+    BcryptDerivedKeyProvider provider;
+
+    @BeforeEach
+    void setProvider() {
+        provider = new BcryptDerivedKeyProvider();
+    }
+
+    @Test
+    void testGetDerivedKey() {
+        final byte[] salt = BcryptBase64Decoder.decode(SALT_ENCODED);
+        final BcryptDerivedKeyParameterSpec parameterSpec = new BcryptDerivedKeyParameterSpec(COST, salt);
+
+        final DerivedKeySpec<BcryptDerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
+                PASSWORD.toCharArray(),
+                DERIVED_KEY_LENGTH,
+                ALGORITHM,
+                parameterSpec
+        );
+
+        final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
+
+        assertNotNull(derivedKey);
+        assertEquals(ALGORITHM, derivedKey.getAlgorithm());
+        assertEquals(SERIALIZED, derivedKey.getSerialized());
+
+        final byte[] encodedHashBytes = HASH.getBytes(SERIALIZED_CHARACTER_SET);
+        final byte[] digestedDerivedKey = getDerivedKeyBytes(encodedHashBytes);
+        assertArrayEquals(digestedDerivedKey, derivedKey.getEncoded());
+    }
+
+    private byte[] getDerivedKeyBytes(final byte[] hash) {
+        final MessageDigest messageDigest = getMessageDigest();
+        final byte[] digested = messageDigest.digest(hash);
+        return Arrays.copyOf(digested, DERIVED_KEY_LENGTH);
+    }
+
+    private MessageDigest getMessageDigest() {
+        try {
+            return MessageDigest.getInstance(DIGEST_ALGORITHM);
+        } catch (final NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(DIGEST_ALGORITHM, e);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReaderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReaderTest.java
new file mode 100644
index 0000000000..59323d4cae
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyParameterSpecReaderTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.detection;
+
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class DetectedDerivedKeyParameterSpecReaderTest {
+
+    private static final String UNSPECIFIED_PARAMETERS = "unspecified";
+
+    private static final String ARGON2_PARAMETERS = "$argon2id$v=19$m=65536,t=2,p=1$QXJnb24yU2FsdFN0cmluZw";
+
+    private static final String BCRYPT_PARAMETERS = "$2a$12$R9h/cIPz0gi.URNNX3kh2O";
+
+    private static final String SCRYPT_PARAMETERS = "$s0$e0801$epIxT/h6HbbwHaehFnh/bw";
+
+    DetectedDerivedKeyParameterSpecReader reader;
+
+    @BeforeEach
+    void setReader() {
+        reader = new DetectedDerivedKeyParameterSpecReader();
+    }
+
+    @Test
+    void testArgon2() {
+        assertParameterSpecInstanceOf(Argon2DerivedKeyParameterSpec.class, ARGON2_PARAMETERS);
+    }
+
+    @Test
+    void testBcrypt() {
+        assertParameterSpecInstanceOf(BcryptDerivedKeyParameterSpec.class, BCRYPT_PARAMETERS);
+    }
+
+    @Test
+    void testScrypt() {
+        assertParameterSpecInstanceOf(ScryptDerivedKeyParameterSpec.class, SCRYPT_PARAMETERS);
+    }
+
+    @Test
+    void testPbkdf2() {
+        assertParameterSpecInstanceOf(Pbkdf2DerivedKeyParameterSpec.class, UNSPECIFIED_PARAMETERS);
+    }
+
+    private void assertParameterSpecInstanceOf(final Class<? extends DerivedKeyParameterSpec> parameterSpecClass, final String parameters) {
+        final byte[] serializedParameters = parameters.getBytes(StandardCharsets.UTF_8);
+
+        final DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertInstanceOf(parameterSpecClass, parameterSpec);
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProviderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProviderTest.java
new file mode 100644
index 0000000000..3f6b87eacd
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/detection/DetectedDerivedKeyProviderTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.detection;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.bcrypt.BcryptDerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.scrypt.ScryptDerivedKeyParameterSpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+class DetectedDerivedKeyProviderTest {
+
+    private static final char[] PASSWORD = {'w', 'o', 'r', 'd'};
+
+    private static final int DERIVED_KEY_LENGTH = 16;
+
+    private static final String ALGORITHM = "AES";
+
+    private static final byte[] SALT = {'N', 'i', 'F', 'i', 'S', 'A', 'L', 'T'};
+
+    private static final int MINIMUM_ITERATIONS = 1;
+
+    private static final int MINIMUM_ARGON2_MEMORY = 2;
+
+    private static final int MINIMUM_PARALLELISM = 1;
+
+    private static final int MINIMUM_BCRYPT_COST = 4;
+
+    private static final byte[] BCRYPT_SALT = {'S', 'I', 'X', 'T', 'E', 'E', 'N', 'B', 'Y', 'T', 'E', 'S', 'S', 'A', 'L', 'T'};
+
+    private static final int MINIMUM_SCRYPT_COST = 2;
+
+    private static final int SCRYPT_BLOCK_SIZE = 16;
+
+    @Mock
+    DerivedKeyParameterSpec unsupportedDerivedKeyParameterSpec;
+
+    DetectedDerivedKeyProvider provider;
+
+    @BeforeEach
+    void setProvider() {
+        provider = new DetectedDerivedKeyProvider();
+    }
+
+    @Test
+    void testArgon2() {
+        final Argon2DerivedKeyParameterSpec parameterSpec = new Argon2DerivedKeyParameterSpec(MINIMUM_ARGON2_MEMORY, MINIMUM_ITERATIONS, MINIMUM_PARALLELISM, SALT);
+        assertDerivedKey(parameterSpec);
+    }
+
+    @Test
+    void testBcrypt() {
+        final BcryptDerivedKeyParameterSpec parameterSpec = new BcryptDerivedKeyParameterSpec(MINIMUM_BCRYPT_COST, BCRYPT_SALT);
+        assertDerivedKey(parameterSpec);
+    }
+
+    @Test
+    void testPbkdf2() {
+        final Pbkdf2DerivedKeyParameterSpec parameterSpec = new Pbkdf2DerivedKeyParameterSpec(MINIMUM_ITERATIONS, SALT);
+        assertDerivedKey(parameterSpec);
+    }
+
+    @Test
+    void testScrypt() {
+        final ScryptDerivedKeyParameterSpec parameterSpec = new ScryptDerivedKeyParameterSpec(MINIMUM_SCRYPT_COST, SCRYPT_BLOCK_SIZE, MINIMUM_PARALLELISM, SALT);
+        assertDerivedKey(parameterSpec);
+    }
+
+    @Test
+    void testUnsupported() {
+        final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(unsupportedDerivedKeyParameterSpec);
+        assertThrows(UnsupportedOperationException.class, () -> provider.getDerivedKey(derivedKeySpec));
+    }
+
+    private <T extends DerivedKeyParameterSpec> DerivedKeySpec<T> getDerivedKeySpec(final T parameterSpec) {
+        return new StandardDerivedKeySpec<>(PASSWORD, DERIVED_KEY_LENGTH, ALGORITHM, parameterSpec);
+    }
+
+    private <T extends DerivedKeyParameterSpec> void assertDerivedKey(final T parameterSpec) {
+        final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(parameterSpec);
+
+        final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
+
+        assertNotNull(derivedKey);
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReaderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReaderTest.java
new file mode 100644
index 0000000000..c88868dc1c
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyParameterSpecReaderTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.pbkdf2;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class Pbkdf2DerivedKeyParameterSpecReaderTest {
+    private static final String STRING_PARAMETERS = String.class.getName();
+
+    Pbkdf2DerivedKeyParameterSpecReader reader;
+
+    @BeforeEach
+    void setReader() {
+        reader = new Pbkdf2DerivedKeyParameterSpecReader();
+    }
+
+    @Test
+    void testRead() {
+        final byte[] serializedParameters = STRING_PARAMETERS.getBytes(StandardCharsets.US_ASCII);
+
+        final Pbkdf2DerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertNotNull(parameterSpec);
+        assertEquals(Pbkdf2DerivedKeyParameterSpecReader.VERSION_0_5_0_ITERATIONS, parameterSpec.getIterations());
+        assertArrayEquals(serializedParameters, parameterSpec.getSalt());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProviderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProviderTest.java
new file mode 100644
index 0000000000..3c5fa01c97
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/pbkdf2/Pbkdf2DerivedKeyProviderTest.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.security.crypto.key.pbkdf2;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class Pbkdf2DerivedKeyProviderTest {
+    private static final char[] PASSWORD = String.class.getSimpleName().toCharArray();
+
+    private static final byte[] SALT = String.class.getName().getBytes(StandardCharsets.UTF_8);
+
+    private static final int DERIVED_KEY_LENGTH = 16;
+
+    private static final String SERIALIZED = "yeoZx0KZR9IzcuaOWOJbFg";
+
+    private static final String ALGORITHM = "AES";
+
+    private static final int ITERATIONS = 160000;
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    Pbkdf2DerivedKeyProvider provider;
+
+    @BeforeEach
+    void setReader() {
+        provider = new Pbkdf2DerivedKeyProvider();
+    }
+
+    @Test
+    void testGetDerivedKey() {
+        final Pbkdf2DerivedKeyParameterSpec parameterSpec = new Pbkdf2DerivedKeyParameterSpec(ITERATIONS, SALT);
+        final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
+                PASSWORD,
+                DERIVED_KEY_LENGTH,
+                ALGORITHM,
+                parameterSpec
+        );
+
+        final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
+
+        assertNotNull(derivedKey);
+        assertEquals(ALGORITHM, derivedKey.getAlgorithm());
+        assertEquals(SERIALIZED, derivedKey.getSerialized());
+
+        final byte[] decoded = decoder.decode(SERIALIZED);
+        assertArrayEquals(decoded, derivedKey.getEncoded());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReaderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReaderTest.java
new file mode 100644
index 0000000000..2c82d578b0
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyParameterSpecReaderTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.scrypt;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ScryptDerivedKeyParameterSpecReaderTest {
+    private static final byte[] STRING_PARAMETERS = String.class.getSimpleName().getBytes(StandardCharsets.US_ASCII);
+
+    private static final int COST = 16384;
+
+    private static final int BLOCK_SIZE = 8;
+
+    private static final int PARALLELIZATION = 1;
+
+    private static final String SALT_ENCODED = "epIxT/h6HbbwHaehFnh/bw";
+
+    private static final String HASH = "7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0";
+
+    private static final String SERIALIZED = String.format("$s0$e0801$%s", SALT_ENCODED);
+
+    private static final String SERIALIZED_HASH = String.format("%s$%s", SERIALIZED, HASH);
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    ScryptDerivedKeyParameterSpecReader reader;
+
+    @BeforeEach
+    void setReader() {
+        reader = new ScryptDerivedKeyParameterSpecReader();
+    }
+
+    @Test
+    void testReadException() {
+        assertThrows(IllegalArgumentException.class, () -> reader.read(STRING_PARAMETERS));
+    }
+
+    @Test
+    void testRead() {
+        final byte[] serializedParameters = SERIALIZED.getBytes(StandardCharsets.US_ASCII);
+
+        final ScryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    @Test
+    void testReadHash() {
+        final byte[] serializedParameters = SERIALIZED_HASH.getBytes(StandardCharsets.US_ASCII);
+
+        final ScryptDerivedKeyParameterSpec parameterSpec = reader.read(serializedParameters);
+
+        assertParameterSpecEquals(parameterSpec);
+    }
+
+    private void assertParameterSpecEquals(final ScryptDerivedKeyParameterSpec parameterSpec) {
+        assertNotNull(parameterSpec);
+        assertEquals(COST, parameterSpec.getCost());
+        assertEquals(BLOCK_SIZE, parameterSpec.getBlockSize());
+        assertEquals(PARALLELIZATION, parameterSpec.getParallelization());
+
+        final byte[] salt = decoder.decode(SALT_ENCODED);
+        assertArrayEquals(salt, parameterSpec.getSalt());
+    }
+}
diff --git a/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProviderTest.java b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProviderTest.java
new file mode 100644
index 0000000000..94f846111c
--- /dev/null
+++ b/nifi-commons/nifi-security-crypto-key/src/test/java/org/apache/nifi/security/crypto/key/scrypt/ScryptDerivedKeyProviderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.crypto.key.scrypt;
+
+import org.apache.nifi.security.crypto.key.DerivedKey;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class ScryptDerivedKeyProviderTest {
+    private static final String PASSWORD = "secret";
+
+    private static final int COST = 16384;
+
+    private static final int BLOCK_SIZE = 8;
+
+    private static final int PARALLELIZATION = 1;
+
+    private static final String SALT_ENCODED = "epIxT/h6HbbwHaehFnh/bw";
+
+    private static final String HASH = "7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0";
+
+    private static final String SERIALIZED = String.format("$s0$e0801$%s$%s", SALT_ENCODED, HASH);
+
+    private static final int DERIVED_KEY_LENGTH = 32;
+
+    private static final String ALGORITHM = "AES";
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    ScryptDerivedKeyProvider provider;
+
+    @BeforeEach
+    void setProvider() {
+        provider = new ScryptDerivedKeyProvider();
+    }
+
+    @Test
+    void testGetDerivedKey() {
+        final byte[] salt = decoder.decode(SALT_ENCODED);
+        final ScryptDerivedKeyParameterSpec parameterSpec = new ScryptDerivedKeyParameterSpec(COST, BLOCK_SIZE, PARALLELIZATION, salt);
+
+        final DerivedKeySpec<ScryptDerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(
+                PASSWORD.toCharArray(),
+                DERIVED_KEY_LENGTH,
+                ALGORITHM,
+                parameterSpec
+        );
+
+        final DerivedKey derivedKey = provider.getDerivedKey(derivedKeySpec);
+
+        assertNotNull(derivedKey);
+        assertEquals(ALGORITHM, derivedKey.getAlgorithm());
+        assertEquals(SERIALIZED, derivedKey.getSerialized());
+
+        final byte[] hashBytes = decoder.decode(HASH);
+        assertArrayEquals(hashBytes, derivedKey.getEncoded());
+    }
+}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 4e8ad42c0a..8a212b5b16 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -53,6 +53,7 @@
         <module>nifi-record-path</module>
         <module>nifi-repository-encryption</module>
         <module>nifi-schema-utils</module>
+        <module>nifi-security-crypto-key</module>
         <module>nifi-security-kerberos-api</module>
         <module>nifi-security-kerberos</module>
         <module>nifi-security-kms</module>
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-nar/pom.xml b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-nar/pom.xml
new file mode 100644
index 0000000000..7df58d8e9e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-nar/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-cipher-bundle</artifactId>
+        <version>1.20.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>nifi-cipher-nar</artifactId>
+    <packaging>nar</packaging>
+    <properties>
+        <maven.javadoc.skip>true</maven.javadoc.skip>
+        <source.skip>true</source.skip>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-cipher-processors</artifactId>
+            <version>1.20.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-standard-services-api-nar</artifactId>
+            <version>1.20.0-SNAPSHOT</version>
+            <type>nar</type>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/pom.xml b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/pom.xml
new file mode 100644
index 0000000000..a06a318a7f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-cipher-bundle</artifactId>
+        <version>1.20.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>nifi-cipher-processors</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-utils</artifactId>
+            <version>1.20.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-crypto-key</artifactId>
+            <version>1.20.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherAttributeKey.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherAttributeKey.java
new file mode 100644
index 0000000000..486bec300d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherAttributeKey.java
@@ -0,0 +1,29 @@
+/*
+ * 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.cipher;
+
+/**
+ * Cipher FlowFile Attribute Key
+ */
+class CipherAttributeKey {
+
+    static final String PBE_SCHEME = "pbe.scheme";
+
+    static final String PBE_SYMMETRIC_CIPHER = "pbe.symmetric.cipher";
+
+    static final String PBE_DIGEST_ALGORITHM = "pbe.digest.algorithm";
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherException.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherException.java
new file mode 100644
index 0000000000..9aa2c95691
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/CipherException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.cipher;
+
+/**
+ * Cipher Exception for categorizing cryptographic failures
+ */
+public class CipherException extends RuntimeException {
+    /**
+     * Cipher Exception constructor with message and cause
+     *
+     * @param message Exception message
+     * @param cause Exception cause
+     */
+    public CipherException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContent.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContent.java
new file mode 100644
index 0000000000..23057f3719
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContent.java
@@ -0,0 +1,364 @@
+/*
+ * 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.cipher;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+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.PropertyDescriptor;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmMode;
+import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmPadding;
+import org.apache.nifi.processors.cipher.algorithm.SymmetricCipher;
+import org.apache.nifi.processors.cipher.encoded.EncodedDelimiter;
+import org.apache.nifi.processors.cipher.encoded.KeySpecificationFormat;
+import org.apache.nifi.processors.cipher.io.DecryptStreamCallback;
+import org.apache.nifi.security.crypto.key.DerivedKeyParameterSpec;
+import org.apache.nifi.security.crypto.key.DerivedKeySpec;
+import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
+import org.apache.nifi.security.crypto.key.detection.DetectedDerivedKeyParameterSpecReader;
+import org.apache.nifi.security.crypto.key.detection.DetectedDerivedKeyProvider;
+import org.apache.nifi.security.crypto.key.io.ByteBufferSearch;
+import org.bouncycastle.util.encoders.Hex;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SideEffectFree
+@SupportsBatching
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"cryptography", "decipher", "decrypt", "AES", "Argon2", "bcrypt", "scrypt", "PBKDF2"})
+@CapabilityDescription("Decrypt content encrypted with AES and encoded according conventions added in NiFi 0.5.0 for the EncryptContent Processor. " +
+        "The Processor reads the first 256 bytes to determine the presence of a cryptographic salt based on finding the 'NiFiSALT' delimiter. " +
+        "The salt is not present for content encrypted with a raw hexadecimal key. " +
+        "The Processor determines the presence of the initialization vector based on finding the 'NiFiIV' delimiter." +
+        "The salt format indicates the Key Derivation Function that the Processor uses to generate a secret key based on a configured password. " +
+        "The Processor derives keys with a size of 128 bits according to the conventions implemented in NiFi 0.5.0."
+)
+public class DecryptContent extends AbstractProcessor {
+
+    static final PropertyDescriptor CIPHER_ALGORITHM_MODE = new PropertyDescriptor.Builder()
+            .name("cipher-algorithm-mode")
+            .displayName("Cipher Algorithm Mode")
+            .description("Block cipher mode of operation for decryption using the Advanced Encryption Standard")
+            .required(true)
+            .allowableValues(CipherAlgorithmMode.class)
+            .defaultValue(CipherAlgorithmMode.GCM.getValue())
+            .build();
+
+    static final PropertyDescriptor CIPHER_ALGORITHM_PADDING = new PropertyDescriptor.Builder()
+            .name("cipher-algorithm-padding")
+            .displayName("Cipher Algorithm Padding")
+            .description("Padding specification used in cipher operation for decryption using the Advanced Encryption Standard")
+            .required(true)
+            .allowableValues(CipherAlgorithmPadding.class)
+            .defaultValue(CipherAlgorithmPadding.NO_PADDING.getValue())
+            .build();
+
+    static final PropertyDescriptor KEY_SPECIFICATION_FORMAT = new PropertyDescriptor.Builder()
+            .name("key-specification-format")
+            .displayName("Key Specification Format")
+            .description("Format describing the configured Key Specification")
+            .required(true)
+            .allowableValues(KeySpecificationFormat.class)
+            .defaultValue(KeySpecificationFormat.PASSWORD.getValue())
+            .build();
+
+    static final PropertyDescriptor KEY_SPECIFICATION = new PropertyDescriptor.Builder()
+            .name("key-specification")
+            .displayName("Key Specification")
+            .description("Specification providing the raw secret key or a password from which to derive a secret key")
+            .required(true)
+            .sensitive(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+    static final Relationship SUCCESS = new Relationship.Builder()
+            .name("success")
+            .description("Decryption succeeded")
+            .build();
+
+    static final Relationship FAILURE = new Relationship.Builder()
+            .name("failure")
+            .description("Decryption failed")
+            .build();
+
+    private static final List<PropertyDescriptor> DESCRIPTORS = Collections.unmodifiableList(Arrays.asList(
+            CIPHER_ALGORITHM_MODE,
+            CIPHER_ALGORITHM_PADDING,
+            KEY_SPECIFICATION_FORMAT,
+            KEY_SPECIFICATION
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            SUCCESS,
+            FAILURE
+    )));
+
+    private static final SymmetricCipher SYMMETRIC_CIPHER = SymmetricCipher.AES;
+
+    private static final String TRANSFORMATION_FORMAT = "%s/%s/%s";
+
+    /**
+     * Get Supported Property Descriptors
+     *
+     * @return Processor Property Descriptors
+     */
+    @Override
+    public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return DESCRIPTORS;
+    }
+
+    /**
+     * Get Relationships
+     *
+     * @return Processor Relationships
+     */
+    @Override
+    public Set<Relationship> getRelationships() {
+        return RELATIONSHIPS;
+    }
+
+    /**
+     * On Trigger decrypts Flow File contents using configured properties
+     *
+     * @param context Process Context
+     * @param session Process Session
+     */
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession session) {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String specificationFormat = context.getProperty(KEY_SPECIFICATION_FORMAT).getValue();
+        final KeySpecificationFormat keySpecificationFormat = KeySpecificationFormat.valueOf(specificationFormat);
+
+        final String cipherTransformation = getCipherTransformation(context);
+        final Cipher cipher = getCipher(cipherTransformation);
+        final String algorithmMode = context.getProperty(CIPHER_ALGORITHM_MODE).getValue();
+        final CipherAlgorithmMode cipherAlgorithmMode = CipherAlgorithmMode.valueOf(algorithmMode);
+
+        final KeySpec keySpec = getKeySpec(context, keySpecificationFormat);
+        final StreamCallback callback = new DecryptCallback(cipher, cipherAlgorithmMode, keySpec);
+        try {
+            flowFile = session.write(flowFile, callback);
+            getLogger().debug("Decryption completed using [{}] {}", cipherTransformation, flowFile);
+            session.transfer(flowFile, SUCCESS);
+        } catch (final RuntimeException e) {
+            getLogger().error("Decryption failed using [{}] {}", cipherTransformation, flowFile, e);
+            session.transfer(flowFile, FAILURE);
+        }
+    }
+
+    private String getCipherTransformation(final ProcessContext context) {
+        final String algorithmMode = context.getProperty(CIPHER_ALGORITHM_MODE).getValue();
+        final String algorithmPadding = context.getProperty(CIPHER_ALGORITHM_PADDING).getValue();
+        return String.format(TRANSFORMATION_FORMAT, SYMMETRIC_CIPHER.getValue(), algorithmMode, algorithmPadding);
+    }
+
+    private Cipher getCipher(final String transformation) {
+        try {
+            return Cipher.getInstance(transformation);
+        } catch (final GeneralSecurityException e) {
+            final String message = String.format("Cipher [%s] not found", transformation);
+            throw new CipherException(message, e);
+        }
+    }
+
+    private KeySpec getKeySpec(final ProcessContext context, final KeySpecificationFormat keySpecificationFormat) {
+        final KeySpec keySpec;
+        final String keySpecification = context.getProperty(KEY_SPECIFICATION).getValue();
+        if (KeySpecificationFormat.RAW == keySpecificationFormat) {
+            final byte[] decodedKey = Hex.decode(keySpecification);
+            keySpec = new SecretKeySpec(decodedKey, SYMMETRIC_CIPHER.getValue());
+        } else {
+            final char[] password = keySpecification.toCharArray();
+            keySpec = new PBEKeySpec(password);
+        }
+        return keySpec;
+    }
+
+    private interface SerializedParameterSpec {
+
+        byte[] getParameters();
+    }
+
+    private static class GCMSerializedParameterSpec extends GCMParameterSpec implements SerializedParameterSpec {
+
+        private static final int GCM_TAG_LENGTH_BITS = 128;
+
+        private final byte[] parameters;
+
+        private GCMSerializedParameterSpec(final byte[] iv, final byte[] parameters) {
+            super(GCM_TAG_LENGTH_BITS, iv);
+            this.parameters = parameters;
+        }
+
+        @Override
+        public byte[] getParameters() {
+            return parameters;
+        }
+    }
+
+    private static class IvSerializedParameterSpec extends IvParameterSpec implements SerializedParameterSpec {
+
+        private final byte[] parameters;
+
+        private IvSerializedParameterSpec(final byte[] iv, final byte[] parameters) {
+            super(iv);
+            this.parameters = parameters;
+        }
+
+        @Override
+        public byte[] getParameters() {
+            return parameters;
+        }
+    }
+
+    private static class DecryptCallback extends DecryptStreamCallback {
+
+        private static final int PARAMETERS_BUFFER_LENGTH = 256;
+
+        private static final int DERIVED_KEY_LENGTH_BYTES = 16;
+
+        private static final int END_OF_FILE = -1;
+
+        private static final byte[] EMPTY_BYTES = {};
+
+        private static final DetectedDerivedKeyParameterSpecReader parameterSpecReader = new DetectedDerivedKeyParameterSpecReader();
+
+        private static final DetectedDerivedKeyProvider derivedKeyProvider = new DetectedDerivedKeyProvider();
+
+        private final CipherAlgorithmMode cipherAlgorithmMode;
+
+        private final KeySpec keySpec;
+
+        private DecryptCallback(
+                final Cipher cipher,
+                final CipherAlgorithmMode cipherAlgorithmMode,
+                final KeySpec keySpec
+        ) {
+            super(cipher, PARAMETERS_BUFFER_LENGTH);
+            this.cipherAlgorithmMode = cipherAlgorithmMode;
+            this.keySpec = keySpec;
+        }
+
+        /**
+         * Read Algorithm Parameters including optional salt bytes for derived keys and required initialization vector bytes
+         *
+         * @param parameterBuffer Buffer of parameter bytes
+         * @return Algorithm Parameters Specification based on Cipher Algorithm Mode and parsed bytes
+         */
+        @Override
+        protected AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer) {
+            final AlgorithmParameterSpec spec;
+
+            final byte[] salt = readDelimitedBytes(parameterBuffer, EncodedDelimiter.SALT);
+            final byte[] iv = readDelimitedBytes(parameterBuffer, EncodedDelimiter.IV);
+
+            if (CipherAlgorithmMode.GCM == cipherAlgorithmMode) {
+                spec = new GCMSerializedParameterSpec(iv, salt);
+            } else {
+                spec = new IvSerializedParameterSpec(iv, salt);
+            }
+
+            return spec;
+        }
+
+        /**
+         * Get Secret Key for cipher operations using either configured hexadecimal key or key derived from password and parameters
+         *
+         * @param algorithmParameterSpec Algorithm Parameters Specification
+         * @return Secret Key for decryption
+         */
+        @Override
+        protected Key getKey(final AlgorithmParameterSpec algorithmParameterSpec) {
+            final Key key;
+
+            if (keySpec instanceof SecretKeySpec) {
+                key = (SecretKeySpec) keySpec;
+            } else if (algorithmParameterSpec instanceof SerializedParameterSpec) {
+                final SerializedParameterSpec serializedParameterSpec = (SerializedParameterSpec) algorithmParameterSpec;
+                final byte[] parameters = serializedParameterSpec.getParameters();
+                key = getDerivedKey(parameters);
+            } else {
+                final String message = String.format("Key Derivation Function Parameters not provided [%s]", algorithmParameterSpec.getClass());
+                throw new IllegalArgumentException(message);
+            }
+
+            return key;
+        }
+
+        private Key getDerivedKey(final byte[] parameters) {
+            final DerivedKeyParameterSpec derivedKeyParameterSpec = parameterSpecReader.read(parameters);
+            final DerivedKeySpec<DerivedKeyParameterSpec> derivedKeySpec = getDerivedKeySpec(derivedKeyParameterSpec);
+            return derivedKeyProvider.getDerivedKey(derivedKeySpec);
+        }
+
+        private DerivedKeySpec<DerivedKeyParameterSpec> getDerivedKeySpec(final DerivedKeyParameterSpec parameterSpec) {
+            final PBEKeySpec pbeKeySpec = (PBEKeySpec) keySpec;
+            final char[] password = pbeKeySpec.getPassword();
+            return new StandardDerivedKeySpec<>(
+                    password,
+                    DERIVED_KEY_LENGTH_BYTES,
+                    SYMMETRIC_CIPHER.getValue(),
+                    parameterSpec
+            );
+        }
+
+        private byte[] readDelimitedBytes(final ByteBuffer buffer, final EncodedDelimiter encodedDelimiter) {
+            final byte[] delimiter = encodedDelimiter.getDelimiter();
+            final int delimiterIndex = ByteBufferSearch.indexOf(buffer, delimiter);
+
+            final byte[] delimitedBytes;
+            if (delimiterIndex == END_OF_FILE) {
+                delimitedBytes = EMPTY_BYTES;
+            } else {
+                final int delimitedLength = delimiterIndex - buffer.position();
+                delimitedBytes = new byte[delimitedLength];
+                buffer.get(delimitedBytes);
+
+                final int newPosition = delimiterIndex + delimiter.length;
+                buffer.position(newPosition);
+            }
+            return delimitedBytes;
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContentCompatibility.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContentCompatibility.java
new file mode 100644
index 0000000000..e13f284ef9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/DecryptContentCompatibility.java
@@ -0,0 +1,295 @@
+/*
+ * 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.cipher;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.SideEffectFree;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
+import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeEncryptionScheme;
+import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeKeyDerivationStrategy;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.cipher.io.DecryptStreamCallback;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+@SideEffectFree
+@SupportsBatching
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"cryptography", "decipher", "decrypt", "Jasypt", "OpenSSL", "PKCS5", "PBES1"})
+@CapabilityDescription("Decrypt content using password-based encryption schemes with legacy algorithms supporting historical compatibility modes.")
+@WritesAttributes({
+        @WritesAttribute(attribute = CipherAttributeKey.PBE_SCHEME, description = "Password-Based Encryption Scheme"),
+        @WritesAttribute(attribute = CipherAttributeKey.PBE_SYMMETRIC_CIPHER, description = "Password-Based Encryption Block Cipher"),
+        @WritesAttribute(attribute = CipherAttributeKey.PBE_DIGEST_ALGORITHM, description = "Password-Based Encryption Digest Algorithm"),
+})
+public class DecryptContentCompatibility extends AbstractProcessor {
+
+    static final PropertyDescriptor ENCRYPTION_SCHEME = new PropertyDescriptor.Builder()
+            .name("encryption-scheme")
+            .displayName("Encryption Scheme")
+            .description("Password-Based Encryption Scheme including PBES1 described in RFC 8018, and others defined according to PKCS12 and Bouncy Castle implementations")
+            .required(true)
+            .allowableValues(CompatibilityModeEncryptionScheme.class)
+            .build();
+
+    static final PropertyDescriptor KEY_DERIVATION_STRATEGY = new PropertyDescriptor.Builder()
+            .name("key-derivation-strategy")
+            .displayName("Key Derivation Strategy")
+            .description("Strategy for reading salt from encoded contents and deriving the decryption key according to the number of function iterations")
+            .required(true)
+            .allowableValues(CompatibilityModeKeyDerivationStrategy.class)
+            .build();
+
+    static final PropertyDescriptor PASSWORD = new PropertyDescriptor.Builder()
+            .name("password")
+            .displayName("Password")
+            .description("Password required for Password-Based Encryption Schemes")
+            .required(true)
+            .sensitive(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+    static final Relationship SUCCESS = new Relationship.Builder()
+            .name("success")
+            .description("Decryption succeeded")
+            .build();
+
+    static final Relationship FAILURE = new Relationship.Builder()
+            .name("failure")
+            .description("Decryption failed")
+            .build();
+
+    private static final List<PropertyDescriptor> DESCRIPTORS = Collections.unmodifiableList(Arrays.asList(
+            ENCRYPTION_SCHEME,
+            KEY_DERIVATION_STRATEGY,
+            PASSWORD
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            SUCCESS,
+            FAILURE
+    )));
+
+    private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
+
+    /**
+     * Get Supported Property Descriptors
+     *
+     * @return Processor Property Descriptors
+     */
+    @Override
+    public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return DESCRIPTORS;
+    }
+
+    /**
+     * Get Relationships
+     *
+     * @return Processor Relationships
+     */
+    @Override
+    public Set<Relationship> getRelationships() {
+        return RELATIONSHIPS;
+    }
+
+    /**
+     * On Trigger decrypts Flow File contents using configured properties
+     *
+     * @param context Process Context
+     * @param session Process Session
+     */
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession session) {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String scheme = context.getProperty(ENCRYPTION_SCHEME).getValue();
+        final CompatibilityModeEncryptionScheme encryptionScheme = getEncryptionScheme(scheme);
+        final Cipher cipher = getCipher(scheme);
+
+        final char[] password = context.getProperty(PASSWORD).getValue().toCharArray();
+        final PBEKeySpec keySpec = new PBEKeySpec(password);
+
+        final String strategy = context.getProperty(KEY_DERIVATION_STRATEGY).getValue();
+        final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.valueOf(strategy);
+        final StreamCallback callback = new DecryptCallback(cipher, keySpec, keyDerivationStrategy);
+
+        final Map<String, String> attributes = new LinkedHashMap<>();
+        attributes.put(CipherAttributeKey.PBE_SCHEME, encryptionScheme.getValue());
+        attributes.put(CipherAttributeKey.PBE_SYMMETRIC_CIPHER, encryptionScheme.getSymmetricCipher().getValue());
+        attributes.put(CipherAttributeKey.PBE_DIGEST_ALGORITHM, encryptionScheme.getDigestAlgorithm().getValue());
+
+        try {
+            flowFile = session.write(flowFile, callback);
+            flowFile = session.putAllAttributes(flowFile, attributes);
+            getLogger().debug("Decryption completed using [{}] {}", scheme, flowFile);
+            session.transfer(flowFile, SUCCESS);
+        } catch (final RuntimeException e) {
+            getLogger().error("Decryption failed using [{}] {}", scheme, flowFile, e);
+            session.transfer(flowFile, FAILURE);
+        }
+    }
+
+    private CompatibilityModeEncryptionScheme getEncryptionScheme(final String scheme) {
+        final Optional<CompatibilityModeEncryptionScheme> encryptionSchemeFound = Arrays.stream(CompatibilityModeEncryptionScheme.values())
+                .filter(encryptionScheme -> encryptionScheme.getValue().equals(scheme))
+                .findFirst();
+        return encryptionSchemeFound.orElseThrow(() -> new IllegalArgumentException(String.format("Encryption Scheme [%s] not found", scheme)));
+    }
+
+    private Cipher getCipher(final String transformation) {
+        try {
+            return Cipher.getInstance(transformation, BOUNCY_CASTLE_PROVIDER);
+        } catch (final GeneralSecurityException e) {
+            final String message = String.format("Cipher [%s] not found", transformation);
+            throw new CipherException(message, e);
+        }
+    }
+
+    private static class DecryptCallback extends DecryptStreamCallback {
+        private static final byte[] EMPTY_SALT = {};
+
+        private static final int BLOCK_SIZE_UNDEFINED = 0;
+
+        private final String cipherAlgorithm;
+
+        private final int cipherBlockSize;
+
+        private final PBEKeySpec keySpec;
+
+        private final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy;
+
+        private DecryptCallback(
+                final Cipher cipher,
+                final PBEKeySpec keySpec,
+                final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy
+        ) {
+            super(cipher, keyDerivationStrategy.getSaltBufferLength());
+            this.cipherAlgorithm = cipher.getAlgorithm();
+            this.cipherBlockSize = cipher.getBlockSize();
+            this.keySpec = keySpec;
+            this.keyDerivationStrategy = keyDerivationStrategy;
+        }
+
+        /**
+         * Get Secret Key generated based on configured key specification
+         *
+         * @param algorithmParameterSpec Algorithm Parameters Specification not used
+         * @return Secret Key
+         */
+        @Override
+        protected Key getKey(final AlgorithmParameterSpec algorithmParameterSpec) {
+            final SecretKeyFactory secretKeyFactory = getSecretKeyFactory();
+            try {
+                return secretKeyFactory.generateSecret(keySpec);
+            } catch (final InvalidKeySpecException e) {
+                final String message = String.format("Generate Secret Key Algorithm [%s] invalid specification", cipherAlgorithm);
+                throw new CipherException(message, e);
+            }
+        }
+
+        /**
+         * Read salt bytes based on configured key derivation strategy
+         *
+         * @param parameterBuffer Buffer of parameter bytes
+         * @return Password-Based Encryption specification with salt and configured iterations
+         */
+        @Override
+        protected AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer) {
+            final byte[] salt = readSalt(parameterBuffer);
+            return new PBEParameterSpec(salt, keyDerivationStrategy.getIterations());
+        }
+
+        private byte[] readSalt(final ByteBuffer byteBuffer) {
+            final byte[] salt;
+
+            if (CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY == keyDerivationStrategy) {
+                salt = readSaltOpenSsl(byteBuffer);
+            } else {
+                salt = readSaltStandard(byteBuffer);
+            }
+
+            return salt;
+        }
+
+        private byte[] readSaltOpenSsl(final ByteBuffer byteBuffer) {
+            final byte[] salt;
+
+            final int saltHeaderLength = keyDerivationStrategy.getSaltHeader().length;
+            final byte[] saltHeader = new byte[saltHeaderLength];
+            byteBuffer.get(saltHeader);
+
+            if (MessageDigest.isEqual(keyDerivationStrategy.getSaltHeader(), saltHeader)) {
+                salt = new byte[keyDerivationStrategy.getSaltStandardLength()];
+                byteBuffer.get(salt);
+            } else {
+                salt = EMPTY_SALT;
+                byteBuffer.rewind();
+            }
+
+            return salt;
+        }
+
+        private byte[] readSaltStandard(final ByteBuffer byteBuffer) {
+            final int saltLength = cipherBlockSize == BLOCK_SIZE_UNDEFINED ? keyDerivationStrategy.getSaltStandardLength() : cipherBlockSize;
+            final byte[] salt = new byte[saltLength];
+            byteBuffer.get(salt);
+            return salt;
+        }
+
+        private SecretKeyFactory getSecretKeyFactory() {
+            try {
+                return SecretKeyFactory.getInstance(cipherAlgorithm, BOUNCY_CASTLE_PROVIDER);
+            } catch (final NoSuchAlgorithmException e) {
+                final String message = String.format("Secret Key Factory Algorithm [%s] not found", cipherAlgorithm);
+                throw new CipherException(message, e);
+            }
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmMode.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmMode.java
new file mode 100644
index 0000000000..379c255feb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmMode.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cipher.algorithm;
+
+import org.apache.nifi.components.DescribedValue;
+
+/**
+ * Cipher Algorithm Modes as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
+ */
+public enum CipherAlgorithmMode implements DescribedValue {
+    CBC("Cipher Blocking Chaining Mode"),
+
+    CTR("Counter Mode"),
+
+    GCM("Galois/Counter Mode supporting Authenticated Encryption with Associated Data");
+
+    private final String description;
+
+    CipherAlgorithmMode(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmPadding.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmPadding.java
new file mode 100644
index 0000000000..67cb607093
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/CipherAlgorithmPadding.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cipher.algorithm;
+
+import org.apache.nifi.components.DescribedValue;
+
+/**
+ * Cipher Algorithm Paddings as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
+ */
+public enum CipherAlgorithmPadding implements DescribedValue {
+    /** No Padding */
+    NO_PADDING("NoPadding"),
+
+    /** PKCS5 Padding described in RFC 8018 */
+    PKCS5_PADDING("PKCS5Padding");
+
+    private final String padding;
+
+    CipherAlgorithmPadding(final String padding) {
+        this.padding = padding;
+    }
+
+    @Override
+    public String getValue() {
+        return padding;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return padding;
+    }
+
+    @Override
+    public String getDescription() {
+        return padding;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/DigestAlgorithm.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/DigestAlgorithm.java
new file mode 100644
index 0000000000..88eefdd278
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/DigestAlgorithm.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cipher.algorithm;
+
+import org.apache.nifi.components.DescribedValue;
+
+/**
+ * Digest Algorithm as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
+ */
+public enum DigestAlgorithm implements DescribedValue {
+    MD5("MD5", "Message Digest Algorithm 5"),
+
+    SHA1("SHA-1", "Secure Hash Algorithm 1"),
+
+    SHA256("SHA-256", "Secure Hash Algorithm 2 with 256 bits");
+
+    private String name;
+
+    private String description;
+
+    DigestAlgorithm(final String name, final String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name;
+    }
+
+    @Override
+    public String getDescription() {
+        return name;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/SymmetricCipher.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/SymmetricCipher.java
new file mode 100644
index 0000000000..f1b2b739a7
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/algorithm/SymmetricCipher.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cipher.algorithm;
+
+import org.apache.nifi.components.DescribedValue;
+
+/**
+ * Symmetric Cipher Algorithm Names as enumerated in Java Cryptography Architecture Standard Algorithm Name Documentation
+ */
+public enum SymmetricCipher implements DescribedValue {
+    AES("AES", "Advanced Encryption Standard defined in FIPS 197"),
+
+    DES("DES", "Data Encryption Standard defined in FIPS 46-3 and withdrawn in 2005"),
+
+    DESEDE("DESede", "Triple Data Encryption Standard also known as 3DES and deprecated in 2023"),
+
+    RC2("RC2", "RSA Rivest Cipher 2 defined in RFC 2268"),
+
+    RC4("RC4", "RSA Rivest Cipher 4"),
+
+    TWOFISH("TWOFISH", "Twofish Block Cipher");
+
+    private final String name;
+
+    private final String description;
+
+    SymmetricCipher(final String name, final String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeEncryptionScheme.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeEncryptionScheme.java
new file mode 100644
index 0000000000..92efaedb16
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeEncryptionScheme.java
@@ -0,0 +1,216 @@
+/*
+ * 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.cipher.compatibility;
+
+import org.apache.nifi.processors.cipher.algorithm.SymmetricCipher;
+import org.apache.nifi.processors.cipher.algorithm.DigestAlgorithm;
+import org.apache.nifi.components.DescribedValue;
+
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.AES;
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.DES;
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.DESEDE;
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.RC2;
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.RC4;
+import static org.apache.nifi.processors.cipher.algorithm.SymmetricCipher.TWOFISH;
+
+/**
+ * Compatibility Mode Encryption Schemes supporting decryption using legacy algorithms such as PBES1 defined in RFC 8018 Section 6.1
+ */
+public enum CompatibilityModeEncryptionScheme implements DescribedValue {
+    PBE_WITH_MD5_AND_AES_CBC_128(
+            "PBEWITHMD5AND128BITAES-CBC-OPENSSL",
+            DigestAlgorithm.MD5,
+            AES,
+            "PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
+    ),
+
+    PBE_WITH_MD5_AND_AES_CBC_192(
+            "PBEWITHMD5AND192BITAES-CBC-OPENSSL",
+            DigestAlgorithm.MD5,
+            AES,
+            "PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
+    ),
+
+    PBE_WITH_MD5_AND_AES_CBC_256(
+            "PBEWITHMD5AND256BITAES-CBC-OPENSSL",
+            DigestAlgorithm.MD5,
+            AES,
+            "PKCS12 with MD5 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
+    ),
+
+    PBE_WITH_MD5_AND_DES(
+            "PBEWITHMD5ANDDES",
+            DigestAlgorithm.MD5,
+            DES,
+            "PKCS5 Scheme 1 with MD5 digest and Data Encryption Standard 64 bit keys. OID 1.2.840.113549.1.5.3"
+    ),
+
+    PBE_WITH_MD5_AND_RC2(
+            "PBEWITHMD5ANDRC2",
+            DigestAlgorithm.MD5,
+            RC2,
+            "PKCS Scheme 1 with MD5 digest and Rivest Cipher 2. OID 1.2.840.113549.1.5.6"
+    ),
+
+    PBE_WITH_SHA1_AND_AES_CBC_128(
+            "PBEWITHSHAAND128BITAES-CBC-BC",
+            DigestAlgorithm.SHA1,
+            AES,
+            "PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
+    ),
+
+    PBE_WITH_SHA1_AND_AES_CBC_192(
+            "PBEWITHSHAAND192BITAES-CBC-BC",
+            DigestAlgorithm.SHA1,
+            AES,
+            "PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
+    ),
+
+    PBE_WITH_SHA1_AND_AES_CBC_256(
+            "PBEWITHSHAAND256BITAES-CBC-BC",
+            DigestAlgorithm.SHA1,
+            AES,
+            "PKCS12 with SHA-1 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
+    ),
+
+    PBE_WITH_SHA1_AND_DES(
+            "PBEWITHSHA1ANDDES",
+            DigestAlgorithm.SHA1,
+            DES,
+            "PKCS5 Scheme 1 with SHA-1 digest and Data Encryption Standard. OID 1.2.840.113549.1.5.10"
+    ),
+
+    PBE_WITH_SHA1_AND_DESEDE_128(
+            "PBEWITHSHAAND2-KEYTRIPLEDES-CBC",
+            DigestAlgorithm.SHA1,
+            DESEDE,
+            "PKCS12 with SHA-1 digest and Triple Data Encryption Standard 128 bit keys. OID 1.2.840.113549.1.12.1.4"
+    ),
+
+    PBE_WITH_SHA1_AND_DESEDE_192(
+            "PBEWITHSHAAND3-KEYTRIPLEDES-CBC",
+            DigestAlgorithm.SHA1,
+            DESEDE,
+            "PKCS12 with SHA-1 digest and Triple Data Encryption Standard 192 bit keys. OID 1.2.840.113549.1.12.1.3"
+    ),
+
+    PBE_WITH_SHA1_AND_RC2(
+            "PBEWITHSHA1ANDRC2",
+            DigestAlgorithm.SHA1,
+            RC2,
+            "PKCS5 Scheme 1 with SHA-1 digest and Rivest Cipher 2. OID 1.2.840.113549.1.5.11"
+    ),
+
+    PBE_WITH_SHA1_AND_RC2_128(
+            "PBEWITHSHAAND128BITRC2-CBC",
+            DigestAlgorithm.SHA1,
+            RC2,
+            "PKCS12 with SHA-1 digest and Rivest Cipher 2 128 bit keys. OID 1.2.840.113549.1.12.1.5"
+    ),
+
+    PBE_WITH_SHA1_AND_RC2_40(
+            "PBEWITHSHAAND40BITRC2-CBC",
+            DigestAlgorithm.SHA1,
+            RC2,
+            "PKCS12 with SHA-1 digest and Rivest Cipher 2 40 bit keys. OID 1.2.840.113549.1.12.1.6"
+    ),
+
+    PBE_WITH_SHA1_AND_RC4_128(
+            "PBEWITHSHAAND128BITRC4",
+            DigestAlgorithm.SHA1,
+            RC4,
+            "PKCS12 with SHA-1 digest and Rivest Cipher 4 128 bit keys. OID 1.2.840.113549.1.12.1.1"
+    ),
+
+    PBE_WITH_SHA1_AND_RC4_40(
+            "PBEWITHSHAAND40BITRC4",
+            DigestAlgorithm.SHA1,
+            RC4,
+            "PKCS12 with SHA-1 digest and Rivest Cipher 4 40 bit keys. OID 1.2.840.113549.1.12.1.2"
+    ),
+
+    PBE_WITH_SHA1_AND_TWOFISH(
+            "PBEWITHSHAANDTWOFISH-CBC",
+            DigestAlgorithm.SHA1,
+            TWOFISH,
+            "PKCS12 with SHA-1 digest and Twofish in Cipher Block Chaining mode using 256 bit keys."
+    ),
+
+    PBE_WITH_SHA256_AND_AES_CBC_128(
+            "PBEWITHSHA256AND128BITAES-CBC-BC",
+            DigestAlgorithm.SHA256,
+            AES,
+            "PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 128 bit keys."
+    ),
+
+    PBE_WITH_SHA256_AND_AES_CBC_192(
+            "PBEWITHSHA256AND192BITAES-CBC-BC",
+            DigestAlgorithm.SHA256,
+            AES,
+            "PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 192 bit keys."
+    ),
+
+    PBE_WITH_SHA256_AND_AES_CBC_256(
+            "PBEWITHSHA256AND256BITAES-CBC-BC",
+            DigestAlgorithm.SHA256,
+            AES,
+            "PKCS12 with SHA-256 digest and Advanced Encryption Standard in Cipher Block Chaining mode using 256 bit keys."
+    );
+
+    private final String algorithm;
+
+    private final String description;
+
+    private final DigestAlgorithm digestAlgorithm;
+
+    private final SymmetricCipher symmetricCipher;
+
+    CompatibilityModeEncryptionScheme(
+            final String algorithm,
+            final DigestAlgorithm digestAlgorithm,
+            final SymmetricCipher symmetricCipher,
+            final String description
+    ) {
+        this.algorithm = algorithm;
+        this.description = description;
+        this.digestAlgorithm = digestAlgorithm;
+        this.symmetricCipher = symmetricCipher;
+    }
+
+    @Override
+    public String getValue() {
+        return algorithm;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public DigestAlgorithm getDigestAlgorithm() {
+        return digestAlgorithm;
+    }
+
+    public SymmetricCipher getSymmetricCipher() {
+        return symmetricCipher;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeKeyDerivationStrategy.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeKeyDerivationStrategy.java
new file mode 100644
index 0000000000..c88c540c63
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/compatibility/CompatibilityModeKeyDerivationStrategy.java
@@ -0,0 +1,99 @@
+/*
+ * 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.cipher.compatibility;
+
+import org.apache.nifi.components.DescribedValue;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Compatibility Mode Key Derivation Strategy supporting decryption with legacy schemes
+ */
+public enum CompatibilityModeKeyDerivationStrategy implements DescribedValue {
+    /** Strategy based on OpenSSL EVP_BytesToKey function */
+    OPENSSL_EVP_BYTES_TO_KEY(
+            "OpenSSL Envelope BytesToKey using a digest algorithm with one iteration and optional salt of eight bytes",
+            0,
+            8,
+            16,
+            "Salted__".getBytes(StandardCharsets.US_ASCII)
+    ),
+
+    /** Strategy based on default configuration of org.jasypt.encryption.pbe.StandardPBEByteEncryptor */
+    JASYPT_STANDARD(
+            "Jasypt Java Simplified Encryption using a digest algorithm with 1000 iterations and required salt of eight or sixteen bytes",
+            1000,
+            8,
+            16,
+            new byte[0]
+    );
+
+    private final String description;
+
+    private final int iterations;
+
+    private final byte[] saltHeader;
+
+    private final int saltStandardLength;
+
+    private final int saltBufferLength;
+
+    CompatibilityModeKeyDerivationStrategy(
+            final String description,
+            final int iterations,
+            final int saltStandardLength,
+            final int saltBufferLength,
+            final byte[] saltHeader
+    ) {
+        this.description = description;
+        this.iterations = iterations;
+        this.saltStandardLength = saltStandardLength;
+        this.saltBufferLength = saltBufferLength;
+        this.saltHeader = saltHeader;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public int getIterations() {
+        return iterations;
+    }
+
+    public int getSaltStandardLength() {
+        return saltStandardLength;
+    }
+
+    public int getSaltBufferLength() {
+        return saltBufferLength;
+    }
+
+    public byte[] getSaltHeader() {
+        return saltHeader;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/EncodedDelimiter.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/EncodedDelimiter.java
new file mode 100644
index 0000000000..4f88f23255
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/EncodedDelimiter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cipher.encoded;
+
+import org.apache.nifi.components.DescribedValue;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Delimiters for content encoded according to conventions added in NiFi 0.5.0
+ */
+public enum EncodedDelimiter implements DescribedValue {
+    SALT("NiFiSALT", "Eight bytes appended to a stream after the salt bytes according to NiFi 0.5.0 conventions"),
+
+    IV("NiFiIV", "Six bytes appended to a stream after the initialization vector bytes according to NiFi 0.5.0 conventions");
+
+    private final byte[] delimiter;
+
+    private final String description;
+
+    EncodedDelimiter(
+            final String delimiterEncoded,
+            final String description
+    ) {
+        this.delimiter = delimiterEncoded.getBytes(StandardCharsets.UTF_8);
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public byte[] getDelimiter() {
+        return delimiter;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/KeySpecificationFormat.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/KeySpecificationFormat.java
new file mode 100644
index 0000000000..6135c600e9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/encoded/KeySpecificationFormat.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cipher.encoded;
+
+import org.apache.nifi.components.DescribedValue;
+
+/**
+ * Key Specification Format supporting raw and derived secret keys
+ */
+public enum KeySpecificationFormat implements DescribedValue {
+    RAW("Raw secret key provided as a hexadecimal string"),
+
+    PASSWORD("Password string for use with a Key Derivation Function to produce a secret key");
+
+    private final String description;
+
+    KeySpecificationFormat(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return name();
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/io/DecryptStreamCallback.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/io/DecryptStreamCallback.java
new file mode 100644
index 0000000000..63087bfa9c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/io/DecryptStreamCallback.java
@@ -0,0 +1,137 @@
+/*
+ * 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.cipher.io;
+
+import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.processors.cipher.CipherException;
+
+import javax.crypto.Cipher;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Decrypt Stream Callback implements shared methods for cipher operations and parameter buffer processing
+ */
+public abstract class DecryptStreamCallback implements StreamCallback {
+
+    private static final int BUFFER_LENGTH = 4096;
+
+    private static final int BUFFER_INPUT_OFFSET = 0;
+
+    private static final int END_OF_FILE = -1;
+
+    private final Cipher cipher;
+
+    private final int parameterBufferLength;
+
+    public DecryptStreamCallback(
+            final Cipher cipher,
+            final int parameterBufferLength
+    ) {
+        this.cipher = cipher;
+        this.parameterBufferLength = parameterBufferLength;
+    }
+
+    /**
+     * Process streams with parsed algorithm parameters and cipher initialization
+     *
+     * @param inputStream Stream of encrypted bytes
+     * @param outputStream Stream of decrypted bytes
+     * @throws IOException Thrown on read or write failures
+     */
+    @Override
+    public void process(final InputStream inputStream, final OutputStream outputStream) throws IOException {
+        final ByteBuffer parameterBuffer = readParameterBuffer(inputStream);
+        final AlgorithmParameterSpec algorithmParameterSpec = readAlgorithmParameterSpec(parameterBuffer);
+        final Key key = getKey(algorithmParameterSpec);
+        initCipher(key, algorithmParameterSpec);
+
+        processBuffer(parameterBuffer, outputStream);
+        processStream(inputStream, outputStream);
+    }
+
+    /**
+     * Read Cipher Algorithm Parameters from initial buffer of parameter bytes
+     *
+     * @param parameterBuffer Buffer of parameter bytes
+     * @return Algorithm Parameters Specification
+     */
+    protected abstract AlgorithmParameterSpec readAlgorithmParameterSpec(final ByteBuffer parameterBuffer);
+
+    /**
+     * Get Key for Cipher operations using Algorithm Parameters when needed
+     *
+     * @param algorithmParameterSpec Algorithm Parameters Specification
+     * @return Key for decryption processing
+     */
+    protected abstract Key getKey(final AlgorithmParameterSpec algorithmParameterSpec);
+
+    private void initCipher(final Key key, final AlgorithmParameterSpec algorithmParameterSpec) {
+        try {
+            cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameterSpec);
+        } catch (final GeneralSecurityException e) {
+            final String message = String.format("Cipher [%s] initialization failed", cipher.getAlgorithm());
+            throw new CipherException(message, e);
+        }
+    }
+
+    private ByteBuffer readParameterBuffer(final InputStream inputStream) throws IOException {
+        final byte[] buffer = new byte[parameterBufferLength];
+        final int read = inputStream.read(buffer);
+        if (read == END_OF_FILE) {
+            throw new EOFException("Read parameters buffer failed");
+        }
+        return ByteBuffer.wrap(buffer, BUFFER_INPUT_OFFSET, read);
+    }
+
+    private void processBuffer(final ByteBuffer byteBuffer, final OutputStream outputStream) throws IOException {
+        if (byteBuffer.hasRemaining()) {
+            final int remaining = byteBuffer.remaining();
+            final byte[] buffer = new byte[remaining];
+            byteBuffer.get(buffer);
+            processBytes(buffer, remaining, outputStream);
+        }
+    }
+
+    private void processBytes(final byte[] buffer, final int length, final OutputStream outputStream) throws IOException {
+        final byte[] deciphered = cipher.update(buffer, BUFFER_INPUT_OFFSET, length);
+        if (deciphered != null) {
+            outputStream.write(deciphered);
+        }
+    }
+
+    private void processStream(final InputStream inputStream, final OutputStream outputStream) throws IOException {
+        final byte[] buffer = new byte[BUFFER_LENGTH];
+        int read;
+        while ((read = inputStream.read(buffer)) != END_OF_FILE) {
+            processBytes(buffer, read, outputStream);
+        }
+        try {
+            final byte[] deciphered = cipher.doFinal();
+            outputStream.write(deciphered);
+        } catch (final GeneralSecurityException e) {
+            final String message = String.format("Cipher [%s] verification failed", cipher.getAlgorithm());
+            throw new CipherException(message, e);
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
new file mode 100644
index 0000000000..9340751c56
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -0,0 +1,16 @@
+# 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.
+org.apache.nifi.processors.cipher.DecryptContentCompatibility
+org.apache.nifi.processors.cipher.DecryptContent
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContent/additionalDetails.html b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContent/additionalDetails.html
new file mode 100644
index 0000000000..11c1a26f61
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContent/additionalDetails.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+  <!--
+    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.
+  -->
+  <head>
+    <meta charset="UTF-8">
+    <title>DecryptContent</title>
+    <link rel="stylesheet" href="../../../../../css/component-usage.css" type="text/css" />
+  </head>
+  <body>
+    <h2>Summary</h2>
+    <p>
+      This Processor supports decryption using the Advanced Encryption Standard algorithm with modern key derivation
+      functions and formats defined in the EncryptContent Processor.
+      Decryption processing is capable of selecting the necessary key derivation function based on reading the header
+      bytes of input files. The Processor supports password-based decryption or key-based decryption depending on
+      configured properties.
+    </p>
+    <h2>Configuration</h2>
+    <p>
+      This Processor can be configured to decrypt information encrypted using the EncryptContent Processor. Successful
+      decryption requires selecting matching algorithm and password properties.
+    </p>
+    <h3>Cipher Algorithm Property Values</h3>
+    <table>
+      <tbody>
+        <tr>
+          <th>Cipher Algorithm Mode</th>
+          <th>Cipher Algorithm Padding</th>
+          <th>EncryptContent Encryption Algorithm</th>
+        </tr>
+        <tr>
+          <td>CBC</td>
+          <td>NoPadding</td>
+          <td>AES_CBC_NO_PADDING</td>
+        </tr>
+        <tr>
+          <td>CBC</td>
+          <td>PKCS5Padding</td>
+          <td>AES_CBC</td>
+        </tr>
+        <tr>
+          <td>CTR</td>
+          <td>NoPadding</td>
+          <td>AES_CTR</td>
+        </tr>
+        <tr>
+          <td>GCM</td>
+          <td>NoPadding</td>
+          <td>AES_GCM</td>
+        </tr>
+      </tbody>
+    </table>
+  </body>
+</html>
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContentCompatibility/additionalDetails.html b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContentCompatibility/additionalDetails.html
new file mode 100644
index 0000000000..962b8eea5b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/docs/org.apache.nifi.processors.cipher.DecryptContentCompatibility/additionalDetails.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html lang="en">
+  <!--
+    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.
+  -->
+  <head>
+    <meta charset="UTF-8">
+    <title>DecryptContentCompatibility</title>
+    <link rel="stylesheet" href="../../../../../css/component-usage.css" type="text/css" />
+  </head>
+  <body>
+    <h2>Summary</h2>
+    <p>
+      This Processor supports decryption using legacy formats and Password-Based Encryption Algorithms.
+      <a href="https://www.rfc-editor.org/rfc/rfc8018" target="_blank">RFC 8018</a> defines the Password-Based Cryptography
+      Specification Version 2.1, including several of the supported encryption schemes described in Section 6.1
+      as PBES1. According to the specification, PBES1 is not recommended for new applications, and this Processor
+      exists for the purpose of providing compatibility with historical information. The supported key derivation
+      strategies align with implementations in OpenSSL and the Java Simplified Encryption library.
+    </p>
+    <h2>Configuration</h2>
+    <p>
+      This Processor can be configured to decrypt information encrypted using the EncryptContent Processor. Successful
+      decryption requires selecting matching algorithm and password properties.
+    </p>
+    <h3>Key Derivation Property Values</h3>
+    <table>
+      <tbody>
+        <tr>
+          <th>DecryptContentCompatibility</th>
+          <th>EncryptContent</th>
+        </tr>
+        <tr>
+          <td>JASYPT_STANDARD</td>
+          <td>NiFi Legacy KDF</td>
+        </tr>
+        <tr>
+          <td>OPENSSL_EVP_BYTES_TO_KEY</td>
+          <td>OpenSSL EVP_BytesToKey</td>
+        </tr>
+      </tbody>
+    </table>
+    <h3>Encryption Scheme Property Values</h3>
+    <table>
+      <tbody>
+        <tr>
+          <th>DecryptContentCompatibility</th>
+          <th>EncryptContent</th>
+        </tr>
+        <tr>
+          <td>PBE_WITH_MD5_AND_AES_CBC_128</td>
+          <td>MD5_128AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_MD5_AND_AES_CBC_192</td>
+          <td>MD5_192AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_MD5_AND_AES_CBC_256</td>
+          <td>MD5_256AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_MD5_AND_DES</td>
+          <td>MD5_DES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_MD5_AND_RC2</td>
+          <td>MD5_RC2</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_AES_CBC_128</td>
+          <td>SHA_128AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_AES_CBC_192</td>
+          <td>SHA_192AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_AES_CBC_256</td>
+          <td>SHA_256AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_DES</td>
+          <td>SHA1_DES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_DESEDE_128</td>
+          <td>SHA_2KEYTRIPLEDES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_DESEDE_192</td>
+          <td>SHA_3KEYTRIPLEDES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_RC2</td>
+          <td>SHA1_RC2</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_RC2_40</td>
+          <td>SHA_40RC2</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_RC2_128</td>
+          <td>SHA_128RC2</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_RC4_40</td>
+          <td>SHA_40RC4</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_RC4_128</td>
+          <td>SHA_128RC4</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA1_AND_TWOFISH</td>
+          <td>SHA_TWOFISH</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA256_AND_AES_CBC_128</td>
+          <td>SHA256_128AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA256_AND_AES_CBC_192</td>
+          <td>SHA256_192AES</td>
+        </tr>
+        <tr>
+          <td>PBE_WITH_SHA256_AND_AES_CBC_256</td>
+          <td>SHA256_256AES</td>
+        </tr>
+      </tbody>
+    </table>
+  </body>
+</html>
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentCompatibilityTest.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentCompatibilityTest.java
new file mode 100644
index 0000000000..6f2cfb3186
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentCompatibilityTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.cipher;
+
+import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeEncryptionScheme;
+import org.apache.nifi.processors.cipher.compatibility.CompatibilityModeKeyDerivationStrategy;
+import org.apache.nifi.util.LogMessage;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DecryptContentCompatibilityTest {
+    private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
+
+    private static final String PASSWORD = "password";
+
+    private static final byte[] RAW = DecryptContentCompatibility.class.getSimpleName().getBytes(StandardCharsets.UTF_8);
+
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+    private static final CompatibilityModeEncryptionScheme SIMPLE_ENCRYPTION_SCHEME = CompatibilityModeEncryptionScheme.PBE_WITH_MD5_AND_DES;
+
+    private static final byte[] EMPTY_SALT = {};
+
+    private static final byte[] OPENSSL_SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+
+    private static final int BLOCK_SIZE_UNDEFINED = 0;
+
+    private static final byte[] WORD = {'W', 'O', 'R', 'D'};
+
+    private static final int WORD_COUNT = 2048;
+
+    TestRunner runner;
+
+    @BeforeEach
+    void setRunner() {
+        runner = TestRunners.newTestRunner(DecryptContentCompatibility.class);
+    }
+
+    @ParameterizedTest
+    @EnumSource(CompatibilityModeEncryptionScheme.class)
+    void testRunKeyDerivationOpenSslUnsalted(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
+        final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
+        runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
+        runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
+        runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
+
+        final Cipher cipher = getCipher(encryptionScheme);
+        final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, EMPTY_SALT, RAW);
+        assertDecrypted(RAW, encrypted, encryptionScheme);
+    }
+
+    @ParameterizedTest
+    @EnumSource(CompatibilityModeEncryptionScheme.class)
+    void testRunKeyDerivationOpenSslSalted(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
+        final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
+        runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
+        runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
+        runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
+
+        final Cipher cipher = getCipher(encryptionScheme);
+        final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, OPENSSL_SALT, RAW);
+        assertDecrypted(RAW, encrypted, encryptionScheme);
+    }
+
+    @Test
+    void testRunKeyDerivationOpenSslSaltedStream() throws Exception {
+        final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.OPENSSL_EVP_BYTES_TO_KEY;
+        runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
+        runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, SIMPLE_ENCRYPTION_SCHEME.getValue());
+        runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
+
+        final Cipher cipher = getCipher(SIMPLE_ENCRYPTION_SCHEME);
+
+        final byte[] words = getWords();
+        final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, OPENSSL_SALT, words);
+        assertDecrypted(words, encrypted, SIMPLE_ENCRYPTION_SCHEME);
+    }
+
+    @ParameterizedTest
+    @EnumSource(CompatibilityModeEncryptionScheme.class)
+    void testRunKeyDerivationJasyptStandard(final CompatibilityModeEncryptionScheme encryptionScheme) throws Exception {
+        final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy = CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD;
+        runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, keyDerivationStrategy.getValue());
+        runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, encryptionScheme.getValue());
+        runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
+
+        final Cipher cipher = getCipher(encryptionScheme);
+        final byte[] salt = getSalt(cipher, keyDerivationStrategy);
+        final byte[] encrypted = getEncrypted(cipher, keyDerivationStrategy, salt, RAW);
+        assertDecrypted(RAW, encrypted, encryptionScheme);
+    }
+
+    @Test
+    void testRunKeyDerivationJasyptStandardSaltMissing() throws Exception {
+        setSimpleEncryptionScheme();
+
+        final Cipher cipher = getCipher(SIMPLE_ENCRYPTION_SCHEME);
+        final byte[] encrypted = getEncrypted(cipher, CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD, EMPTY_SALT, RAW);
+
+        runner.enqueue(encrypted);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
+
+        assertErrorLogged();
+    }
+
+    @Test
+    void testRunKeyDerivationJasyptStandardFileEnd() {
+        setSimpleEncryptionScheme();
+
+        runner.enqueue(EMPTY_SALT);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
+
+        assertErrorLogged();
+    }
+
+    @Test
+    void testRunKeyDerivationJasyptStandardSaltLengthMissing() {
+        setSimpleEncryptionScheme();
+
+        runner.enqueue(WORD);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.FAILURE);
+
+        assertErrorLogged();
+    }
+
+    private void setSimpleEncryptionScheme() {
+        runner.setProperty(DecryptContentCompatibility.KEY_DERIVATION_STRATEGY, CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD.getValue());
+        runner.setProperty(DecryptContentCompatibility.ENCRYPTION_SCHEME, SIMPLE_ENCRYPTION_SCHEME.getValue());
+        runner.setProperty(DecryptContentCompatibility.PASSWORD, PASSWORD);
+    }
+
+    private void assertErrorLogged() {
+        final List<LogMessage> errorMessages = runner.getLogger().getErrorMessages();
+        final Optional<LogMessage> firstErrorMessage = errorMessages.stream().findFirst();
+        assertTrue(firstErrorMessage.isPresent());
+
+        final LogMessage errorLogMessage = firstErrorMessage.get();
+        final String message = errorLogMessage.getMsg();
+        assertTrue(message.contains(SIMPLE_ENCRYPTION_SCHEME.getValue()));
+    }
+
+    private void assertDecrypted(final byte[] expected, final byte[] encrypted, final CompatibilityModeEncryptionScheme encryptionScheme) throws IOException {
+        runner.enqueue(encrypted);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContentCompatibility.SUCCESS);
+        final MockFlowFile flowFile = runner.getFlowFilesForRelationship(DecryptContentCompatibility.SUCCESS).get(0);
+
+        flowFile.assertContentEquals(expected);
+
+        flowFile.assertAttributeEquals(CipherAttributeKey.PBE_SCHEME, encryptionScheme.getValue());
+        flowFile.assertAttributeEquals(CipherAttributeKey.PBE_SYMMETRIC_CIPHER, encryptionScheme.getSymmetricCipher().getValue());
+        flowFile.assertAttributeEquals(CipherAttributeKey.PBE_DIGEST_ALGORITHM, encryptionScheme.getDigestAlgorithm().getValue());
+    }
+
+    private byte[] getSalt(final Cipher cipher, final CompatibilityModeKeyDerivationStrategy strategy) {
+        final int blockSize = cipher.getBlockSize();
+        final int saltLength = blockSize == BLOCK_SIZE_UNDEFINED ? strategy.getSaltStandardLength() : blockSize;
+        final byte[] salt = new byte[saltLength];
+        SECURE_RANDOM.nextBytes(salt);
+        return salt;
+    }
+
+    private byte[] getEncrypted(
+            final Cipher cipher,
+            final CompatibilityModeKeyDerivationStrategy keyDerivationStrategy,
+            final byte[] salt,
+            final byte[] raw
+    ) throws GeneralSecurityException, IOException {
+        initCipher(cipher, keyDerivationStrategy, salt);
+        final byte[] encrypted = cipher.doFinal(raw);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        if (CompatibilityModeKeyDerivationStrategy.JASYPT_STANDARD == keyDerivationStrategy || salt.length == 0) {
+            outputStream.write(salt);
+        } else {
+            outputStream.write(keyDerivationStrategy.getSaltHeader());
+            outputStream.write(salt);
+        }
+
+        outputStream.write(encrypted);
+        return outputStream.toByteArray();
+    }
+
+    private Cipher getCipher(final CompatibilityModeEncryptionScheme encryptionScheme) throws GeneralSecurityException {
+        final String algorithm = encryptionScheme.getValue();
+        return Cipher.getInstance(algorithm, BOUNCY_CASTLE_PROVIDER);
+    }
+
+    private void initCipher(final Cipher cipher, final CompatibilityModeKeyDerivationStrategy strategy, final byte[] salt) throws GeneralSecurityException {
+        final String algorithm = cipher.getAlgorithm();
+        final Key key = getKey(algorithm);
+        final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, strategy.getIterations());
+        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
+    }
+
+    private Key getKey(final String algorithm) throws GeneralSecurityException  {
+        final SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, BOUNCY_CASTLE_PROVIDER);
+        final PBEKeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray());
+        return secretKeyFactory.generateSecret(keySpec);
+    }
+
+    private byte[] getWords() throws IOException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        for (int i = 0; i < WORD_COUNT; i++) {
+            outputStream.write(WORD);
+        }
+
+        return outputStream.toByteArray();
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentTest.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentTest.java
new file mode 100644
index 0000000000..6dcdeffabd
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/DecryptContentTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.cipher;
+
+import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmMode;
+import org.apache.nifi.processors.cipher.algorithm.CipherAlgorithmPadding;
+import org.apache.nifi.processors.cipher.encoded.EncodedDelimiter;
+import org.apache.nifi.processors.cipher.encoded.KeySpecificationFormat;
+import org.apache.nifi.util.LogMessage;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.bouncycastle.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DecryptContentTest {
+
+    private static final byte[] WORD = {'W', 'O', 'R', 'D'};
+
+    private static final byte[] INITIALIZATION_VECTOR = {'I', 'N', 'I', 'T', 'I', 'A', 'L', 'I', 'Z', 'A', 'T', 'I', 'O', 'N', 'I', 'V'};
+
+    private static final String HEXADECIMAL_KEY_SPECIFICATION = "0123456789abcdef0123456789abcdef";
+
+    private static final String SECRET_KEY_SPECIFICATION = "ContentEncrypted";
+
+    private static final String UNENCRYPTED = "unencrypted";
+
+    private static final String ARGON2_PARAMETERS = "$argon2id$v=19$m=65536,t=5,p=8$OCXIIeGgaovfpBZXCDxGDg";
+
+    private static final String ARGON2_IV_ENCODED = "veGFZ+EWI0qShgT5AUJ+Qg==";
+
+    private static final String ARGON2_CIPHERED_ENCODED = "I32fSCCeZEkKFwP04MtnK9rbL283yXBTis4T";
+
+    private static final String BCRYPT_PARAMETERS = "$2a$12$JhHkHwk6ojTSWn9seyQV2O";
+
+    private static final String BCRYPT_IV_ENCODED = "8bDMMSPI+dKfNL3iC3hBow==";
+
+    private static final String BCRYPT_CIPHERED_ENCODED = "IeNNMCulpLoehwKg/A0e5dhIztA6fpqtBENl";
+
+    private static final String PBKDF2_SALT_ENCODED = "yoHJ1TbaLMc9qpDNAhV5bQ==";
+
+    private static final String PBKDF2_IV_ENCODED = "tgN6TnR6EbXnjKUBT4mq6Q==";
+
+    private static final String PBKDF2_CIPHERED_ENCODED = "++5mqUPqtG4bXNwJ7ruq4cSWIncOpFhiQRDR";
+
+    private static final String SCRYPT_PARAMETERS = "$s0$e0801$RWV0Tnr5u0fbQ3oPsHG9FA";
+
+    private static final String SCRYPT_IV_ENCODED = "oZxuUWK5le9eCkkckKoLhw==";
+
+    private static final String SCRYPT_CIPHERED_ENCODED = "Z4de2Bo+Zs5tpcgSp8jasXuea8tJl+vj6wqh";
+
+    private static final String HEXADECIMAL_KEY_IV_ENCODED = "z5sz+mXn3GdEWq/qvFUbvw==";
+
+    private static final String HEXADECIMAL_KEY_CIPHERED_ENCODED = "GtpE6cPs8zSYO2U=";
+
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+
+    TestRunner runner;
+
+    @BeforeEach
+    void setRunner() {
+        runner = TestRunners.newTestRunner(DecryptContent.class);
+    }
+
+    @Test
+    void testRunInitializationVectorNotFound() {
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.PASSWORD.getValue());
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION, SECRET_KEY_SPECIFICATION);
+
+        runner.enqueue(WORD);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
+        assertErrorLogged();
+    }
+
+    @Test
+    void testRunInputNotFound() {
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
+
+        final byte[] bytes = Arrays.concatenate(INITIALIZATION_VECTOR, EncodedDelimiter.IV.getDelimiter());
+        runner.enqueue(bytes);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
+        assertErrorLogged();
+    }
+
+    @Test
+    void testRunInputInvalid() {
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
+
+        final byte[] bytes = Arrays.concatenate(INITIALIZATION_VECTOR, EncodedDelimiter.IV.getDelimiter(), INITIALIZATION_VECTOR);
+        runner.enqueue(bytes);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(DecryptContent.FAILURE);
+        assertErrorLogged();
+    }
+
+    @Test
+    void testRunAesGcmNoPaddingPasswordArgon2() throws IOException {
+        setGcmNoPaddingPassword();
+
+        final byte[] encryptedBytes = getEncryptedBytes(ARGON2_PARAMETERS.getBytes(StandardCharsets.UTF_8), ARGON2_IV_ENCODED, ARGON2_CIPHERED_ENCODED);
+        runner.enqueue(encryptedBytes);
+        runner.run();
+
+        assertDecryptedSuccess();
+    }
+
+    @Test
+    void testRunAesGcmNoPaddingPasswordBcrypt() throws IOException {
+        setGcmNoPaddingPassword();
+
+        final byte[] encryptedBytes = getEncryptedBytes(BCRYPT_PARAMETERS.getBytes(StandardCharsets.UTF_8), BCRYPT_IV_ENCODED, BCRYPT_CIPHERED_ENCODED);
+        runner.enqueue(encryptedBytes);
+        runner.run();
+
+        assertDecryptedSuccess();
+    }
+
+    @Test
+    void testRunAesGcmNoPaddingPasswordPbkdf2() throws IOException {
+        setGcmNoPaddingPassword();
+
+        final byte[] encryptedBytes = getEncryptedBytes(decoder.decode(PBKDF2_SALT_ENCODED), PBKDF2_IV_ENCODED, PBKDF2_CIPHERED_ENCODED);
+        runner.enqueue(encryptedBytes);
+        runner.run();
+
+        assertDecryptedSuccess();
+    }
+
+    @Test
+    void testRunAesGcmNoPaddingPasswordScrypt() throws IOException {
+        setGcmNoPaddingPassword();
+
+        final byte[] encryptedBytes = getEncryptedBytes(SCRYPT_PARAMETERS.getBytes(StandardCharsets.UTF_8), SCRYPT_IV_ENCODED, SCRYPT_CIPHERED_ENCODED);
+        runner.enqueue(encryptedBytes);
+        runner.run();
+
+        assertDecryptedSuccess();
+    }
+
+    @Test
+    void testRunAesCtrNoPaddingRaw() throws IOException {
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.RAW.getValue());
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION, HEXADECIMAL_KEY_SPECIFICATION);
+        runner.setProperty(DecryptContent.CIPHER_ALGORITHM_MODE, CipherAlgorithmMode.CTR.getValue());
+        runner.setProperty(DecryptContent.CIPHER_ALGORITHM_PADDING, CipherAlgorithmPadding.NO_PADDING.getValue());
+
+        final byte[] encryptedBytes = getHexadecimalKeyEncryptedBytes();
+        runner.enqueue(encryptedBytes);
+        runner.run();
+
+        assertDecryptedSuccess();
+    }
+
+    private void setGcmNoPaddingPassword() {
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION_FORMAT, KeySpecificationFormat.PASSWORD.getValue());
+        runner.setProperty(DecryptContent.KEY_SPECIFICATION, SECRET_KEY_SPECIFICATION);
+        runner.setProperty(DecryptContent.CIPHER_ALGORITHM_MODE, CipherAlgorithmMode.GCM.getValue());
+        runner.setProperty(DecryptContent.CIPHER_ALGORITHM_PADDING, CipherAlgorithmPadding.NO_PADDING.getValue());
+    }
+
+    private void assertDecryptedSuccess() {
+        runner.assertAllFlowFilesTransferred(DecryptContent.SUCCESS);
+        final MockFlowFile flowFile = runner.getFlowFilesForRelationship(DecryptContent.SUCCESS).get(0);
+        flowFile.assertContentEquals(UNENCRYPTED);
+    }
+
+    private void assertErrorLogged() {
+        final List<LogMessage> errorMessages = runner.getLogger().getErrorMessages();
+        final Optional<LogMessage> firstErrorMessage = errorMessages.stream().findFirst();
+        assertTrue(firstErrorMessage.isPresent());
+    }
+
+    private byte[] getEncryptedBytes(
+            final byte[] serializedParameters,
+            final String initializationVectorEncoded,
+            final String cipheredEncoded
+    ) throws IOException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        outputStream.write(serializedParameters);
+        outputStream.write(EncodedDelimiter.SALT.getDelimiter());
+
+        final byte[] initializationVector = decoder.decode(initializationVectorEncoded);
+        outputStream.write(initializationVector);
+        outputStream.write(EncodedDelimiter.IV.getDelimiter());
+
+        final byte[] ciphered = decoder.decode(cipheredEncoded);
+        outputStream.write(ciphered);
+
+        return outputStream.toByteArray();
+    }
+
+    private byte[] getHexadecimalKeyEncryptedBytes() throws IOException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        final byte[] initializationVector = decoder.decode(HEXADECIMAL_KEY_IV_ENCODED);
+        outputStream.write(initializationVector);
+        outputStream.write(EncodedDelimiter.IV.getDelimiter());
+
+        final byte[] ciphered = decoder.decode(HEXADECIMAL_KEY_CIPHERED_ENCODED);
+        outputStream.write(ciphered);
+
+        return outputStream.toByteArray();
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/pom.xml b/nifi-nar-bundles/nifi-cipher-bundle/pom.xml
new file mode 100644
index 0000000000..f9291460c8
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-nar-bundles</artifactId>
+        <version>1.20.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>nifi-cipher-bundle</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>nifi-cipher-processors</module>
+        <module>nifi-cipher-nar</module>
+    </modules>
+</project>
diff --git a/nifi-nar-bundles/pom.xml b/nifi-nar-bundles/pom.xml
index c8a53d5b6b..d1c149be4f 100755
--- a/nifi-nar-bundles/pom.xml
+++ b/nifi-nar-bundles/pom.xml
@@ -123,6 +123,7 @@
         <module>nifi-iceberg-bundle</module>
         <module>nifi-jslt-bundle</module>
         <module>nifi-iotdb-bundle</module>
+        <module>nifi-cipher-bundle</module>
     </modules>
 
     <build>