You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jg...@apache.org on 2022/01/13 18:18:22 UTC

[nifi] branch main updated: NIFI-9438 Refactored sensitive-property-provider to multiple modules

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

jgresock 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 2ffd4a5  NIFI-9438 Refactored sensitive-property-provider to multiple modules
2ffd4a5 is described below

commit 2ffd4a5a9a9ded05b39dabc13056a1edc67511c5
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Mon Jan 10 20:11:22 2022 -0600

    NIFI-9438 Refactored sensitive-property-provider to multiple modules
    
    - Added nifi-property-protection-api with provider interfaces
    - Added nifi-property-protection-factory with implementation references
    - Added ProtectionSchemeResolver for abstracting conversion from command arguments
    - Refactored PropertyProtectionScheme to package private visibility
    - Refactored multiple unit test and removed provider integration tests
    - Renamed AESSensitivePropertyProvider to AesGcmSensitivePropertyProvider
    - Added getSupportedProtectionSchemes() to StandardProtectionSchemeResolver
    - Updated command argument descriptions for protection schemes to include supported values
    
    Signed-off-by: Joe Gresock <jg...@gmail.com>
    
    This closes #5650.
---
 nifi-commons/nifi-property-protection-api/pom.xml  |  31 +
 .../SensitivePropertyProtectionException.java}     |  32 +-
 .../properties/SensitivePropertyProtector.java     |  16 -
 .../nifi/properties/SensitivePropertyProvider.java |  11 +-
 .../SensitivePropertyProviderFactory.java          |  23 +-
 .../nifi/properties/scheme/ProtectionScheme.java}  |  22 +-
 .../scheme/ProtectionSchemeResolver.java}          |  23 +-
 .../scheme/StandardProtectionScheme.java}          |  26 +-
 nifi-commons/nifi-property-protection-aws/pom.xml  |  84 ++
 .../AwsKmsSensitivePropertyProvider.java           |   9 +-
 ...AwsSecretsManagerSensitivePropertyProvider.java |  24 +-
 .../configuration/AbstractAwsClientProvider.java   |   0
 .../configuration/AwsKmsClientProvider.java        |   0
 .../AwsSecretsManagerClientProvider.java           |   0
 .../AwsKmsSensitivePropertyProviderIT.java         |   0
 .../AwsKmsSensitivePropertyProviderTest.java       |   8 +
 ...sSecretsManagerSensitivePropertyProviderIT.java |   0
 ...ecretsManagerSensitivePropertyProviderTest.java |   9 +
 .../pom.xml                                        | 110 +--
 .../AzureKeyVaultKeySensitivePropertyProvider.java |  13 +-
 ...ureKeyVaultSecretSensitivePropertyProvider.java |  14 +-
 .../configuration/AzureClientProvider.java         |   0
 .../AzureCryptographyClientProvider.java           |   0
 .../configuration/AzureSecretClientProvider.java   |   0
 ...zureKeyVaultKeySensitivePropertyProviderIT.java |   0
 ...reKeyVaultKeySensitivePropertyProviderTest.java |   8 +
 ...eyVaultSecretSensitivePropertyProviderTest.java |   8 +
 .../AzureCryptographyClientProviderTest.java       |   3 +-
 .../AzureSecretClientProviderTest.java             |   3 +-
 .../nifi-property-protection-cipher/pom.xml        |  36 +
 .../AesGcmSensitivePropertyProvider.java           | 200 +++++
 .../AesGcmSensitivePropertyProviderTest.java       | 109 +++
 .../nifi-property-protection-factory/pom.xml       |  82 ++
 .../properties/ApplicationPropertiesProtector.java |  92 +--
 .../SensitivePropertyProviderFactoryAware.java     |   4 +-
 .../StandardSensitivePropertyProviderFactory.java  | 190 +++--
 .../scheme/PropertyProtectionScheme.java}          |  42 +-
 .../scheme/StandardProtectionSchemeResolver.java   |  50 ++
 ...andardSensitivePropertyProviderFactoryTest.java | 200 ++---
 .../StandardProtectionSchemeResolverTest.java      |  54 ++
 nifi-commons/nifi-property-protection-gcp/pom.xml  |  65 ++
 .../GcpKmsSensitivePropertyProvider.java           |   9 +-
 .../GoogleKeyManagementServiceClientProvider.java  |   0
 .../GcpKmsSensitivePropertyProviderIT.java         |   0
 .../nifi-property-protection-hashicorp/pom.xml     |  45 ++
 ...actHashiCorpVaultSensitivePropertyProvider.java |  23 +-
 ...CorpVaultKeyValueSensitivePropertyProvider.java |  26 +-
 ...iCorpVaultTransitSensitivePropertyProvider.java |  28 +-
 ...VaultKeyValueSensitivePropertyProviderTest.java |  55 ++
 ...pVaultTransitSensitivePropertyProviderTest.java |  55 ++
 .../nifi-property-protection-shared/pom.xml        |  31 +
 ...lientBasedEncodedSensitivePropertyProvider.java |   4 +-
 .../EncodedSensitivePropertyProvider.java          |  26 -
 .../BootstrapPropertiesClientProvider.java         |  10 +-
 .../properties/configuration/ClientProvider.java   |   0
 .../properties/AESSensitivePropertyProvider.java   | 268 ------
 .../AbstractSensitivePropertyProvider.java         |  50 --
 ...ltipleSensitivePropertyProtectionException.java | 128 ---
 .../nifi/properties/PropertyProtectionScheme.java  |  86 --
 .../SensitivePropertyProtectionException.java      |  91 ---
 .../AESSensitivePropertyProviderTest.groovy        | 496 ------------
 nifi-commons/pom.xml                               |   9 +-
 .../nifi-framework/nifi-properties-loader/pom.xml  |   6 +-
 .../nifi/properties/NiFiPropertiesLoader.java      |   4 +-
 .../nifi/properties/ProtectedNiFiProperties.java   |  21 +-
 .../ProtectedNiFiPropertiesGroovyTest.groovy       | 897 ---------------------
 nifi-nar-bundles/nifi-framework-bundle/pom.xml     |   7 +-
 .../nifi-registry-framework/pom.xml                |   5 +
 .../authentication/IdentityProviderFactory.java    |  20 +-
 .../security/authorization/AuthorizerFactory.java  |   4 +-
 .../nifi-registry-properties/pom.xml               |   2 +-
 .../properties/NiFiRegistryPropertiesLoader.java   |   9 +-
 .../ProtectedNiFiRegistryProperties.java           |  21 +-
 ...rotectedNiFiRegistryPropertiesGroovyTest.groovy | 350 +-------
 nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml   |  10 +
 .../nifi/properties/ConfigEncryptionTool.groovy    | 149 ++--
 .../nifi/toolkit/encryptconfig/DecryptMode.groovy  |  18 +-
 .../encryptconfig/NiFiRegistryDecryptMode.groovy   |   7 +-
 .../toolkit/encryptconfig/NiFiRegistryMode.groovy  |  20 +-
 .../encryptconfig/util/PropertiesEncryptor.groovy  |  11 -
 .../toolkit/encryptconfig/util/XmlEncryptor.groovy |   6 -
 .../properties/ConfigEncryptionToolTest.groovy     | 210 +----
 .../encryptconfig/EncryptConfigMainTest.groovy     |   4 +-
 .../NiFiRegistryDecryptModeSpec.groovy             | 117 ---
 .../nifi/toolkit/encryptconfig/TestUtil.groovy     |   4 +-
 85 files changed, 1481 insertions(+), 3462 deletions(-)

diff --git a/nifi-commons/nifi-property-protection-api/pom.xml b/nifi-commons/nifi-property-protection-api/pom.xml
new file mode 100644
index 0000000..a10401b
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-api/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-api</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-utils</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
index cee6a9c..e82c4a9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
@@ -14,32 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.properties.configuration;
-
-import org.apache.nifi.properties.BootstrapProperties;
-
-import java.util.Optional;
-import java.util.Properties;
+package org.apache.nifi.properties;
 
 /**
- * Client Provider responsible for reading Client Properties and instantiating client services
- *
- * @param <T> Client Type
+ * Sensitive Property Protection Exception indicating runtime failures
  */
-public interface ClientProvider<T> {
+public class SensitivePropertyProtectionException extends RuntimeException {
     /**
-     * Get Client Properties from Bootstrap Properties
+     * Sensitive Property Protection Exception constructor with message and without associated cause
      *
-     * @param bootstrapProperties Bootstrap Properties
-     * @return Client Properties or empty when not configured
+     * @param message Message describing failure condition
      */
-    Optional<Properties> getClientProperties(BootstrapProperties bootstrapProperties);
+    public SensitivePropertyProtectionException(final String message) {
+        super(message);
+    }
 
     /**
-     * Get Client using Client Properties
+     * Sensitive Property Protection Exception constructor with message and associated cause
      *
-     * @param properties Client Properties
-     * @return Client or empty when not configured
+     * @param message Message describing failure condition
+     * @param cause Throwable containing associated cause of failure condition
      */
-    Optional<T> getClient(Properties properties);
+    public SensitivePropertyProtectionException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
similarity index 91%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
index b78dd6d..add7c94 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
@@ -26,7 +26,6 @@ import java.util.Set;
  * @param <U> The ApplicationProperties type
  */
 public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U extends ApplicationProperties> {
-
     /**
      * Returns the number of properties, excluding protection scheme properties.
      * <p>
@@ -80,13 +79,6 @@ public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U
     boolean hasProtectedKeys();
 
     /**
-     * Returns the unique set of all protection schemes currently in use for this instance.
-     *
-     * @return the set of protection schemes
-     */
-    Set<String> getProtectionSchemes();
-
-    /**
      * Returns a Map of the keys identifying "sensitive" properties that are currently protected and the "protection" key for each.
      * This may or may not include all properties marked as sensitive.
      *
@@ -95,20 +87,12 @@ public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U
     Map<String, String> getProtectedPropertyKeys();
 
     /**
-     * Returns the local provider cache (null-safe) as a Map of protection schemes -> implementations.
-     *
-     * @return the map
-     */
-    Map<String, SensitivePropertyProvider> getSensitivePropertyProviders();
-
-    /**
      * Returns true if the property identified by this key is considered sensitive in this instance of {@code ApplicationProperties}.
      * Some properties are sensitive by default, while others can be specified by
      * {@link ProtectedProperties#getAdditionalSensitivePropertiesKeys()}.
      *
      * @param key the key
      * @return true if it is sensitive
-     * @see ApplicationPropertiesProtector#getSensitivePropertyKeys()
      */
     boolean isPropertySensitive(String key);
 
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
index e4d7c8c..2b57fc4 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
@@ -16,15 +16,10 @@
  */
 package org.apache.nifi.properties;
 
+/**
+ * Sensitive Property Provider abstracts persistence and retrieval of properties that require additional protection
+ */
 public interface SensitivePropertyProvider {
-
-    /**
-     * Returns the name of the underlying implementation.
-     *
-     * @return the name of this sensitive property provider
-     */
-    String getName();
-
     /**
      * Returns the key used to identify the provider implementation in {@code nifi.properties}.
      *
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
similarity index 74%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
index 70ba62f..75bbee2 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
@@ -16,27 +16,34 @@
  */
 package org.apache.nifi.properties;
 
+import org.apache.nifi.properties.scheme.ProtectionScheme;
+
 import java.util.Collection;
 
+/**
+ * Sensitive Property Provider Factory abstracts instantiation of supported providers
+ */
 public interface SensitivePropertyProviderFactory {
-
     /**
-     * Gives the appropriate SensitivePropertyProvider, given a protection scheme.
-     * @param protectionScheme The protection scheme to use
-     * @return The appropriate SensitivePropertyProvider
+     * Get Provider for specified Protection Strategy
+     *
+     * @param protectionScheme Protection Strategy requested
+     * @return Property Provider implementation
      */
-    SensitivePropertyProvider getProvider(PropertyProtectionScheme protectionScheme);
+    SensitivePropertyProvider getProvider(ProtectionScheme protectionScheme);
 
     /**
-     * Returns a collection of all supported sensitive property providers.
-     * @return The supported sensitive property providers
+     * Get Supported Property Providers
+     *
+     * @return Collection of supported provider implementations
      */
-    Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProviders();
+    Collection<SensitivePropertyProvider> getSupportedProviders();
 
     /**
      * Returns a ProtectedPropertyContext with the given property name.  The ProtectedPropertyContext's
      * contextName will be the name found in a matching context mapping from bootstrap.conf, or 'default' if
      * no matching mapping was found.
+     *
      * @param groupIdentifier The identifier of a group that contains the configuration property.  The definition
      *                        of a group depends on the type of configuration file.
      * @param propertyName A property name
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
index bc48e45..cb70382 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
@@ -14,26 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
 
 /**
- * Abstract Microsoft Azure Client Provider
+ * Definition of Protection Scheme
  */
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
-    public AzureClientProvider() {
-        super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
-    }
-
+public interface ProtectionScheme {
     /**
-     * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
+     * Get path of the protection scheme definition
      *
-     * @return Token Credential
+     * @return Protection scheme path
      */
-    protected TokenCredential getDefaultTokenCredential() {
-        return new DefaultAzureCredentialBuilder().build();
-    }
+    String getPath();
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
index bc48e45..adb780b 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
@@ -14,26 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
 
 /**
- * Abstract Microsoft Azure Client Provider
+ * Protection Scheme Resolver abstracts resolution of Protection Scheme references from string arguments
  */
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
-    public AzureClientProvider() {
-        super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
-    }
-
+public interface ProtectionSchemeResolver {
     /**
-     * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
+     * Get Protection Scheme
      *
-     * @return Token Credential
+     * @param scheme Scheme name required
+     * @return Protection Scheme
      */
-    protected TokenCredential getDefaultTokenCredential() {
-        return new DefaultAzureCredentialBuilder().build();
-    }
+    ProtectionScheme getProtectionScheme(String scheme);
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
index bc48e45..c0ce8d9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
@@ -14,26 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.properties.configuration;
+package org.apache.nifi.properties.scheme;
 
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+import java.util.Objects;
 
 /**
- * Abstract Microsoft Azure Client Provider
+ * Standard implementation of Protection Scheme with configurable parameters
  */
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
-    public AzureClientProvider() {
-        super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+public class StandardProtectionScheme implements ProtectionScheme {
+    private String path;
+
+    public StandardProtectionScheme(final String path) {
+        this.path = Objects.requireNonNull(path, "Path required");
     }
 
-    /**
-     * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
-     *
-     * @return Token Credential
-     */
-    protected TokenCredential getDefaultTokenCredential() {
-        return new DefaultAzureCredentialBuilder().build();
+    @Override
+    public String getPath() {
+        return path;
     }
 }
diff --git a/nifi-commons/nifi-property-protection-aws/pom.xml b/nifi-commons/nifi-property-protection-aws/pom.xml
new file mode 100644
index 0000000..973d89d
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-aws/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-aws</artifactId>
+    <properties>
+        <aws.sdk.version>2.17.106</aws.sdk.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-shared</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>url-connection-client</artifactId>
+            <version>${aws.sdk.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>kms</artifactId>
+            <version>${aws.sdk.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>netty-nio-client</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>apache-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>secretsmanager</artifactId>
+            <version>${aws.sdk.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>netty-nio-client</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>software.amazon.awssdk</groupId>
+                    <artifactId>apache-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
similarity index 96%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
index e9cb4ae..80a34f3 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
@@ -34,8 +34,15 @@ import java.util.Properties;
 public class AwsKmsSensitivePropertyProvider extends ClientBasedEncodedSensitivePropertyProvider<KmsClient> {
     protected static final String KEY_ID_PROPERTY = "aws.kms.key.id";
 
+    private static final String IDENTIFIER_KEY = "aws/kms";
+
     AwsKmsSensitivePropertyProvider(final KmsClient kmsClient, final Properties properties) throws SensitivePropertyProtectionException {
-        super(PropertyProtectionScheme.AWS_KMS, kmsClient, properties);
+        super(kmsClient, properties);
+    }
+
+    @Override
+    public String getIdentifierKey() {
+        return IDENTIFIER_KEY;
     }
 
     /**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
index cb899fd..17c265f 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
@@ -31,7 +31,12 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
+/**
+ * Amazon Web Services Secrets Manager implementation of Sensitive Property Provider
+ */
+public class AwsSecretsManagerSensitivePropertyProvider implements SensitivePropertyProvider {
+    private static final String IDENTIFIER_KEY = "aws/secretsmanager";
+
     private final SecretsManagerClient client;
     private final ObjectMapper objectMapper;
 
@@ -40,13 +45,16 @@ public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitiv
     private final Lock writeLock = rwLock.writeLock();
 
     AwsSecretsManagerSensitivePropertyProvider(final SecretsManagerClient client) {
-        super(null);
-
         this.client = client;
         this.objectMapper = new ObjectMapper();
     }
 
     @Override
+    public String getIdentifierKey() {
+        return IDENTIFIER_KEY;
+    }
+
+    @Override
     public boolean isSupported() {
         return client != null;
     }
@@ -145,16 +153,6 @@ public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitiv
     }
 
     @Override
-    protected PropertyProtectionScheme getProtectionScheme() {
-        return PropertyProtectionScheme.AWS_SECRETSMANAGER;
-    }
-
-    @Override
-    public String getIdentifierKey() {
-        return getProtectionScheme().getIdentifier();
-    }
-
-    @Override
     public void cleanUp() {
         if (client != null) {
             client.close();
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
index b21b017..be747c6 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
@@ -58,6 +58,8 @@ public class AwsKmsSensitivePropertyProviderTest {
 
     private static final Properties PROPERTIES = new Properties();
 
+    private static final String IDENTIFIER_KEY = "aws/kms";
+
     static {
         PROPERTIES.setProperty(AwsKmsSensitivePropertyProvider.KEY_ID_PROPERTY, KEY_ID);
     }
@@ -116,4 +118,10 @@ public class AwsKmsSensitivePropertyProviderTest {
         final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
         assertEquals(PROPERTY, property);
     }
+
+    @Test
+    public void testGetIdentifierKey() {
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
index 93addaa..09f85e6 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
@@ -45,6 +45,8 @@ public class AwsSecretsManagerSensitivePropertyProviderTest {
 
     private static final String SECRET_STRING = String.format("{ \"%s\": \"%s\" }", PROPERTY_NAME, PROPERTY_VALUE);
 
+    private static final String IDENTIFIER_KEY = "aws/secretsmanager";
+
     @Mock
     private SecretsManagerClient secretsManagerClient;
 
@@ -113,4 +115,11 @@ public class AwsSecretsManagerSensitivePropertyProviderTest {
         final String property = provider.unprotect("anyValue", ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
         assertEquals(PROPERTY_VALUE, property);
     }
+
+
+    @Test
+    public void testGetIdentifierKey() {
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/pom.xml b/nifi-commons/nifi-property-protection-azure/pom.xml
similarity index 52%
rename from nifi-commons/nifi-sensitive-property-provider/pom.xml
rename to nifi-commons/nifi-property-protection-azure/pom.xml
index ac4d307..72c8c5c 100644
--- a/nifi-commons/nifi-sensitive-property-provider/pom.xml
+++ b/nifi-commons/nifi-property-protection-azure/pom.xml
@@ -20,73 +20,19 @@
         <artifactId>nifi-commons</artifactId>
         <version>1.16.0-SNAPSHOT</version>
     </parent>
-    <artifactId>nifi-sensitive-property-provider</artifactId>
-    <properties>
-        <aws.sdk.version>2.17.1</aws.sdk.version>
-        <gcp.sdk.version>22.0.0</gcp.sdk.version>
-    </properties>
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>com.google.cloud</groupId>
-                <artifactId>libraries-bom</artifactId>
-                <version>${gcp.sdk.version}</version>
-                <type>pom</type>
-                <scope>import</scope>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
+    <artifactId>nifi-property-protection-azure</artifactId>
     <dependencies>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-properties</artifactId>
+            <artifactId>nifi-property-protection-api</artifactId>
             <version>1.16.0-SNAPSHOT</version>
         </dependency>
         <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk15on</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-            <version>3.12.0</version>
-        </dependency>
-        <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-security-utils</artifactId>
+            <artifactId>nifi-property-protection-shared</artifactId>
             <version>1.16.0-SNAPSHOT</version>
         </dependency>
         <dependency>
-            <groupId>software.amazon.awssdk</groupId>
-            <artifactId>kms</artifactId>
-            <version>${aws.sdk.version}</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>software.amazon.awssdk</groupId>
-                    <artifactId>netty-nio-client</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>software.amazon.awssdk</groupId>
-                    <artifactId>apache-client</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>software.amazon.awssdk</groupId>
-            <artifactId>secretsmanager</artifactId>
-            <version>${aws.sdk.version}</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>software.amazon.awssdk</groupId>
-                    <artifactId>netty-nio-client</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>software.amazon.awssdk</groupId>
-                    <artifactId>apache-client</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
             <groupId>com.azure</groupId>
             <artifactId>azure-security-keyvault-secrets</artifactId>
             <version>4.3.3</version>
@@ -157,60 +103,10 @@
             <version>1.7.1</version>
         </dependency>
         <dependency>
-            <groupId>software.amazon.awssdk</groupId>
-            <artifactId>url-connection-client</artifactId>
-            <version>${aws.sdk.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>com.google.cloud</groupId>
-            <artifactId>google-cloud-kms</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>commons-logging</groupId>
-                    <artifactId>commons-logging</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-vault-utils</artifactId>
-            <version>1.16.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.10.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-inline</artifactId>
             <version>${mockito.version}</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
-    <build>
-        <!-- Required to run Groovy tests without any Java tests -->
-        <plugins>
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>build-helper-maven-plugin</artifactId>
-                <version>1.5</version>
-                <executions>
-                    <execution>
-                        <id>add-test-source</id>
-                        <phase>generate-test-sources</phase>
-                        <goals>
-                            <goal>add-test-source</goal>
-                        </goals>
-                        <configuration>
-                            <sources>
-                                <source>src/test/groovy</source>
-                            </sources>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
 </project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
index f8f8fbe..1d60b2a 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
@@ -24,8 +24,6 @@ import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
 import com.azure.security.keyvault.keys.models.KeyOperation;
 import com.azure.security.keyvault.keys.models.KeyProperties;
 
-import org.apache.nifi.util.StringUtils;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Properties;
@@ -38,10 +36,17 @@ public class AzureKeyVaultKeySensitivePropertyProvider extends ClientBasedEncode
 
     protected static final List<KeyOperation> REQUIRED_OPERATIONS = Arrays.asList(KeyOperation.DECRYPT, KeyOperation.ENCRYPT);
 
+    private static final String IDENTIFIER_KEY = "azure/keyvault/key";
+
     private EncryptionAlgorithm encryptionAlgorithm;
 
     AzureKeyVaultKeySensitivePropertyProvider(final CryptographyClient cryptographyClient, final Properties properties) {
-        super(PropertyProtectionScheme.AZURE_KEYVAULT_KEY, cryptographyClient, properties);
+        super(cryptographyClient, properties);
+    }
+
+    @Override
+    public String getIdentifierKey() {
+        return IDENTIFIER_KEY;
     }
 
     /**
@@ -72,7 +77,7 @@ public class AzureKeyVaultKeySensitivePropertyProvider extends ClientBasedEncode
                 throw new SensitivePropertyProtectionException("Azure Key Vault Key Validation Failed", e);
             }
             final String algorithm = getProperties().getProperty(ENCRYPTION_ALGORITHM_PROPERTY);
-            if (StringUtils.isBlank(algorithm)) {
+            if (algorithm == null || algorithm.isEmpty()) {
                 throw new SensitivePropertyProtectionException("Azure Key Vault Key Algorithm not configured");
             }
             encryptionAlgorithm = EncryptionAlgorithm.fromString(algorithm);
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
similarity index 93%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
index ac9e1da..e2cbfc7 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
@@ -32,6 +32,8 @@ public class AzureKeyVaultSecretSensitivePropertyProvider implements SensitivePr
 
     private static final String DASH = "-";
 
+    private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
+
     private SecretClient secretClient;
 
     AzureKeyVaultSecretSensitivePropertyProvider(final SecretClient secretClient) {
@@ -39,23 +41,13 @@ public class AzureKeyVaultSecretSensitivePropertyProvider implements SensitivePr
     }
 
     /**
-     * Get Provider name using Protection Scheme
-     *
-     * @return Provider name
-     */
-    @Override
-    public String getName() {
-        return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getName();
-    }
-
-    /**
      * Get Identifier key using Protection Scheme
      *
      * @return Identifier key
      */
     @Override
     public String getIdentifierKey() {
-        return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getIdentifier();
+        return IDENTIFIER_KEY;
     }
 
     /**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
similarity index 100%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
index 412a9df..539a90e 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
@@ -58,6 +58,8 @@ public class AzureKeyVaultKeySensitivePropertyProviderTest {
 
     private static final EncryptionAlgorithm ALGORITHM = EncryptionAlgorithm.A256GCM;
 
+    private static final String IDENTIFIER_KEY = "azure/keyvault/key";
+
     static {
         PROPERTIES.setProperty(AzureKeyVaultKeySensitivePropertyProvider.ENCRYPTION_ALGORITHM_PROPERTY, ALGORITHM.toString());
     }
@@ -107,4 +109,10 @@ public class AzureKeyVaultKeySensitivePropertyProviderTest {
         final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
         assertEquals(PROPERTY, property);
     }
+
+    @Test
+    public void testGetIdentifierKey() {
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
index 3ec2d4a..399d2fb 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
@@ -46,6 +46,8 @@ public class AzureKeyVaultSecretSensitivePropertyProviderTest {
 
     private static final String ID = KeyVaultSecret.class.getName();
 
+    private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
+
     @Mock
     private SecretClient secretClient;
 
@@ -105,4 +107,10 @@ public class AzureKeyVaultSecretSensitivePropertyProviderTest {
         final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
         assertThrows(SensitivePropertyProtectionException.class, () -> provider.unprotect(PROTECTED_PROPERTY, context));
     }
+
+    @Test
+    public void testGetIdentifierKey() {
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
index e898ea4..92ac1b9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
@@ -17,7 +17,6 @@
 package org.apache.nifi.properties.configuration;
 
 import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
-import org.apache.nifi.util.StringUtils;
 import org.junit.jupiter.api.Test;
 
 import java.util.Optional;
@@ -38,7 +37,7 @@ public class AzureCryptographyClientProviderTest {
     public void testClientPropertiesKeyIdBlank() {
         final AzureCryptographyClientProvider provider = new AzureCryptographyClientProvider();
         final Properties clientProperties = new Properties();
-        clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY, StringUtils.EMPTY);
+        clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY, "");
         final Optional<CryptographyClient> optionalClient = provider.getClient(clientProperties);
         assertFalse(optionalClient.isPresent());
     }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
similarity index 96%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
index 6f312df..417b522 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
@@ -17,7 +17,6 @@
 package org.apache.nifi.properties.configuration;
 
 import com.azure.security.keyvault.secrets.SecretClient;
-import org.apache.nifi.util.StringUtils;
 import org.junit.jupiter.api.Test;
 
 import java.util.Optional;
@@ -38,7 +37,7 @@ public class AzureSecretClientProviderTest {
     public void testClientPropertiesUriBlank() {
         final AzureSecretClientProvider provider = new AzureSecretClientProvider();
         final Properties clientProperties = new Properties();
-        clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY, StringUtils.EMPTY);
+        clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY, "");
         final Optional<SecretClient> optionalClient = provider.getClient(clientProperties);
         assertFalse(optionalClient.isPresent());
     }
diff --git a/nifi-commons/nifi-property-protection-cipher/pom.xml b/nifi-commons/nifi-property-protection-cipher/pom.xml
new file mode 100644
index 0000000..c30dcf7
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-cipher</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.15</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java
new file mode 100644
index 0000000..1641bc1
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java
@@ -0,0 +1,200 @@
+/*
+ * 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.properties;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Sensitive Property Provider implementation using AES-GCM with configurable key sizes
+ */
+class AesGcmSensitivePropertyProvider implements SensitivePropertyProvider {
+    private static final String ALGORITHM = "AES/GCM/NoPadding";
+    private static final String SECRET_KEY_ALGORITHM = "AES";
+    private static final String DELIMITER = "||"; // "|" is not a valid Base64 character, so ensured not to be present in cipher text
+    private static final int IV_LENGTH = 12;
+    private static final int TAG_LENGTH = 128;
+    private static final int MIN_CIPHER_TEXT_LENGTH = IV_LENGTH * 4 / 3 + DELIMITER.length() + 1;
+    private static final List<Integer> VALID_KEY_LENGTHS = Arrays.asList(128, 192, 256);
+
+    private static final String IDENTIFIER_KEY_FORMAT = "aes/gcm/%d";
+    private static final int BITS_PER_BYTE = 8;
+
+    private static final Base64.Encoder BASE_64_ENCODER = Base64.getEncoder();
+    private static final Base64.Decoder BASE_64_DECODER = Base64.getDecoder();
+
+    private final SecureRandom secureRandom;
+    private final Cipher cipher;
+    private final SecretKey key;
+    private final String identifierKey;
+
+    public AesGcmSensitivePropertyProvider(final String keyHex) {
+        byte[] keyBytes = validateKey(keyHex);
+
+        secureRandom = new SecureRandom();
+        try {
+            cipher = Cipher.getInstance(ALGORITHM);
+            key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);
+
+            final int keySize = keyBytes.length * BITS_PER_BYTE;
+            identifierKey = String.format(IDENTIFIER_KEY_FORMAT, keySize);
+        } catch (final NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new SensitivePropertyProtectionException(String.format("Cipher [%s] initialization failed", ALGORITHM), e);
+        }
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    /**
+     * Returns the key used to identify the provider implementation in {@code nifi.properties}.
+     *
+     * @return the key to persist in the sibling property
+     */
+    @Override
+    public String getIdentifierKey() {
+        return identifierKey;
+    }
+
+    /**
+     * Returns the encrypted cipher text.
+     *
+     * @param unprotectedValue the sensitive value
+     * @param context The property context, unused in this provider
+     * @return the value to persist in the {@code nifi.properties} file
+     * @throws SensitivePropertyProtectionException if there is an exception encrypting the value
+     */
+    @Override
+    public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+        Objects.requireNonNull(unprotectedValue, "Value required");
+
+        final byte[] iv = generateIV();
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
+
+            byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
+            byte[] cipherBytes = cipher.doFinal(plainBytes);
+            return BASE_64_ENCODER.encodeToString(iv) + DELIMITER + BASE_64_ENCODER.encodeToString(cipherBytes);
+        } catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+            throw new SensitivePropertyProtectionException("AES-GCM encryption failed", e);
+        }
+    }
+
+    /**
+     * Returns the decrypted plaintext.
+     *
+     * @param protectedValue the cipher text read from the {@code nifi.properties} file
+     * @param context The property context, unused in this provider
+     * @return the raw value to be used by the application
+     * @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
+     */
+    @Override
+    public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+        if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
+            throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
+        }
+
+        if (!protectedValue.contains(DELIMITER)) {
+            throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)");
+        }
+        final String trimmedProtectedValue = protectedValue.trim();
+
+        final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER));
+        final byte[] iv = BASE_64_DECODER.decode(armoredIV);
+        if (iv.length < IV_LENGTH) {
+            throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH));
+        }
+
+        final String encodedCipherBytes = trimmedProtectedValue.substring(trimmedProtectedValue.indexOf(DELIMITER) + 2);
+
+        try {
+            final byte[] cipherBytes = BASE_64_DECODER.decode(encodedCipherBytes);
+
+            cipher.init(Cipher.DECRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
+            final byte[] plainBytes = cipher.doFinal(cipherBytes);
+            return new String(plainBytes, StandardCharsets.UTF_8);
+        } catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+            throw new SensitivePropertyProtectionException("AES-GCM decryption failed", e);
+        }
+    }
+
+    /**
+     * No cleanup necessary
+     */
+    @Override
+    public void cleanUp() { }
+
+    private byte[] generateIV() {
+        final byte[] iv = new byte[IV_LENGTH];
+        secureRandom.nextBytes(iv);
+        return iv;
+    }
+
+    private byte[] validateKey(String keyHex) {
+        if (keyHex == null || keyHex.isEmpty()) {
+            throw new SensitivePropertyProtectionException("AES Key not provided");
+        }
+        keyHex = formatHexKey(keyHex);
+        if (!isHexKeyValid(keyHex)) {
+            throw new SensitivePropertyProtectionException("AES Key not hexadecimal");
+        }
+        final byte[] key;
+        try {
+            key = Hex.decodeHex(keyHex);
+        } catch (final DecoderException e) {
+            throw new SensitivePropertyProtectionException("AES Key Hexadecimal decoding failed", e);
+        }
+        final int keyLengthBits = key.length * BITS_PER_BYTE;
+        if (!VALID_KEY_LENGTHS.contains(keyLengthBits)) {
+            throw new SensitivePropertyProtectionException(String.format("AES Key length not valid [%d]", keyLengthBits));
+        }
+        return key;
+    }
+
+    private static String formatHexKey(String input) {
+        if (input == null || input.isEmpty()) {
+            return "";
+        }
+        return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
+    }
+
+    private static boolean isHexKeyValid(String key) {
+        if (key == null || key.isEmpty()) {
+            return false;
+        }
+        return key.matches("^[0-9a-fA-F]*$");
+    }
+}
diff --git a/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..5e01fc2
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AesGcmSensitivePropertyProviderTest {
+    private static final String HEXADECIMAL_KEY_32 = "12345678";
+
+    private static final String HEXADECIMAL_KEY_128 = "12345678123456788765432187654321";
+
+    private static final String HEXADECIMAL_KEY_256 = "1234567812345678876543218765432112345678123456788765432187654321";
+
+    private static final String AES_GCM_128 = "aes/gcm/128";
+
+    private static final String AES_GCM_256 = "aes/gcm/256";
+
+    private static final ProtectedPropertyContext PROPERTY_CONTEXT = ProtectedPropertyContext.defaultContext("propertyName");
+
+    private static final String PROPERTY_VALUE = "propertyValue";
+
+    private static final String DELIMITER = "||";
+
+    private static final String DELIMITER_PATTERN = "\\|\\|";
+
+    private static final int DELIMITED_ELEMENTS = 2;
+
+    private static final int INITIALIZATION_VECTOR_LENGTH = 12;
+
+    @Test
+    public void testInvalidKeyLength() {
+        assertThrows(SensitivePropertyProtectionException.class, () -> new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_32));
+    }
+
+    @Test
+    public void testIsSupported() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+        assertTrue(provider.isSupported());
+    }
+
+    @Test
+    public void testGetIdentifierKeyAesGcm128() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(AES_GCM_128, identifierKey);
+    }
+
+    @Test
+    public void testGetIdentifierKeyAesGcm256() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
+        final String identifierKey = provider.getIdentifierKey();
+        assertEquals(AES_GCM_256, identifierKey);
+    }
+
+    @Test
+    public void testProtectUnprotectSuccess() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+        final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
+        final String unprotectedPropertyValue = provider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT);
+
+        assertEquals(PROPERTY_VALUE, unprotectedPropertyValue);
+        assertTrue(protectedPropertyValue.contains(DELIMITER));
+
+        final String[] elements = protectedPropertyValue.split(DELIMITER_PATTERN);
+        assertEquals(DELIMITED_ELEMENTS, elements.length);
+
+        final String initializationVectorEncoded = elements[0];
+        final byte[] initializationVector = Base64.getDecoder().decode(initializationVectorEncoded);
+        assertEquals(INITIALIZATION_VECTOR_LENGTH, initializationVector.length);
+    }
+
+    @Test
+    public void testProtectUnprotectDifferentKeyFailed() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+        final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
+
+        final AesGcmSensitivePropertyProvider secondProvider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
+        assertThrows(SensitivePropertyProtectionException.class, () -> secondProvider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT));
+    }
+
+    @Test
+    public void testUnprotectMinLengthRequired() {
+        final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+        assertThrows(IllegalArgumentException.class, () -> provider.unprotect(HEXADECIMAL_KEY_32, PROPERTY_CONTEXT));
+    }
+}
diff --git a/nifi-commons/nifi-property-protection-factory/pom.xml b/nifi-commons/nifi-property-protection-factory/pom.xml
new file mode 100644
index 0000000..2753074
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-factory</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-shared</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-aws</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-azure</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-cipher</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-gcp</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-hashicorp</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-utils</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-properties</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-utils</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.11.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
similarity index 74%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
index 1064329..4500ff1 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
@@ -47,9 +47,9 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
 
     private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertiesProtector.class);
 
-    private T protectedProperties;
+    private final T protectedProperties;
 
-    private Map<String, SensitivePropertyProvider> localProviderCache = new HashMap<>();
+    private final Map<String, SensitivePropertyProvider> localProviderCache = new HashMap<>();
 
     /**
      * Creates an instance containing the provided {@link ProtectedProperties}.
@@ -188,11 +188,6 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
     }
 
     @Override
-    public Set<String> getProtectionSchemes() {
-        return new HashSet<>(getProtectedPropertyKeys().values());
-    }
-
-    @Override
     public boolean isPropertySensitive(final String key) {
         // If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will loop infinitely
         return key != null && !key.equals(getAdditionalSensitivePropertiesKeysName()) && getSensitivePropertyKeys().contains(key.trim());
@@ -243,17 +238,11 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
             }
 
             if (!failedKeys.isEmpty()) {
-                if (failedKeys.size() > 1) {
-                    logger.warn("Combining {} failed keys [{}] into single exception", failedKeys.size(), StringUtils.join(failedKeys, ", "));
-                    throw new MultipleSensitivePropertyProtectionException("Failed to unprotect keys", failedKeys);
-                } else {
-                    throw new SensitivePropertyProtectionException("Failed to unprotect key " + failedKeys.iterator().next());
-                }
+                final String failed = failedKeys.size() == 1 ? failedKeys.iterator().next() : StringUtils.join(failedKeys, ", ");
+                throw new SensitivePropertyProtectionException(String.format("Failed unprotected properties: %s", failed));
             }
 
-            final U unprotected = protectedProperties.createApplicationProperties(rawProperties);
-
-            return unprotected;
+            return protectedProperties.createApplicationProperties(rawProperties);
         } else {
             logger.debug("No protected properties");
             return protectedProperties.getApplicationProperties();
@@ -262,50 +251,19 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
 
     @Override
     public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) {
-        Objects.requireNonNull(sensitivePropertyProvider, "Cannot add null SensitivePropertyProvider");
-        if (sensitivePropertyProvider == null) {
-            throw new IllegalArgumentException("Cannot add null SensitivePropertyProvider");
-        }
+        Objects.requireNonNull(sensitivePropertyProvider, "Provider required");
 
-        if (getSensitivePropertyProviders().containsKey(sensitivePropertyProvider.getIdentifierKey())) {
-            throw new UnsupportedOperationException("Cannot overwrite existing sensitive property provider registered for " + sensitivePropertyProvider.getIdentifierKey());
+        final String identifierKey = sensitivePropertyProvider.getIdentifierKey();
+        if (localProviderCache.containsKey(identifierKey)) {
+            throw new UnsupportedOperationException(String.format("Sensitive Property Provider Identifier [%s] override not supported", identifierKey));
         }
 
-        getSensitivePropertyProviders().put(sensitivePropertyProvider.getIdentifierKey(), sensitivePropertyProvider);
+        localProviderCache.put(identifierKey, sensitivePropertyProvider);
     }
 
     @Override
     public String toString() {
-        final Set<String> providers = getSensitivePropertyProviders().keySet();
-        return new StringBuilder("ApplicationPropertiesProtector instance with ")
-                .append(size()).append(" properties (")
-                .append(getProtectedPropertyKeys().size())
-                .append(" protected) and ")
-                .append(providers.size())
-                .append(" sensitive property providers: ")
-                .append(StringUtils.join(providers, ", "))
-                .toString();
-    }
-
-    @Override
-    public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
-        if (localProviderCache == null) {
-            localProviderCache = new HashMap<>();
-        }
-
-        return localProviderCache;
-    }
-
-    private SensitivePropertyProvider getSensitivePropertyProvider(final String protectionScheme) {
-        if (isProviderAvailable(protectionScheme)) {
-            return getSensitivePropertyProviders().get(protectionScheme);
-        } else {
-            throw new SensitivePropertyProtectionException("No provider available for " + protectionScheme);
-        }
-    }
-
-    private boolean isProviderAvailable(final String protectionScheme) {
-        return getSensitivePropertyProviders().containsKey(protectionScheme);
+        return String.format("%s Properties [%d]", getClass().getSimpleName(), getPropertyKeys().size());
     }
 
     /**
@@ -318,23 +276,25 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
     private String unprotectValue(final String key, final String retrievedValue) {
         // Checks if the key is sensitive and marked as protected
         if (isPropertyProtected(key)) {
-            final String protectionScheme = getProperty(getProtectionKey(key));
-
-            // No provider registered for this scheme
-            if (!isProviderAvailable(protectionScheme)) {
-                throw new IllegalStateException(String.format("No provider available for " + key));
-            }
+            final String protectionSchemePath = getProperty(getProtectionKey(key));
 
             try {
-                final SensitivePropertyProvider sensitivePropertyProvider = getSensitivePropertyProvider(protectionScheme);
-                return sensitivePropertyProvider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
-            } catch (SensitivePropertyProtectionException e) {
-                logger.error("Error unprotecting value for " + key, e);
-                throw e;
-            } catch (IllegalArgumentException e) {
-                throw new SensitivePropertyProtectionException("Error unprotecting value for " + key, e);
+                final SensitivePropertyProvider provider = findProvider(protectionSchemePath);
+                return provider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
+            } catch (final RuntimeException e) {
+                throw new SensitivePropertyProtectionException(String.format("Property [%s] unprotect failed", key), e);
             }
         }
         return retrievedValue;
     }
+
+    private SensitivePropertyProvider findProvider(final String protectionSchemePath) {
+        Objects.requireNonNull(protectionSchemePath, "Protection Scheme Path required");
+        return localProviderCache.entrySet()
+                .stream()
+                .filter(entry -> protectionSchemePath.startsWith(entry.getKey()))
+                .findFirst()
+                .map(Map.Entry::getValue)
+                .orElseThrow(() -> new UnsupportedOperationException(String.format("Protection Scheme Path [%s] Provider not found", protectionSchemePath)));
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
index c2045d7..46bfbff 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.properties;
 
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
+
 import java.util.function.Supplier;
 
 /**
@@ -35,7 +37,7 @@ public class SensitivePropertyProviderFactoryAware {
     protected String decryptValue(final String cipherText, final String protectionScheme, final String propertyName, final String groupIdentifier) throws SensitivePropertyProtectionException {
         final SensitivePropertyProviderFactory sensitivePropertyProviderFactory = getSensitivePropertyProviderFactory();
         final ProtectedPropertyContext protectedPropertyContext = sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName);
-        return sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(protectionScheme))
+        return sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(protectionScheme))
                 .unprotect(cipherText, protectedPropertyContext);
     }
 
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
similarity index 55%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
index f9a9e6c..74ca100 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
@@ -19,6 +19,7 @@ package org.apache.nifi.properties;
 import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
 import com.azure.security.keyvault.secrets.SecretClient;
 import com.google.cloud.kms.v1.KeyManagementServiceClient;
+
 import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
 import org.apache.nifi.properties.configuration.AwsKmsClientProvider;
 import org.apache.nifi.properties.configuration.AwsSecretsManagerClientProvider;
@@ -26,8 +27,10 @@ import org.apache.nifi.properties.configuration.AzureCryptographyClientProvider;
 import org.apache.nifi.properties.configuration.AzureSecretClientProvider;
 import org.apache.nifi.properties.configuration.ClientProvider;
 import org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
+import org.apache.nifi.properties.scheme.ProtectionScheme;
 import org.apache.nifi.util.NiFiBootstrapUtils;
 import org.apache.nifi.util.StringUtils;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.services.kms.KmsClient;
@@ -37,6 +40,7 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -45,12 +49,26 @@ import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+/**
+ * Standard implementation of Sensitive Property Provider Factory with custom initialization for each provider
+ */
 public class StandardSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory {
     private static final Logger logger = LoggerFactory.getLogger(StandardSensitivePropertyProviderFactory.class);
 
+    private static final List<Class<? extends SensitivePropertyProvider>> PROVIDER_CLASSES = Arrays.asList(
+        AesGcmSensitivePropertyProvider.class,
+        AwsKmsSensitivePropertyProvider.class,
+        AwsSecretsManagerSensitivePropertyProvider.class,
+        AzureKeyVaultKeySensitivePropertyProvider.class,
+        AzureKeyVaultSecretSensitivePropertyProvider.class,
+        GcpKmsSensitivePropertyProvider.class,
+        HashiCorpVaultKeyValueSensitivePropertyProvider.class,
+        HashiCorpVaultTransitSensitivePropertyProvider.class
+    );
+
     private final Optional<String> keyHex;
     private final Supplier<BootstrapProperties> bootstrapPropertiesSupplier;
-    private final Map<PropertyProtectionScheme, SensitivePropertyProvider> providerMap;
+    private final Map<Class<? extends SensitivePropertyProvider>, SensitivePropertyProvider> providers;
     private Map<String, Pattern> customPropertyContextMap;
 
     /**
@@ -88,19 +106,61 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
     private StandardSensitivePropertyProviderFactory(final String keyHex, final Supplier<BootstrapProperties> bootstrapPropertiesSupplier) {
         this.keyHex = Optional.ofNullable(keyHex);
         this.bootstrapPropertiesSupplier = bootstrapPropertiesSupplier == null ? () -> null : bootstrapPropertiesSupplier;
-        this.providerMap = new HashMap<>();
+        this.providers = new HashMap<>();
         this.customPropertyContextMap = null;
     }
 
+    /**
+     * Get Provider where Protection Scheme path starts with a prefix mapped to the Provider Class
+     *
+     * @param protectionScheme Protection Scheme requested
+     * @return Sensitive Property Provider
+     * @throws SensitivePropertyProtectionException Thrown when provider path not found for requested scheme
+     */
+    @Override
+    public SensitivePropertyProvider getProvider(final ProtectionScheme protectionScheme) throws SensitivePropertyProtectionException {
+        final String path = Objects.requireNonNull(protectionScheme, "Protection Scheme required").getPath();
+        final Collection<SensitivePropertyProvider> supportedProviders = getSupportedProviders();
+        return supportedProviders.stream()
+                .filter(provider -> provider.getIdentifierKey().startsWith(path))
+                .findFirst()
+                .orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not found", path)));
+    }
+
+    /**
+     * Get Supported Sensitive Property Providers instantiates providers as needed and checks supported status
+     *
+     * @return Supported Sensitive Property Providers
+     */
+    @Override
+    public Collection<SensitivePropertyProvider> getSupportedProviders() {
+        return PROVIDER_CLASSES
+                .stream()
+                .map(this::getProvider)
+                .filter(SensitivePropertyProvider::isSupported)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
+        if (customPropertyContextMap == null) {
+            populateCustomPropertyContextMap();
+        }
+        final String contextName = customPropertyContextMap.entrySet().stream()
+                .filter(entry -> entry.getValue().matcher(groupIdentifier).find())
+                .map(Map.Entry::getKey)
+                .findFirst()
+                .orElse(null);
+        return ProtectedPropertyContext.contextFor(propertyName, contextName);
+    }
+
     private void populateCustomPropertyContextMap() {
         final BootstrapProperties bootstrapProperties = getBootstrapProperties();
         customPropertyContextMap = new HashMap<>();
         final String contextMappingKeyPrefix = BootstrapPropertyKey.CONTEXT_MAPPING_PREFIX.getKey();
         bootstrapProperties.getPropertyKeys().stream()
                 .filter(k -> k.contains(contextMappingKeyPrefix))
-                .forEach(k -> {
-                    customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k)));
-                });
+                .forEach(k -> customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k))));
     }
 
     private String getKeyHex() {
@@ -108,11 +168,6 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
                 .orElseThrow(() -> new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf")));
     }
 
-    /**
-     * Returns the configured bootstrap properties, or the default bootstrap.conf properties if
-     * not provided.
-     * @return The bootstrap.conf properties
-     */
     private BootstrapProperties getBootstrapProperties() {
         return Optional.ofNullable(bootstrapPropertiesSupplier.get()).orElseGet(() -> {
             try {
@@ -124,81 +179,54 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
         });
     }
 
-    @Override
-    public SensitivePropertyProvider getProvider(final PropertyProtectionScheme protectionScheme) throws SensitivePropertyProtectionException {
-        Objects.requireNonNull(protectionScheme, "Protection scheme is required");
-        // Only look up the secret key, which can perform a disk read, if this provider actually requires one
-        final String keyHex = protectionScheme.requiresSecretKey() ? getKeyHex() : null;
-        switch (protectionScheme) {
-            case AES_GCM:
-                return providerMap.computeIfAbsent(protectionScheme, s -> new AESSensitivePropertyProvider(keyHex));
-            case AWS_KMS:
-                return providerMap.computeIfAbsent(protectionScheme, s -> {
-                    final AwsKmsClientProvider clientProvider = new AwsKmsClientProvider();
-                    final Properties clientProperties = getClientProperties(clientProvider);
-                    final Optional<KmsClient> kmsClient = clientProvider.getClient(clientProperties);
-                    return new AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
-                });
-            case AWS_SECRETSMANAGER:
-                return providerMap.computeIfAbsent(protectionScheme, s -> {
-                    final AwsSecretsManagerClientProvider clientProvider = new AwsSecretsManagerClientProvider();
-                    final Properties clientProperties = getClientProperties(clientProvider);
-                    final Optional<SecretsManagerClient> secretsManagerClient = clientProvider.getClient(clientProperties);
-                    return new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient.orElse(null));
-                });
-            case AZURE_KEYVAULT_KEY:
-                return providerMap.computeIfAbsent(protectionScheme, s -> {
-                    final AzureCryptographyClientProvider clientProvider = new AzureCryptographyClientProvider();
-                    final Properties clientProperties = getClientProperties(clientProvider);
-                    final Optional<CryptographyClient> cryptographyClient = clientProvider.getClient(clientProperties);
-                    return new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null), clientProperties);
-                });
-            case AZURE_KEYVAULT_SECRET:
-                return providerMap.computeIfAbsent(protectionScheme, s -> {
-                    final AzureSecretClientProvider clientProvider = new AzureSecretClientProvider();
-                    final Properties clientProperties = getClientProperties(clientProvider);
-                    final Optional<SecretClient> secretClient = clientProvider.getClient(clientProperties);
-                    return new AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
-                });
-            case GCP_KMS:
-                return providerMap.computeIfAbsent(protectionScheme, s -> {
-                    final GoogleKeyManagementServiceClientProvider clientProvider = new GoogleKeyManagementServiceClientProvider();
-                    final Properties clientProperties = getClientProperties(clientProvider);
-                    final Optional<KeyManagementServiceClient> keyManagementServiceClient = clientProvider.getClient(clientProperties);
-                    return new GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null), clientProperties);
-                });
-            case HASHICORP_VAULT_TRANSIT:
-                return providerMap.computeIfAbsent(protectionScheme, s -> new HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties()));
-            case HASHICORP_VAULT_KV:
-                return providerMap.computeIfAbsent(protectionScheme, s -> new HashiCorpVaultKeyValueSensitivePropertyProvider(getBootstrapProperties()));
-            default:
-                throw new SensitivePropertyProtectionException("Unsupported protection scheme " + protectionScheme);
-        }
+    private <T> Properties getClientProperties(final ClientProvider<T> clientProvider) {
+        final Optional<Properties> clientProperties = clientProvider.getClientProperties(getBootstrapProperties());
+        return clientProperties.orElse(null);
     }
 
-    @Override
-    public Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProviders() {
-        return Arrays.stream(PropertyProtectionScheme.values())
-                .map(this::getProvider)
-                .filter(SensitivePropertyProvider::isSupported)
-                .collect(Collectors.toList());
-    }
+    private SensitivePropertyProvider getProvider(final Class<? extends SensitivePropertyProvider> providerClass) throws SensitivePropertyProtectionException {
+        SensitivePropertyProvider provider = providers.get(providerClass);
+        if (provider == null) {
+            if (AesGcmSensitivePropertyProvider.class.equals(providerClass)) {
+                final String hexadecimalKey = getKeyHex();
+                provider = new AesGcmSensitivePropertyProvider(hexadecimalKey);
+            } else if (AwsKmsSensitivePropertyProvider.class.equals(providerClass)) {
+                final AwsKmsClientProvider clientProvider = new AwsKmsClientProvider();
+                final Properties clientProperties = getClientProperties(clientProvider);
+                final Optional<KmsClient> kmsClient = clientProvider.getClient(clientProperties);
+                provider = new AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
+            } else if (AwsSecretsManagerSensitivePropertyProvider.class.equals(providerClass)) {
+                final AwsSecretsManagerClientProvider clientProvider = new AwsSecretsManagerClientProvider();
+                final Properties clientProperties = getClientProperties(clientProvider);
+                final Optional<SecretsManagerClient> secretsManagerClient = clientProvider.getClient(clientProperties);
+                provider = new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient.orElse(null));
+            } else if (AzureKeyVaultKeySensitivePropertyProvider.class.equals(providerClass)) {
+                final AzureCryptographyClientProvider clientProvider = new AzureCryptographyClientProvider();
+                final Properties clientProperties = getClientProperties(clientProvider);
+                final Optional<CryptographyClient> cryptographyClient = clientProvider.getClient(clientProperties);
+                provider = new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null), clientProperties);
+            } else if (AzureKeyVaultSecretSensitivePropertyProvider.class.equals(providerClass)) {
+                final AzureSecretClientProvider clientProvider = new AzureSecretClientProvider();
+                final Properties clientProperties = getClientProperties(clientProvider);
+                final Optional<SecretClient> secretClient = clientProvider.getClient(clientProperties);
+                provider = new AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
+            } else if (GcpKmsSensitivePropertyProvider.class.equals(providerClass)) {
+                final GoogleKeyManagementServiceClientProvider clientProvider = new GoogleKeyManagementServiceClientProvider();
+                final Properties clientProperties = getClientProperties(clientProvider);
+                final Optional<KeyManagementServiceClient> keyManagementServiceClient = clientProvider.getClient(clientProperties);
+                provider = new GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null), clientProperties);
+            } else if (HashiCorpVaultKeyValueSensitivePropertyProvider.class.equals(providerClass)) {
+                provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(getBootstrapProperties());
+            } else if (HashiCorpVaultTransitSensitivePropertyProvider.class.equals(providerClass)) {
+                provider = new HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties());
+            }
+        }
 
-    @Override
-    public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
-        if (customPropertyContextMap == null) {
-            populateCustomPropertyContextMap();
+        if (provider == null) {
+            throw new UnsupportedOperationException(String.format("Provider class not supported [%s]", providerClass));
         }
-        final String contextName = customPropertyContextMap.entrySet().stream()
-                .filter(entry -> entry.getValue().matcher(groupIdentifier).find())
-                .map(Map.Entry::getKey)
-                .findFirst()
-                .orElse(null);
-        return ProtectedPropertyContext.contextFor(propertyName, contextName);
-    }
 
-    private <T> Properties getClientProperties(final ClientProvider<T> clientProvider) {
-        final Optional<Properties> clientProperties = clientProvider.getClientProperties(getBootstrapProperties());
-        return clientProperties.orElse(null);
+        providers.put(providerClass, provider);
+        return provider;
     }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
similarity index 51%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
index bc48e45..51764e1 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
@@ -14,26 +14,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
 
 /**
- * Abstract Microsoft Azure Client Provider
+ * Property Protection Schemes supported as arguments for encryption commands should not have direct references
  */
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
-    public AzureClientProvider() {
-        super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+enum PropertyProtectionScheme implements ProtectionScheme {
+    AES_GCM("aes/gcm"),
+
+    AWS_SECRETSMANAGER("aws/secretsmanager"),
+
+    AWS_KMS("aws/kms"),
+
+    AZURE_KEYVAULT_KEY("azure/keyvault/key"),
+
+    AZURE_KEYVAULT_SECRET("azure/keyvault/secret"),
+
+    GCP_KMS("gcp/kms"),
+
+    HASHICORP_VAULT_KV("hashicorp/vault/kv"),
+
+    HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit");
+
+    PropertyProtectionScheme(final String path) {
+        this.path = path;
     }
 
-    /**
-     * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
-     *
-     * @return Token Credential
-     */
-    protected TokenCredential getDefaultTokenCredential() {
-        return new DefaultAzureCredentialBuilder().build();
+    private final String path;
+
+    @Override
+    public String getPath() {
+        return path;
     }
 }
diff --git a/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.java
new file mode 100644
index 0000000..0c797b3
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.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.properties.scheme;
+
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Standard implementation of Protection Scheme Resolver using Property Protection Scheme enumeration
+ */
+public class StandardProtectionSchemeResolver implements ProtectionSchemeResolver {
+    /**
+     * Get Protection Scheme based on scheme matching one the supported Protection Property Scheme enumerated values
+     *
+     * @param scheme Scheme name required
+     * @return Protection Scheme
+     */
+    @Override
+    public ProtectionScheme getProtectionScheme(final String scheme) {
+        Objects.requireNonNull(scheme, "Scheme required");
+        return Arrays.stream(PropertyProtectionScheme.values())
+                .filter(propertyProtectionScheme -> propertyProtectionScheme.name().equals(scheme))
+                .findFirst()
+                .orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not supported", scheme)));
+    }
+
+    public List<String> getSupportedProtectionSchemes() {
+        return Arrays.stream(PropertyProtectionScheme.values())
+                .map(PropertyProtectionScheme::name)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
similarity index 52%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
rename to nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
index c7aa609..a17cd43 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
+++ b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
@@ -17,131 +17,88 @@
 package org.apache.nifi.properties;
 
 import org.apache.commons.io.FilenameUtils;
+import org.apache.nifi.properties.scheme.ProtectionScheme;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
 import org.apache.nifi.util.NiFiProperties;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.internal.util.io.IOUtil;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
-import java.security.Security;
+import java.util.Collection;
 import java.util.Properties;
-import java.util.function.Supplier;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class StandardSensitivePropertyProviderFactoryTest {
 
-    private static final String AES_GCM_128 = "aes/gcm/128";
     private SensitivePropertyProviderFactory factory;
 
     private static final String BOOTSTRAP_KEY_HEX = "0123456789ABCDEFFEDCBA9876543210";
-    private static final String AD_HOC_KEY_HEX = "123456789ABCDEFFEDCBA98765432101";
+
+    private static final ProtectionScheme AES_GCM = new StandardProtectionScheme("aes/gcm");
+    private static final ProtectionScheme AES_GCM_128 = new StandardProtectionScheme("aes/gcm/128");
+    private static final ProtectionScheme HASHICORP_VAULT_TRANSIT = new StandardProtectionScheme("hashicorp/vault/transit/testing");
+    private static final ProtectionScheme HASHICORP_VAULT_KV = new StandardProtectionScheme("hashicorp/vault/kv/testing");
 
     private static Path tempConfDir;
     private static Path bootstrapConf;
     private static Path hashicorpVaultBootstrapConf;
     private static Path nifiProperties;
+    private static Path azureKeyVaultConf;
     private static String defaultBootstrapContents;
 
-    private static NiFiProperties niFiProperties;
-
     @BeforeAll
     public static void initOnce() throws IOException {
-        Security.addProvider(new BouncyCastleProvider());
         tempConfDir = Files.createTempDirectory("conf");
         bootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
+        azureKeyVaultConf = Files.createTempFile("bootstrap-azure-keyvault", ".conf").toAbsolutePath();
         hashicorpVaultBootstrapConf = Files.createTempFile("bootstrap-hashicorp-vault", ".conf").toAbsolutePath();
 
         nifiProperties = Files.createTempFile("nifi", ".properties").toAbsolutePath();
 
         nifiProperties = Files.move(nifiProperties, tempConfDir.resolve("nifi.properties"));
 
-        defaultBootstrapContents = String.format("%s=%s\n%s=%s",
+        defaultBootstrapContents = String.format("%s=%s\n%s=%s\n%s=%s",
                 "nifi.bootstrap.sensitive.key", BOOTSTRAP_KEY_HEX,
+                "nifi.bootstrap.protection.azure.keyvault.conf", FilenameUtils.separatorsToUnix(azureKeyVaultConf.toString()),
                 "nifi.bootstrap.protection.hashicorp.vault.conf", FilenameUtils.separatorsToUnix(hashicorpVaultBootstrapConf.toString()));
         bootstrapConf = writeDefaultBootstrapConf();
         System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, FilenameUtils.separatorsToUnix(nifiProperties.toString()));
     }
 
-    private static Path writeDefaultBootstrapConf() throws IOException {
-        return writeBootstrapConf(defaultBootstrapContents);
-    }
-
-    private static Path writeBootstrapConf(final String contents) throws IOException {
-        final Path tempBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
-        final Path bootstrapConf = Files.move(tempBootstrapConf, tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
-
-        IOUtil.writeText(contents, bootstrapConf.toFile());
-        return bootstrapConf;
-    }
-
     @AfterAll
     public static void tearDownOnce() throws IOException {
         Files.deleteIfExists(bootstrapConf);
+        Files.deleteIfExists(azureKeyVaultConf);
         Files.deleteIfExists(hashicorpVaultBootstrapConf);
         Files.deleteIfExists(nifiProperties);
         Files.deleteIfExists(tempConfDir);
         System.clearProperty(NiFiProperties.PROPERTIES_FILE_PATH);
     }
 
-    /**
-     * Configures the factory using the default bootstrap location.
-     */
-    private void configureDefaultFactory() {
+    @BeforeEach
+    public void setFactory() {
         factory = StandardSensitivePropertyProviderFactory.withDefaults();
     }
 
-    /**
-     * Configures the factory using an ad hoc key hex.
-     */
-    private void configureAdHocKeyFactory() {
-        factory = StandardSensitivePropertyProviderFactory.withKey(AD_HOC_KEY_HEX);
-    }
-
-    /**
-     * Configures the factory using an ad hoc key hex and bootstrap.conf properties.  The key should override
-     * the on in the bootstrap.conf.
-     */
-    private void configureAdHocKeyAndPropertiesFactory() throws IOException {
-        factory = StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(AD_HOC_KEY_HEX, mockBootstrapProperties());
-    }
-
-    private Supplier<BootstrapProperties> mockBootstrapProperties() throws IOException {
-        final Properties bootstrapProperties = new Properties();
-        try (final InputStream inputStream = Files.newInputStream(bootstrapConf)) {
-            bootstrapProperties.load(inputStream);
-            return () -> new BootstrapProperties("nifi", bootstrapProperties, bootstrapConf);
-        }
-    }
-
-    private void configureHashicorpVault(final Properties properties) throws IOException {
-        try (OutputStream out = new FileOutputStream(hashicorpVaultBootstrapConf.toFile())) {
-            properties.store(out, "HashiCorpVault test");
-        }
-    }
-
     @Test
     public void testGetPropertyContextNotConfigured() {
-        configureDefaultFactory();
         assertEquals("default/prop", factory.getPropertyContext("ldap-provider", "prop").getContextKey());
     }
 
     @Test
     public void testGetPropertyContext() throws IOException {
-        configureDefaultFactory();
         writeBootstrapConf(defaultBootstrapContents + "\n" +
                 "nifi.bootstrap.protection.context.mapping.ldap=ldap-.*");
         try {
@@ -153,80 +110,97 @@ public class StandardSensitivePropertyProviderFactoryTest {
     }
 
     @Test
-    public void testHashicorpVaultTransit() throws IOException {
-        configureDefaultFactory();
+    public void testGetSupportedProviders() {
+        final Collection<SensitivePropertyProvider> providers = factory.getSupportedProviders();
+        assertFalse(providers.isEmpty());
+
+        final boolean aesProviderFound = providers.stream()
+                .anyMatch(provider -> provider instanceof AesGcmSensitivePropertyProvider);
+        assertTrue(aesProviderFound);
+    }
+
+    @Test
+    public void testAzureKeyVaultSecret() throws IOException {
         final Properties properties = new Properties();
-        properties.put("vault.transit.path", "nifi-transit");
-        configureHashicorpVault(properties);
+        properties.put("azure.keyvault.uri", "https://testing.vault.azure.net");
+        configureAzureKeyVault(properties);
 
-        factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
+        final SensitivePropertyProvider provider = factory.getProvider(new StandardProtectionScheme("azure/keyvault/secret"));
+        assertTrue(provider.isSupported());
+        assertEquals(AzureKeyVaultSecretSensitivePropertyProvider.class, provider.getClass());
     }
 
     @Test
-    public void testHashicorpVaultTransitSupported() throws IOException {
-        configureDefaultFactory();
+    public void testHashiCorpVaultKeyVaultSupported() throws IOException {
         final Properties properties = new Properties();
-        properties.put("vault.transit.path", "nifi-transit");
+        properties.put("vault.kv.path", "testing");
         properties.put("vault.uri", "http://localhost:8200");
         properties.put("vault.token", "test-token");
         configureHashicorpVault(properties);
 
-        SensitivePropertyProvider spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
-        assertTrue(spp.isSupported());
+        final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_KV);
+        assertTrue(provider.isSupported());
+        assertEquals(HashiCorpVaultKeyValueSensitivePropertyProvider.class, provider.getClass());
+    }
 
-        properties.remove("vault.uri");
+    @Test
+    public void testHashiCorpVaultTransitSupported() throws IOException {
+        final Properties properties = new Properties();
+        properties.put("vault.transit.path", "testing");
+        properties.put("vault.uri", "http://localhost:8200");
+        properties.put("vault.token", "test-token");
         configureHashicorpVault(properties);
-        configureDefaultFactory();
-        spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
-        assertFalse(spp.isSupported());
 
-        properties.put("vault.uri", "http://localhost:8200");
-        properties.remove("vault.transit.path");
-        spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
-        assertFalse(spp.isSupported());
+        final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_TRANSIT);
+        assertTrue(provider.isSupported());
+        assertEquals(HashiCorpVaultTransitSensitivePropertyProvider.class, provider.getClass());
     }
 
     @Test
-    public void testHashicorpVaultTransitInvalidCharacters() throws IOException {
-        configureDefaultFactory();
+    public void testHashiCorpVaultTransitExceptionWhenMissingProperties() throws IOException {
         final Properties properties = new Properties();
-        properties.put("vault.transit.path", "invalid/characters");
+        properties.put("vault.uri", "http://localhost:8200");
         configureHashicorpVault(properties);
 
-        assertThrows(SensitivePropertyProtectionException.class, () -> factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT));
+        assertThrows(SensitivePropertyProtectionException.class, () -> factory.getProvider(HASHICORP_VAULT_TRANSIT));
     }
 
     @Test
-    public void testAesGcm() throws IOException {
-        configureDefaultFactory();
-        final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext("propertyName");
-
-        final SensitivePropertyProvider spp = factory.getProvider(PropertyProtectionScheme.AES_GCM);
-        assertNotNull(spp);
-        assertTrue(spp.isSupported());
-
-        final String cleartext = "test";
-        assertEquals(cleartext, spp.unprotect(spp.protect(cleartext, context), context));
-        assertNotEquals(cleartext, spp.protect(cleartext, context));
-        assertEquals(AES_GCM_128, spp.getIdentifierKey());
-
-        // Key is now different
-        configureAdHocKeyFactory();
-        final SensitivePropertyProvider sppAdHocKey = factory.getProvider(PropertyProtectionScheme.AES_GCM);
-        assertNotNull(sppAdHocKey);
-        assertTrue(sppAdHocKey.isSupported());
-        assertEquals(AES_GCM_128, sppAdHocKey.getIdentifierKey());
-
-        assertNotEquals(spp.protect(cleartext, context), sppAdHocKey.protect(cleartext, context));
-        assertEquals(cleartext, sppAdHocKey.unprotect(sppAdHocKey.protect(cleartext, context), context));
-
-        // This should use the same keyHex as the second one
-        configureAdHocKeyAndPropertiesFactory();
-        final SensitivePropertyProvider sppKeyProperties = factory.getProvider(PropertyProtectionScheme.AES_GCM);
-        assertNotNull(sppKeyProperties);
-        assertTrue(sppKeyProperties.isSupported());
-        assertEquals(AES_GCM_128, sppKeyProperties.getIdentifierKey());
-
-        assertEquals(cleartext, sppKeyProperties.unprotect(sppKeyProperties.protect(cleartext, context), context));
+    public void testAesGcmWithoutKeySizeSupported() {
+        final SensitivePropertyProvider provider = factory.getProvider(AES_GCM);
+        assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
+        assertTrue(provider.isSupported());
+    }
+
+    @Test
+    public void testAesGcm128Supported() {
+        final SensitivePropertyProvider provider = factory.getProvider(AES_GCM_128);
+        assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
+        assertTrue(provider.isSupported());
+    }
+
+
+    private static Path writeDefaultBootstrapConf() throws IOException {
+        return writeBootstrapConf(defaultBootstrapContents);
+    }
+
+    private static Path writeBootstrapConf(final String contents) throws IOException {
+        final Path tempBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
+        final Path bootstrapConf = Files.move(tempBootstrapConf, tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
+
+        Files.write(bootstrapConf, contents.getBytes(StandardCharsets.UTF_8));
+        return bootstrapConf;
+    }
+
+    private void configureHashicorpVault(final Properties properties) throws IOException {
+        try (OutputStream out = new FileOutputStream(hashicorpVaultBootstrapConf.toFile())) {
+            properties.store(out, hashicorpVaultBootstrapConf.toString());
+        }
+    }
+
+    private void configureAzureKeyVault(final Properties properties) throws IOException {
+        try (OutputStream out = new FileOutputStream(azureKeyVaultConf.toFile())) {
+            properties.store(out, azureKeyVaultConf.toString());
+        }
     }
 }
diff --git a/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.java b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.java
new file mode 100644
index 0000000..9cfc499
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.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.properties.scheme;
+
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StandardProtectionSchemeResolverTest {
+    private static final String AES_GCM = "AES_GCM";
+
+    private static final String AES_GCM_PATH = "aes/gcm";
+
+    private static final String UNKNOWN = "UNKNOWN";
+
+    private StandardProtectionSchemeResolver resolver;
+
+    @BeforeEach
+    public void setResolver() {
+        resolver = new StandardProtectionSchemeResolver();
+    }
+
+    @Test
+    public void getProtectionSchemeAesGcmFound() {
+        final ProtectionScheme protectionScheme = resolver.getProtectionScheme(AES_GCM);
+        assertNotNull(protectionScheme);
+        assertEquals(AES_GCM_PATH, protectionScheme.getPath());
+    }
+
+    @Test
+    public void getProtectionSchemeUnknownNotFound() {
+        final SensitivePropertyProtectionException exception = assertThrows(SensitivePropertyProtectionException.class, () -> resolver.getProtectionScheme(UNKNOWN));
+        assertTrue(exception.getMessage().contains(UNKNOWN));
+    }
+}
diff --git a/nifi-commons/nifi-property-protection-gcp/pom.xml b/nifi-commons/nifi-property-protection-gcp/pom.xml
new file mode 100644
index 0000000..dd05400
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-gcp/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-gcp</artifactId>
+    <properties>
+        <gcp.sdk.version>24.1.2</gcp.sdk.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.google.cloud</groupId>
+                <artifactId>libraries-bom</artifactId>
+                <version>${gcp.sdk.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-shared</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-kms</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
index c7b275d..af76dc8 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
@@ -37,10 +37,17 @@ public class GcpKmsSensitivePropertyProvider extends ClientBasedEncodedSensitive
     protected static final String KEYRING_PROPERTY = "gcp.kms.keyring";
     protected static final String KEY_PROPERTY = "gcp.kms.key";
 
+    private static final String SCHEME_BASE_PATH = "gcp/kms";
+
     private CryptoKeyName cryptoKeyName;
 
     GcpKmsSensitivePropertyProvider(final KeyManagementServiceClient keyManagementServiceClient, final Properties properties) {
-        super(PropertyProtectionScheme.GCP_KMS, keyManagementServiceClient, properties);
+        super(keyManagementServiceClient, properties);
+    }
+
+    @Override
+    public String getIdentifierKey() {
+        return SCHEME_BASE_PATH;
     }
 
     /**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
rename to nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-gcp/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-gcp/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-property-protection-hashicorp/pom.xml b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
new file mode 100644
index 0000000..5abaaca
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-hashicorp</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-vault-utils</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
similarity index 89%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
index f992ccd..6ccb457 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
 import java.io.IOException;
 import java.nio.file.Paths;
 
-public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
+public abstract class AbstractHashiCorpVaultSensitivePropertyProvider implements SensitivePropertyProvider {
     private static final String VAULT_PREFIX = "vault";
 
     private final String path;
@@ -34,8 +34,6 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
     private final BootstrapProperties vaultBootstrapProperties;
 
     AbstractHashiCorpVaultSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
-        super(bootstrapProperties);
-
         final String vaultBootstrapConfFilename = bootstrapProperties
                 .getProperty(BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
         vaultBootstrapProperties = getVaultBootstrapProperties(vaultBootstrapConfFilename);
@@ -87,7 +85,7 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
 
     protected HashiCorpVaultCommunicationService getVaultCommunicationService() {
         if (vaultCommunicationService == null) {
-            throw new SensitivePropertyProtectionException(getIdentifierKey() + " protection scheme is not fully configured in hashicorp-vault-bootstrap.conf");
+            throw new SensitivePropertyProtectionException("Vault Protection Scheme missing required properties");
         }
         return vaultCommunicationService;
     }
@@ -113,19 +111,14 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
     }
 
     /**
-     * Returns the key used to identify the provider implementation in {@code nifi.properties},
-     * in the format 'vault/{secretsEngine}/{secretsEnginePath}'.
-     *
-     * @return the key to persist in the sibling property
-     */
-    @Override
-    public String getIdentifierKey() {
-        return getProtectionScheme().getIdentifier(path);
-    }
-
-    /**
      * No cleanup necessary
      */
     @Override
     public void cleanUp() { }
+
+    protected void requireNotBlank(final String value) {
+        if (value == null || value.isEmpty()) {
+            throw new IllegalArgumentException("Property value is null or empty");
+        }
+    }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
similarity index 78%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
index d373a5a..f846490 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
@@ -16,17 +16,17 @@
  */
 package org.apache.nifi.properties;
 
-import org.apache.commons.lang3.StringUtils;
-
 import java.util.Objects;
 
 /**
- * Uses the HashiCorp Vault Key/Value (unversioned) Secrets Engine to store sensitive values.
+ * Uses the HashiCorp Vault Key/Value version 1 Secrets Engine to store sensitive values.
  */
 public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHashiCorpVaultSensitivePropertyProvider {
 
     private static final String KEY_VALUE_PATH = "vault.kv.path";
 
+    private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/kv/%s";
+
     HashiCorpVaultKeyValueSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
         super(bootstrapProperties);
     }
@@ -36,20 +36,12 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
         if (vaultBootstrapProperties == null) {
             return null;
         }
-        final String kvPath = vaultBootstrapProperties.getProperty(KEY_VALUE_PATH);
-        // Validate transit path
-        try {
-            PropertyProtectionScheme.fromIdentifier(getProtectionScheme().getIdentifier(kvPath));
-        } catch (IllegalArgumentException e) {
-            throw new SensitivePropertyProtectionException(String.format("%s [%s] contains unsupported characters", KEY_VALUE_PATH, kvPath), e);
-        }
-
-        return kvPath;
+        return vaultBootstrapProperties.getProperty(KEY_VALUE_PATH);
     }
 
     @Override
-    protected PropertyProtectionScheme getProtectionScheme() {
-        return PropertyProtectionScheme.HASHICORP_VAULT_KV;
+    public String getIdentifierKey() {
+        return String.format(IDENTIFIER_KEY_FORMAT, getPath());
     }
 
     /**
@@ -62,9 +54,7 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
      */
     @Override
     public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
-        if (StringUtils.isBlank(unprotectedValue)) {
-            throw new IllegalArgumentException("Cannot protect an empty value");
-        }
+        requireNotBlank(unprotectedValue);
         Objects.requireNonNull(context, "Context is required to protect a value");
 
         getVaultCommunicationService().writeKeyValueSecret(getPath(), context.getContextKey(), unprotectedValue);
@@ -77,7 +67,7 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
      * @param protectedValue The value read from {@code nifi.properties} file.  Ignored in this provider.
      * @param context The property context, from which the Vault secret name is pulled
      * @return the raw value to be used by the application
-     * @throws SensitivePropertyProtectionException if there is an error retrieving the scret
+     * @throws SensitivePropertyProtectionException if there is an error retrieving the secret
      */
     @Override
     public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
similarity index 75%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
index 452ee14..9b8cc4b 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
@@ -16,8 +16,6 @@
  */
 package org.apache.nifi.properties;
 
-import org.apache.commons.lang3.StringUtils;
-
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
@@ -28,6 +26,8 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
     private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
     private static final String TRANSIT_PATH = "vault.transit.path";
 
+    private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/transit/%s";
+
     HashiCorpVaultTransitSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
         super(bootstrapProperties);
     }
@@ -37,20 +37,12 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
         if (vaultBootstrapProperties == null) {
             return null;
         }
-        final String transitPath = vaultBootstrapProperties.getProperty(TRANSIT_PATH);
-        // Validate transit path
-        try {
-            PropertyProtectionScheme.fromIdentifier(getProtectionScheme().getIdentifier(transitPath));
-        } catch (IllegalArgumentException e) {
-            throw new SensitivePropertyProtectionException(String.format("%s [%s] contains unsupported characters", TRANSIT_PATH, transitPath), e);
-        }
-
-        return transitPath;
+        return vaultBootstrapProperties.getProperty(TRANSIT_PATH);
     }
 
     @Override
-    protected PropertyProtectionScheme getProtectionScheme() {
-        return PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT;
+    public String getIdentifierKey() {
+        return String.format(IDENTIFIER_KEY_FORMAT, getPath());
     }
 
     /**
@@ -63,10 +55,7 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
      */
     @Override
     public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
-        if (StringUtils.isBlank(unprotectedValue)) {
-            throw new IllegalArgumentException("Cannot encrypt an empty value");
-        }
-
+        requireNotBlank(unprotectedValue);
         return getVaultCommunicationService().encrypt(getPath(), unprotectedValue.getBytes(PROPERTY_CHARSET));
     }
 
@@ -80,10 +69,7 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
      */
     @Override
     public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
-        if (StringUtils.isBlank(protectedValue)) {
-            throw new IllegalArgumentException("Cannot decrypt an empty value");
-        }
-
+        requireNotBlank(protectedValue);
         return new String(getVaultCommunicationService().decrypt(getPath(), protectedValue), PROPERTY_CHARSET);
     }
 }
diff --git a/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..d421de8
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.eq;
+
+@ExtendWith(MockitoExtension.class)
+public class HashiCorpVaultKeyValueSensitivePropertyProviderTest {
+    private static final String IDENTIFIER_KEY = "hashicorp/vault/kv/null";
+
+    @Mock
+    private BootstrapProperties bootstrapProperties;
+
+    @Test
+    public void testGetIdentifierKeyPropertiesNotFound() {
+        when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+        final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
+
+        final String identifierKey = provider.getIdentifierKey();
+
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
+
+    @Test
+    public void testIsSupportedPropertiesNotFound() {
+        when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+        final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
+
+        assertFalse(provider.isSupported());
+    }
+}
diff --git a/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..c446046
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class HashiCorpVaultTransitSensitivePropertyProviderTest {
+    private static final String IDENTIFIER_KEY = "hashicorp/vault/transit/null";
+
+    @Mock
+    private BootstrapProperties bootstrapProperties;
+
+    @Test
+    public void testGetIdentifierKeyPropertiesNotFound() {
+        when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+        final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
+
+        final String identifierKey = provider.getIdentifierKey();
+
+        assertEquals(IDENTIFIER_KEY, identifierKey);
+    }
+
+    @Test
+    public void testIsSupportedPropertiesNotFound() {
+        when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+        final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
+
+        assertFalse(provider.isSupported());
+    }
+}
diff --git a/nifi-commons/nifi-property-protection-shared/pom.xml b/nifi-commons/nifi-property-protection-shared/pom.xml
new file mode 100644
index 0000000..cddf0a8
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-shared/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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-commons</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-property-protection-shared</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
similarity index 92%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
index 6887378..c5753b3 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
@@ -33,10 +33,8 @@ public abstract class ClientBasedEncodedSensitivePropertyProvider<T> extends Enc
 
     private final Properties properties;
 
-    public ClientBasedEncodedSensitivePropertyProvider(final PropertyProtectionScheme propertyProtectionScheme,
-                                                       final T client,
+    public ClientBasedEncodedSensitivePropertyProvider(final T client,
                                                        final Properties properties) {
-        super(propertyProtectionScheme);
         this.client = client;
         this.properties = properties;
         validate(client);
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
similarity index 83%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
index b288402..1a1a217 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
@@ -31,32 +31,6 @@ public abstract class EncodedSensitivePropertyProvider implements SensitivePrope
 
     private static final Base64.Decoder DECODER = Base64.getDecoder();
 
-    private final PropertyProtectionScheme propertyProtectionScheme;
-
-    public EncodedSensitivePropertyProvider(final PropertyProtectionScheme propertyProtectionScheme) {
-        this.propertyProtectionScheme = Objects.requireNonNull(propertyProtectionScheme, "Scheme Required");
-    }
-
-    /**
-     * Get Property Protection Scheme Name
-     *
-     * @return Property Protection Scheme Name
-     */
-    @Override
-    public String getName() {
-        return propertyProtectionScheme.getName();
-    }
-
-    /**
-     * Get Identifier Key based on Property Protection Scheme
-     *
-     * @return Property Protection Scheme Identifier
-     */
-    @Override
-    public String getIdentifierKey() {
-        return propertyProtectionScheme.getIdentifier();
-    }
-
     /**
      * Protect property value and return Base64-encoded representation of encrypted bytes
      *
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
index b3bfbd7..ac8c82a 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.properties.configuration;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.properties.BootstrapProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,7 +66,7 @@ public abstract class BootstrapPropertiesClientProvider<T> implements ClientProv
     public Optional<Properties> getClientProperties(final BootstrapProperties bootstrapProperties) {
         Objects.requireNonNull(bootstrapProperties, "Bootstrap Properties required");
         final String clientBootstrapPropertiesPath = bootstrapProperties.getProperty(bootstrapPropertyKey).orElse(null);
-        if (StringUtils.isBlank(clientBootstrapPropertiesPath)) {
+        if (clientBootstrapPropertiesPath == null || clientBootstrapPropertiesPath.isEmpty()) {
             logger.debug("Client Properties [{}] not configured", bootstrapPropertyKey);
             return Optional.empty();
         } else {
@@ -106,8 +105,9 @@ public abstract class BootstrapPropertiesClientProvider<T> implements ClientProv
     protected abstract Set<String> getRequiredPropertyNames();
 
     private boolean isMissingProperties(final Properties clientProperties) {
-        return clientProperties == null || getRequiredPropertyNames().stream().anyMatch(propertyName ->
-                StringUtils.isBlank(clientProperties.getProperty(propertyName))
-        );
+        return clientProperties == null || getRequiredPropertyNames().stream().anyMatch(propertyName -> {
+            final String property = clientProperties.getProperty(propertyName);
+            return property == null || property.isEmpty();
+        });
     }
 }
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
deleted file mode 100644
index 543b166..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.DecoderException;
-import org.bouncycastle.util.encoders.EncoderException;
-import org.bouncycastle.util.encoders.Hex;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class AESSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
-    private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProvider.class);
-
-    private static final String IMPLEMENTATION_NAME = "AES Sensitive Property Provider";
-    private static final String ALGORITHM = "AES/GCM/NoPadding";
-    private static final String PROVIDER = "BC";
-    private static final String DELIMITER = "||"; // "|" is not a valid Base64 character, so ensured not to be present in cipher text
-    private static final int IV_LENGTH = 12;
-    private static final int MIN_CIPHER_TEXT_LENGTH = IV_LENGTH * 4 / 3 + DELIMITER.length() + 1;
-
-    private final Cipher cipher;
-    private final SecretKey key;
-    private final int keySize;
-
-    AESSensitivePropertyProvider(final byte[] keyHex) {
-        this(keyHex == null ? "" : Hex.toHexString(keyHex));
-    }
-
-    AESSensitivePropertyProvider(final String keyHex) {
-        super(null);
-
-        byte[] keyBytes = validateKey(keyHex);
-
-        try {
-            this.cipher = Cipher.getInstance(ALGORITHM, PROVIDER);
-            // Only store the key if the cipher was initialized successfully
-            this.key = new SecretKeySpec(keyBytes, "AES");
-            this.keySize = getKeySize(Hex.toHexString(this.key.getEncoded()));
-        } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
-            logger.error("Encountered an error initializing the {}: {}", IMPLEMENTATION_NAME, e.getMessage());
-            throw new SensitivePropertyProtectionException("Error initializing the protection cipher", e);
-        }
-    }
-
-    @Override
-    protected PropertyProtectionScheme getProtectionScheme() {
-        return PropertyProtectionScheme.AES_GCM;
-    }
-
-    @Override
-    public boolean isSupported() {
-        return true; // AES protection is always supported
-    }
-
-    private byte[] validateKey(String keyHex) {
-        if (keyHex == null || StringUtils.isBlank(keyHex)) {
-            throw new SensitivePropertyProtectionException("The key cannot be empty");
-        }
-        keyHex = formatHexKey(keyHex);
-        if (!isHexKeyValid(keyHex)) {
-            throw new SensitivePropertyProtectionException("The key must be a valid hexadecimal key");
-        }
-        byte[] key = Hex.decode(keyHex);
-        final List<Integer> validKeyLengths = getValidKeyLengths();
-        if (!validKeyLengths.contains(key.length * 8)) {
-            List<String> validKeyLengthsAsStrings = validKeyLengths.stream().map(i -> Integer.toString(i)).collect(Collectors.toList());
-            throw new SensitivePropertyProtectionException("The key (" + key.length * 8 + " bits) must be a valid length: " + StringUtils.join(validKeyLengthsAsStrings, ", "));
-        }
-        return key;
-    }
-
-    private static String formatHexKey(String input) {
-        if (input == null || StringUtils.isBlank(input)) {
-            return "";
-        }
-        return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
-    }
-
-    private static boolean isHexKeyValid(String key) {
-        if (key == null || StringUtils.isBlank(key)) {
-            return false;
-        }
-        // Key length is in "nibbles" (i.e. one hex char = 4 bits)
-        return getValidKeyLengths().contains(key.length() * 4) && key.matches("^[0-9a-fA-F]*$");
-    }
-
-    private static List<Integer> getValidKeyLengths() {
-        List<Integer> validLengths = new ArrayList<>();
-        validLengths.add(128);
-
-        try {
-            if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
-                validLengths.add(192);
-                validLengths.add(256);
-            } else {
-                logger.warn("JCE Unlimited Strength Cryptography Jurisdiction policies are not available, so the max key length is 128 bits");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            logger.warn("Encountered an error determining the max key length", e);
-        }
-
-        return validLengths;
-    }
-
-    /**
-     * Returns the key used to identify the provider implementation in {@code nifi.properties}.
-     *
-     * @return the key to persist in the sibling property
-     */
-    @Override
-    public String getIdentifierKey() {
-        return getProtectionScheme().getIdentifier(String.valueOf(keySize));
-    }
-
-    private static int getKeySize(final String key) {
-        // A key in hexadecimal format has one char per nibble (4 bits)
-        return StringUtils.isBlank(key) ? 0 : formatHexKey(key).length() * 4;
-    }
-
-    /**
-     * Returns the encrypted cipher text.
-     *
-     * @param unprotectedValue the sensitive value
-     * @param context The property context, unused in this provider
-     * @return the value to persist in the {@code nifi.properties} file
-     * @throws SensitivePropertyProtectionException if there is an exception encrypting the value
-     */
-    @Override
-    public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
-        if (StringUtils.isBlank(unprotectedValue)) {
-            throw new IllegalArgumentException("Cannot encrypt an empty value");
-        }
-
-        // Generate IV
-        byte[] iv = generateIV();
-        if (iv.length < IV_LENGTH) {
-            throw new IllegalArgumentException("The IV (" + iv.length + " bytes) must be at least " + IV_LENGTH + " bytes");
-        }
-
-        try {
-            // Initialize cipher for encryption
-            cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(iv));
-
-            byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
-            byte[] cipherBytes = cipher.doFinal(plainBytes);
-            logger.debug(getName() + " encrypted a sensitive value successfully");
-            return base64Encode(iv) + DELIMITER + base64Encode(cipherBytes);
-            // return Base64.toBase64String(iv) + DELIMITER + Base64.toBase64String(cipherBytes);
-        } catch (BadPaddingException | IllegalBlockSizeException | EncoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
-            final String msg = "Error encrypting a protected value";
-            logger.error(msg, e);
-            throw new SensitivePropertyProtectionException(msg, e);
-        }
-    }
-
-    private String base64Encode(final byte[] input) {
-        return Base64.toBase64String(input).replaceAll("=", "");
-    }
-
-    /**
-     * Generates a new random IV of 12 bytes using {@link java.security.SecureRandom}.
-     *
-     * @return the IV
-     */
-    private byte[] generateIV() {
-        final byte[] iv = new byte[IV_LENGTH];
-        new SecureRandom().nextBytes(iv);
-        return iv;
-    }
-
-    /**
-     * Returns the decrypted plaintext.
-     *
-     * @param protectedValue the cipher text read from the {@code nifi.properties} file
-     * @param context The property context, unused in this provider
-     * @return the raw value to be used by the application
-     * @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
-     */
-    @Override
-    public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
-        if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
-            throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
-        }
-
-        if (!protectedValue.contains(DELIMITER)) {
-            throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)");
-        }
-        final String trimmedProtectedValue = protectedValue.trim();
-
-        final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER));
-        final byte[] iv = Base64.decode(armoredIV);
-        if (iv.length < IV_LENGTH) {
-            throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH));
-        }
-
-        String armoredCipherText = trimmedProtectedValue.substring(trimmedProtectedValue.indexOf(DELIMITER) + 2);
-
-        // Restore the = padding if necessary to reconstitute the GCM MAC check
-        if (armoredCipherText.length() % 4 != 0) {
-            final int paddedLength = armoredCipherText.length() + 4 - (armoredCipherText.length() % 4);
-            armoredCipherText = StringUtils.rightPad(armoredCipherText, paddedLength, '=');
-        }
-
-        try {
-            final byte[] cipherBytes = Base64.decode(armoredCipherText);
-
-            cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(iv));
-            final byte[] plainBytes = cipher.doFinal(cipherBytes);
-            logger.debug(getName() + " decrypted a sensitive value successfully");
-            return new String(plainBytes, StandardCharsets.UTF_8);
-        } catch (BadPaddingException | IllegalBlockSizeException | DecoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
-            final String msg = "Error decrypting a protected value";
-            logger.error(msg, e);
-            throw new SensitivePropertyProtectionException(msg, e);
-        }
-    }
-
-    public static int getIvLength() {
-        return IV_LENGTH;
-    }
-
-    public static int getMinCipherTextLength() {
-        return MIN_CIPHER_TEXT_LENGTH;
-    }
-
-    public static String getDelimiter() {
-        return DELIMITER;
-    }
-
-    /**
-     * No cleanup necessary
-     */
-    @Override
-    public void cleanUp() { }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java
deleted file mode 100644
index e163747..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties;
-
-public abstract class AbstractSensitivePropertyProvider implements SensitivePropertyProvider {
-    private final BootstrapProperties bootstrapProperties;
-
-    public AbstractSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
-        this.bootstrapProperties = bootstrapProperties;
-    }
-
-    protected BootstrapProperties getBootstrapProperties() {
-        return bootstrapProperties;
-    }
-
-    /**
-     * Return the appropriate PropertyProtectionScheme for this provider.
-     * @return The PropertyProtectionScheme
-     */
-    protected abstract PropertyProtectionScheme getProtectionScheme();
-
-    @Override
-    public String getName() {
-        return getProtectionScheme().getName();
-    }
-
-    /**
-     * Default implementation to return the protection scheme identifier, with no args to populate the identifier key.
-     * Concrete classes may choose to override this in order to fill in the identifier with specific args.
-     * @return The identifier key
-     */
-    @Override
-    public String getIdentifierKey() {
-        return getProtectionScheme().getIdentifier();
-    }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java
deleted file mode 100644
index 3b6f3cd..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-
-public class MultipleSensitivePropertyProtectionException extends SensitivePropertyProtectionException {
-
-    private Set<String> failedKeys;
-
-    /**
-     * Constructs a new throwable with {@code null} as its detail message.
-     * The cause is not initialized, and may subsequently be initialized by a
-     * call to {@link #initCause}.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     */
-    public MultipleSensitivePropertyProtectionException() {
-    }
-
-    /**
-     * Constructs a new throwable with the specified detail message.  The
-     * cause is not initialized, and may subsequently be initialized by
-     * a call to {@link #initCause}.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param message the detail message. The detail message is saved for
-     *                later retrieval by the {@link #getMessage()} method.
-     */
-    public MultipleSensitivePropertyProtectionException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new throwable with the specified detail message and
-     * cause.  <p>Note that the detail message associated with
-     * {@code cause} is <i>not</i> automatically incorporated in
-     * this throwable's detail message.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param message the detail message (which is saved for later retrieval
-     *                by the {@link #getMessage()} method).
-     * @param cause   the cause (which is saved for later retrieval by the
-     *                {@link #getCause()} method).  (A {@code null} value is
-     *                permitted, and indicates that the cause is nonexistent or
-     *                unknown.)
-     * @since 1.4
-     */
-    public MultipleSensitivePropertyProtectionException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Constructs a new throwable with the specified cause and a detail
-     * message of {@code (cause==null ? null : cause.toString())} (which
-     * typically contains the class and detail message of {@code cause}).
-     * This constructor is useful for throwables that are little more than
-     * wrappers for other throwables (for example, PrivilegedActionException).
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param cause the cause (which is saved for later retrieval by the
-     *              {@link #getCause()} method).  (A {@code null} value is
-     *              permitted, and indicates that the cause is nonexistent or
-     *              unknown.)
-     * @since 1.4
-     */
-    public MultipleSensitivePropertyProtectionException(Throwable cause) {
-        super(cause);
-    }
-
-    /**
-     * Constructs a new exception with the provided message and a unique set of the keys that caused the error.
-     *
-     * @param message    the message
-     * @param failedKeys any failed keys
-     */
-    public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys) {
-        this(message, failedKeys, null);
-    }
-
-    /**
-     * Constructs a new exception with the provided message and a unique set of the keys that caused the error.
-     *
-     * @param message    the message
-     * @param failedKeys any failed keys
-     * @param cause      the cause (which is saved for later retrieval by the
-     *                   {@link #getCause()} method).  (A {@code null} value is
-     *                   permitted, and indicates that the cause is nonexistent or
-     *                   unknown.)
-     */
-    public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys, Throwable cause) {
-        super(message, cause);
-        this.failedKeys = new HashSet<>(failedKeys);
-    }
-
-    public Set<String> getFailedKeys() {
-        return this.failedKeys;
-    }
-
-    @Override
-    public String toString() {
-        return "SensitivePropertyProtectionException for [" + StringUtils.join(this.failedKeys, ", ") + "]: " + getLocalizedMessage();
-    }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
deleted file mode 100644
index 4a85799..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties;
-
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * A scheme for protecting sensitive properties.  Each scheme is intended to be backed by an implementation of
- * SensitivePropertyProvider.
- */
-public enum PropertyProtectionScheme {
-    AES_GCM("aes/gcm/(128|192|256)", "aes/gcm/%s", "AES Sensitive Property Provider", true),
-    AWS_SECRETSMANAGER("aws/secretsmanager", "aws/secretsmanager", "AWS Secrets Manager Sensitive Property Provider", false),
-    AWS_KMS("aws/kms", "aws/kms", "AWS KMS Sensitive Property Provider", false),
-    AZURE_KEYVAULT_KEY("azure/keyvault/key", "azure/keyvault/key", "Azure Key Vault Key Sensitive Property Provider", false),
-    AZURE_KEYVAULT_SECRET("azure/keyvault/secret", "azure/keyvault/secret", "Azure Key Vault Secret Sensitive Property Provider", false),
-    GCP_KMS("gcp/kms", "gcp/kms", "GCP Cloud KMS Sensitive Property Provider", false),
-    HASHICORP_VAULT_KV("hashicorp/vault/kv/[a-zA-Z0-9_-]+", "hashicorp/vault/kv/%s", "HashiCorp Vault Key/Value Engine Sensitive Property Provider", false),
-    HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit/[a-zA-Z0-9_-]+", "hashicorp/vault/transit/%s", "HashiCorp Vault Transit Engine Sensitive Property Provider", false);
-
-    PropertyProtectionScheme(final String identifierPattern, final String identifierFormat, final String name, final boolean requiresSecretKey) {
-        this.identifierPattern = identifierPattern;
-        this.identifierFormat = identifierFormat;
-        this.name = name;
-        this.requiresSecretKey = requiresSecretKey;
-    }
-
-    private final String identifierFormat;
-    private final String identifierPattern;
-    private final String name;
-    private final boolean requiresSecretKey;
-
-    /**
-     * Returns a the identifier of the PropertyProtectionScheme.
-     * @param args scheme-specific arguments used to fill in the formatted identifierPattern
-     * @return The identifier of the PropertyProtectionScheme
-     */
-    public String getIdentifier(final String... args) {
-        return String.format(identifierFormat, args);
-    }
-
-    /**
-     * Returns whether this scheme requires a secret key.
-     * @return True if this scheme requires a secret key
-     */
-    public boolean requiresSecretKey() {
-        return requiresSecretKey;
-    }
-
-    /**
-     * Returns the name of the PropertyProtectionScheme.
-     * @return The name
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns the PropertyProtectionScheme matching the provided name.
-     * @param identifier The unique PropertyProtectionScheme identifier
-     * @return The matching PropertyProtectionScheme
-     * @throws IllegalArgumentException If the name was not recognized
-     */
-    public static PropertyProtectionScheme fromIdentifier(final String identifier) {
-        Objects.requireNonNull(identifier, "Identifier must be specified");
-        return Arrays.stream(PropertyProtectionScheme.values())
-                .filter(scheme -> identifier.matches(scheme.identifierPattern))
-                .findFirst()
-                .orElseThrow(() -> new IllegalArgumentException("Unrecognized protection scheme :" + identifier));
-    }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
deleted file mode 100644
index 2870c2a..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties;
-
-public class SensitivePropertyProtectionException extends RuntimeException {
-    /**
-     * Constructs a new throwable with {@code null} as its detail message.
-     * The cause is not initialized, and may subsequently be initialized by a
-     * call to {@link #initCause}.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     */
-    public SensitivePropertyProtectionException() {
-    }
-
-    /**
-     * Constructs a new throwable with the specified detail message.  The
-     * cause is not initialized, and may subsequently be initialized by
-     * a call to {@link #initCause}.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param message the detail message. The detail message is saved for
-     *                later retrieval by the {@link #getMessage()} method.
-     */
-    public SensitivePropertyProtectionException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new throwable with the specified detail message and
-     * cause.  <p>Note that the detail message associated with
-     * {@code cause} is <i>not</i> automatically incorporated in
-     * this throwable's detail message.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param message the detail message (which is saved for later retrieval
-     *                by the {@link #getMessage()} method).
-     * @param cause   the cause (which is saved for later retrieval by the
-     *                {@link #getCause()} method).  (A {@code null} value is
-     *                permitted, and indicates that the cause is nonexistent or
-     *                unknown.)
-     * @since 1.4
-     */
-    public SensitivePropertyProtectionException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Constructs a new throwable with the specified cause and a detail
-     * message of {@code (cause==null ? null : cause.toString())} (which
-     * typically contains the class and detail message of {@code cause}).
-     * This constructor is useful for throwables that are little more than
-     * wrappers for other throwables (for example, PrivilegedActionException).
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created throwable.
-     *
-     * @param cause the cause (which is saved for later retrieval by the
-     *              {@link #getCause()} method).  (A {@code null} value is
-     *              permitted, and indicates that the cause is nonexistent or
-     *              unknown.)
-     * @since 1.4
-     */
-    public SensitivePropertyProtectionException(Throwable cause) {
-        super(cause);
-    }
-
-    @Override
-    public String toString() {
-        return "SensitivePropertyProtectionException: " + getLocalizedMessage();
-    }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy b/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy
deleted file mode 100644
index fe838c3..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeAll
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.SecureRandom
-import java.security.Security
-
-import static org.junit.Assume.assumeTrue
-
-class AESSensitivePropertyProviderTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProviderTest.class)
-
-    private static final String KEY_128_HEX = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_256_HEX = KEY_128_HEX * 2
-    private static final int IV_LENGTH = AESSensitivePropertyProvider.getIvLength()
-
-    private static final List<Integer> KEY_SIZES = getAvailableKeySizes()
-
-    private static final SecureRandom secureRandom = new SecureRandom()
-
-    private static final ProtectedPropertyContext PROPERTY_CONTEXT = ProtectedPropertyContext.defaultContext("propertyName")
-
-    private static final Base64.Encoder encoder = Base64.encoder
-    private static final Base64.Decoder decoder = Base64.decoder
-
-    @BeforeAll
-    static void setUpOnce() throws Exception {
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-    }
-
-    @BeforeEach
-    void setUp() throws Exception {
-
-    }
-
-    @AfterEach
-    void tearDown() throws Exception {
-
-    }
-
-    private static Cipher getCipher(boolean encrypt = true, int keySize = 256, byte[] iv = [0x00] * IV_LENGTH) {
-        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding")
-        String key = getKeyOfSize(keySize)
-        cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, new SecretKeySpec(Hex.decode(key), "AES"), new IvParameterSpec(iv))
-        logger.setup("Initialized a cipher in ${encrypt ? "encrypt" : "decrypt"} mode with a key of length ${keySize} bits")
-        cipher
-    }
-
-    private static String getKeyOfSize(int keySize = 256) {
-        switch (keySize) {
-            case 128:
-                return KEY_128_HEX
-            case 192:
-            case 256:
-                if (Cipher.getMaxAllowedKeyLength("AES") < keySize) {
-                    throw new IllegalArgumentException("The JCE unlimited strength cryptographic jurisdiction policies are not installed, so the max key size is 128 bits")
-                }
-                return KEY_256_HEX[0..<keySize.intdiv(4)]
-            default:
-                throw new IllegalArgumentException("Key size ${keySize} bits is not valid")
-        }
-    }
-
-    private static List<Integer> getAvailableKeySizes() {
-        if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
-            [128, 192, 256]
-        } else {
-            [128]
-        }
-    }
-
-    private static String manipulateString(String input, int start = 0, int end = input?.length()) {
-        if ((input[start..end] as List).unique().size() == 1) {
-            throw new IllegalArgumentException("Can't manipulate a String where the entire range is identical [${input[start..end]}]")
-        }
-        List shuffled = input[start..end] as List
-        Collections.shuffle(shuffled)
-        String reconstituted = input[0..<start] + shuffled.join() + input[end + 1..-1]
-        return reconstituted != input ? reconstituted : manipulateString(input, start, end)
-    }
-
-    @Test
-    void testShouldThrowExceptionOnInitializationWithoutBouncyCastle() throws Exception {
-        // Arrange
-        try {
-            Security.removeProvider(new BouncyCastleProvider().getName())
-
-            // Act
-            def msg = shouldFail(SensitivePropertyProtectionException) {
-                SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(KEY_128_HEX))
-                logger.error("This should not be reached")
-            }
-
-            // Assert
-            assert msg =~ "Error initializing the protection cipher"
-        } finally {
-            Security.addProvider(new BouncyCastleProvider())
-        }
-    }
-
-    // TODO: testShouldGetName()
-
-    @Test
-    void testShouldProtectValue() throws Exception {
-        final String PLAINTEXT = "This is a plaintext value"
-
-        // Act
-        Map<Integer, String> CIPHER_TEXTS = KEY_SIZES.collectEntries { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            [(keySize): spp.protect(PLAINTEXT, PROPERTY_CONTEXT)]
-        }
-        CIPHER_TEXTS.each { ks, ct -> logger.info("Encrypted for ${ks} length key: ${ct}") }
-
-        // Assert
-
-        // The IV generation is part of #protect, so the expected cipher text values must be generated after #protect has run
-        Map<Integer, Cipher> decryptionCiphers = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
-            // The 12 byte IV is the first 16 Base64-encoded characters of the "complete" cipher text
-            byte[] iv = decoder.decode(cipherText[0..<16])
-            [(keySize): getCipher(false, keySize, iv)]
-        }
-        Map<Integer, String> plaintexts = decryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
-            String cipherTextWithoutIVAndDelimiter = CIPHER_TEXTS[e.key][18..-1]
-            String plaintext = new String(e.value.doFinal(decoder.decode(cipherTextWithoutIVAndDelimiter)), StandardCharsets.UTF_8)
-            [(e.key): plaintext]
-        }
-        CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
-        assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
-    }
-
-    @Test
-    void testShouldHandleProtectEmptyValue() throws Exception {
-        final List<String> EMPTY_PLAINTEXTS = ["", "    ", null]
-
-        // Act
-        KEY_SIZES.collectEntries { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            EMPTY_PLAINTEXTS.each { String emptyPlaintext ->
-                def msg = shouldFail(IllegalArgumentException) {
-                    spp.protect(emptyPlaintext, PROPERTY_CONTEXT)
-                }
-                logger.expected("${msg} for keySize ${keySize} and plaintext [${emptyPlaintext}]")
-
-                // Assert
-                assert msg == "Cannot encrypt an empty value"
-            }
-        }
-    }
-
-    @Test
-    void testShouldUnprotectValue() throws Exception {
-        // Arrange
-        final String PLAINTEXT = "This is a plaintext value"
-
-        Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
-            byte[] iv = new byte[IV_LENGTH]
-            secureRandom.nextBytes(iv)
-            [(keySize): getCipher(true, keySize, iv)]
-        }
-
-        Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
-            String iv = encoder.encodeToString(e.value.getIV())
-            String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
-            [(e.key): "${iv}||${cipherText}"]
-        }
-        CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
-        // Act
-        Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            [(keySize): spp.unprotect(cipherText, PROPERTY_CONTEXT)]
-        }
-        plaintexts.each { ks, pt -> logger.info("Decrypted for ${ks} length key: ${pt}") }
-
-        // Assert
-        assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
-    }
-
-    /**
-     * Tests inputs where the entire String is empty/blank space/{@code null}.
-     *
-     * @throws Exception
-     */
-    @Test
-    void testShouldHandleUnprotectEmptyValue() throws Exception {
-        // Arrange
-        final List<String> EMPTY_CIPHER_TEXTS = ["", "    ", null]
-
-        // Act
-        KEY_SIZES.each { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
-                def msg = shouldFail(IllegalArgumentException) {
-                    spp.unprotect(emptyCipherText, PROPERTY_CONTEXT)
-                }
-                logger.expected("${msg} for keySize ${keySize} and cipher text [${emptyCipherText}]")
-
-                // Assert
-                assert msg == "Cannot decrypt a cipher text shorter than ${AESSensitivePropertyProvider.minCipherTextLength} chars".toString()
-            }
-        }
-    }
-
-    @Test
-    void testShouldUnprotectValueWithWhitespace() throws Exception {
-        // Arrange
-        final String PLAINTEXT = "This is a plaintext value"
-
-        Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
-            byte[] iv = new byte[IV_LENGTH]
-            secureRandom.nextBytes(iv)
-            [(keySize): getCipher(true, keySize, iv)]
-        }
-
-        Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
-            String iv = encoder.encodeToString(e.value.getIV())
-            String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
-            [(e.key): "${iv}||${cipherText}"]
-        }
-        CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
-        // Act
-        Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            [(keySize): spp.unprotect("\t" + cipherText + "\n", PROPERTY_CONTEXT)]
-        }
-        plaintexts.each { ks, pt -> logger.info("Decrypted for ${ks} length key: ${pt}") }
-
-        // Assert
-        assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
-    }
-
-    @Test
-    void testShouldHandleUnprotectMalformedValue() throws Exception {
-        // Arrange
-        final String PLAINTEXT = "This is a plaintext value"
-
-        // Act
-        KEY_SIZES.each { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
-            // Swap two characters in the cipher text
-            final String MALFORMED_CIPHER_TEXT = manipulateString(cipherText, 25, 28)
-            logger.info("Manipulated ${cipherText} to\n${MALFORMED_CIPHER_TEXT.padLeft(163)}")
-
-            def msg = shouldFail(SensitivePropertyProtectionException) {
-                spp.unprotect(MALFORMED_CIPHER_TEXT, PROPERTY_CONTEXT)
-            }
-            logger.expected("${msg} for keySize ${keySize} and cipher text [${MALFORMED_CIPHER_TEXT}]")
-
-            // Assert
-            assert msg == "Error decrypting a protected value"
-        }
-    }
-
-    @Test
-    void testShouldHandleUnprotectMissingIV() throws Exception {
-        // Arrange
-        final String PLAINTEXT = "This is a plaintext value"
-
-        // Act
-        KEY_SIZES.each { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
-
-            // Remove the IV from the "complete" cipher text
-            final String MISSING_IV_CIPHER_TEXT = cipherText[18..-1]
-            logger.info("Manipulated ${cipherText} to\n${MISSING_IV_CIPHER_TEXT.padLeft(172)}")
-
-            def msg = shouldFail(IllegalArgumentException) {
-                spp.unprotect(MISSING_IV_CIPHER_TEXT, PROPERTY_CONTEXT)
-            }
-            logger.expected("${msg} for keySize ${keySize} and cipher text [${MISSING_IV_CIPHER_TEXT}]")
-
-            // Remove the IV from the "complete" cipher text but keep the delimiter
-            final String MISSING_IV_CIPHER_TEXT_WITH_DELIMITER = cipherText[16..-1]
-            logger.info("Manipulated ${cipherText} to\n${MISSING_IV_CIPHER_TEXT_WITH_DELIMITER.padLeft(172)}")
-
-            def msgWithDelimiter = shouldFail(IllegalArgumentException) {
-                spp.unprotect(MISSING_IV_CIPHER_TEXT_WITH_DELIMITER, PROPERTY_CONTEXT)
-            }
-            logger.expected("${msgWithDelimiter} for keySize ${keySize} and cipher text [${MISSING_IV_CIPHER_TEXT_WITH_DELIMITER}]")
-
-            // Assert
-            assert msg == "The cipher text does not contain the delimiter || -- it should be of the form Base64(IV) || Base64(cipherText)"
-
-            // Assert
-            assert msgWithDelimiter == "The IV (0 bytes) must be at least 12 bytes"
-        }
-    }
-
-    /**
-     * Tests inputs which have a valid IV and delimiter but no "cipher text".
-     *
-     * @throws Exception
-     */
-    @Test
-    void testShouldHandleUnprotectEmptyCipherText() throws Exception {
-        // Arrange
-        final String IV_AND_DELIMITER = "${encoder.encodeToString("Bad IV value".getBytes(StandardCharsets.UTF_8))}||"
-        logger.info("IV and delimiter: ${IV_AND_DELIMITER}")
-
-        final List<String> EMPTY_CIPHER_TEXTS = ["", "      ", "\n"].collect { "${IV_AND_DELIMITER}${it}" }
-
-        // Act
-        KEY_SIZES.each { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
-                def msg = shouldFail(IllegalArgumentException) {
-                    spp.unprotect(emptyCipherText, PROPERTY_CONTEXT)
-                }
-                logger.expected("${msg} for keySize ${keySize} and cipher text [${emptyCipherText}]")
-
-                // Assert
-                assert msg == "Cannot decrypt a cipher text shorter than ${AESSensitivePropertyProvider.minCipherTextLength} chars".toString()
-            }
-        }
-    }
-
-    @Test
-    void testShouldHandleUnprotectMalformedIV() throws Exception {
-        // Arrange
-        final String PLAINTEXT = "This is a plaintext value"
-
-        // Act
-        KEY_SIZES.each { int keySize ->
-            SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
-            logger.info("Initialized ${spp.name} with key size ${keySize}")
-            String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
-            // Swap two characters in the IV
-            final String MALFORMED_IV_CIPHER_TEXT = manipulateString(cipherText, 8, 11)
-            logger.info("Manipulated ${cipherText} to\n${MALFORMED_IV_CIPHER_TEXT.padLeft(163)}")
-
-            def msg = shouldFail(SensitivePropertyProtectionException) {
-                spp.unprotect(MALFORMED_IV_CIPHER_TEXT, PROPERTY_CONTEXT)
-            }
-            logger.expected("${msg} for keySize ${keySize} and cipher text [${MALFORMED_IV_CIPHER_TEXT}]")
-
-            // Assert
-            assert msg == "Error decrypting a protected value"
-        }
-    }
-
-    @Test
-    void testShouldGetIdentifierKeyWithDifferentMaxKeyLengths() throws Exception {
-        // Arrange
-        def keys = getAvailableKeySizes().collectEntries { int keySize ->
-            [(keySize): getKeyOfSize(keySize)]
-        }
-        logger.info("Keys: ${keys}")
-
-        // Act
-        keys.each { int size, String key ->
-            String identifierKey = new AESSensitivePropertyProvider(key).getIdentifierKey()
-            logger.info("Identifier key: ${identifierKey} for size ${size}")
-
-            // Assert
-            assert identifierKey =~ /aes\/gcm\/${size}/
-        }
-    }
-
-    @Test
-    void testShouldNotAllowEmptyKey() throws Exception {
-        // Arrange
-        final String INVALID_KEY = ""
-
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
-        }
-
-        // Assert
-        assert msg == "The key cannot be empty"
-    }
-
-    @Test
-    void testShouldNotAllowIncorrectlySizedKey() throws Exception {
-        // Arrange
-        final String INVALID_KEY = "Z" * 31
-
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
-        }
-
-        // Assert
-        assert msg == "The key must be a valid hexadecimal key"
-    }
-
-    @Test
-    void testShouldNotAllowInvalidKey() throws Exception {
-        // Arrange
-        final String INVALID_KEY = "Z" * 32
-
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
-        }
-
-        // Assert
-        assert msg == "The key must be a valid hexadecimal key"
-    }
-
-    /**
-     * This test is to ensure internal consistency and allow for encrypting value for various property files
-     */
-    @Test
-    void testShouldEncryptArbitraryValues() {
-        // Arrange
-        def values = ["thisIsABadPassword", "thisIsABadSensitiveKeyPassword", "thisIsABadKeystorePassword", "thisIsABadKeyPassword", "thisIsABadTruststorePassword", "This is an encrypted banner message"]
-
-        String key = "2C576A9585DB862F5ECBEE5B4FFFCCA1" //getKeyOfSize(128)
-        // key = "0" * 64
-
-        SensitivePropertyProvider spp = new AESSensitivePropertyProvider(key)
-
-        // Act
-        def encryptedValues = values.collect { String v ->
-            def encryptedValue = spp.protect(v, PROPERTY_CONTEXT)
-            logger.info("${v} -> ${encryptedValue}")
-            def (String iv, String cipherText) = encryptedValue.tokenize("||")
-            logger.info("Normal Base64 encoding would be ${encoder.encodeToString(decoder.decode(iv))}||${encoder.encodeToString(decoder.decode(cipherText))}")
-            encryptedValue
-        }
-
-        // Assert
-        assert values == encryptedValues.collect { spp.unprotect(it, PROPERTY_CONTEXT) }
-    }
-
-    /**
-     * This test is to ensure external compatibility in case someone encodes the encrypted value with Base64 and does not remove the padding
-     */
-    @Test
-    void testShouldDecryptPaddedValue() {
-        // Arrange
-        assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
-
-        final String EXPECTED_VALUE = getKeyOfSize(256) // "thisIsABadKeyPassword"
-        String cipherText = "aYDkDKys1ENr3gp+||sTBPpMlIvHcOLTGZlfWct8r9RY8BuDlDkoaYmGJ/9m9af9tZIVzcnDwvYQAaIKxRGF7vI2yrY7Xd6x9GTDnWGiGiRXlaP458BBMMgfzH2O8"
-        String unpaddedCipherText = cipherText.replaceAll("=", "")
-
-        String key = "AAAABBBBCCCCDDDDEEEEFFFF00001111" * 2 // getKeyOfSize(256)
-
-        SensitivePropertyProvider spp = new AESSensitivePropertyProvider(key)
-
-        // Act
-        String rawValue = spp.unprotect(cipherText, PROPERTY_CONTEXT)
-        logger.info("Decrypted ${cipherText} to ${rawValue}")
-        String rawUnpaddedValue = spp.unprotect(unpaddedCipherText, PROPERTY_CONTEXT)
-        logger.info("Decrypted ${unpaddedCipherText} to ${rawUnpaddedValue}")
-
-        // Assert
-        assert rawValue == EXPECTED_VALUE
-        assert rawUnpaddedValue == EXPECTED_VALUE
-    }
-}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index fb16c31..c073576 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -37,7 +37,14 @@
         <module>nifi-property-encryptor</module>
         <module>nifi-property-utils</module>
         <module>nifi-properties</module>
-        <module>nifi-sensitive-property-provider</module>
+        <module>nifi-property-protection-api</module>
+        <module>nifi-property-protection-aws</module>
+        <module>nifi-property-protection-azure</module>
+        <module>nifi-property-protection-cipher</module>
+        <module>nifi-property-protection-factory</module>
+        <module>nifi-property-protection-gcp</module>
+        <module>nifi-property-protection-hashicorp</module>
+        <module>nifi-property-protection-shared</module>
         <module>nifi-record</module>
         <module>nifi-record-path</module>
         <module>nifi-repository-encryption</module>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
index 56a7175..f7690cb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
@@ -48,7 +48,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-sensitive-property-provider</artifactId>
+            <artifactId>nifi-property-protection-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-factory</artifactId>
         </dependency>
     </dependencies>
     <build>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
index 7547e71..77db7fb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
@@ -170,13 +170,13 @@ public class NiFiPropertiesLoader {
         if (protectedNiFiProperties.hasProtectedKeys()) {
             Security.addProvider(new BouncyCastleProvider());
             getSensitivePropertyProviderFactory()
-                    .getSupportedSensitivePropertyProviders()
+                    .getSupportedProviders()
                     .forEach(protectedNiFiProperties::addSensitivePropertyProvider);
         }
         NiFiProperties props = protectedNiFiProperties.getUnprotectedProperties();
         if (protectedNiFiProperties.hasProtectedKeys()) {
             getSensitivePropertyProviderFactory()
-                    .getSupportedSensitivePropertyProviders()
+                    .getSupportedProviders()
                     .forEach(SensitivePropertyProvider::cleanUp);
         }
         return props;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
index cf565b6..09b1f92 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.properties;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -186,11 +185,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
     }
 
     @Override
-    public Set<String> getProtectionSchemes() {
-        return propertyProtectionDelegate.getProtectionSchemes();
-    }
-
-    @Override
     public boolean isPropertySensitive(final String key) {
         return propertyProtectionDelegate.isPropertySensitive(key);
     }
@@ -210,11 +204,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
         propertyProtectionDelegate.addSensitivePropertyProvider(sensitivePropertyProvider);
     }
 
-    @Override
-    public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
-        return propertyProtectionDelegate.getSensitivePropertyProviders();
-    }
-
     /**
      * Returns the number of properties that are marked as protected in the provided {@link NiFiProperties} instance without requiring external creation of a {@link ProtectedNiFiProperties} instance.
      *
@@ -237,14 +226,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
 
     @Override
     public String toString() {
-        final Set<String> providers = getSensitivePropertyProviders().keySet();
-        return new StringBuilder("ProtectedNiFiProperties instance with ")
-                .append(size()).append(" properties (")
-                .append(getProtectedPropertyKeys().size())
-                .append(" protected) and ")
-                .append(providers.size())
-                .append(" sensitive property providers: ")
-                .append(StringUtils.join(providers, ", "))
-                .toString();
+        return String.format("%s Size [%d]", getClass().getSimpleName(), size());
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
deleted file mode 100644
index f6faea5..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.properties
-
-import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiPropertiesGroovyTest.class)
-
-    final def DEFAULT_SENSITIVE_PROPERTIES = [
-            "nifi.sensitive.props.key",
-            "nifi.security.keystorePasswd",
-            "nifi.security.keyPasswd",
-            "nifi.security.truststorePasswd",
-            "nifi.provenance.repository.encryption.key",
-            "nifi.provenance.repository.encryption.key.provider.password",
-            "nifi.flowfile.repository.encryption.key.provider.password",
-            "nifi.content.repository.encryption.key.provider.password",
-            "nifi.repository.encryption.key.provider.keystore.password"
-    ]
-
-    final def COMMON_ADDITIONAL_SENSITIVE_PROPERTIES = [
-            "nifi.sensitive.props.algorithm",
-            "nifi.kerberos.service.principal",
-            "nifi.kerberos.krb5.file",
-            "nifi.kerberos.keytab.location"
-    ]
-
-    private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" * 2
-
-    private static String originalPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
-
-    private static SensitivePropertyProviderFactory sensitivePropertyProviderFactory =
-            StandardSensitivePropertyProviderFactory.withKey(KEY_HEX)
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS)
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-    }
-
-    @Before
-    void setUp() throws Exception {
-    }
-
-    @After
-    void tearDown() throws Exception {
-    }
-
-    @AfterClass
-    static void tearDownOnce() {
-        if (originalPropertiesPath) {
-            System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, originalPropertiesPath)
-        }
-    }
-
-    private static ProtectedNiFiProperties loadFromFile(String propertiesFilePath) {
-        String filePath
-        try {
-            filePath = ProtectedNiFiPropertiesGroovyTest.class.getResource(propertiesFilePath).toURI().getPath()
-        } catch (URISyntaxException ex) {
-            throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex)
-        }
-
-        File file = new File(filePath)
-
-        if (file == null || !file.exists() || !file.canRead()) {
-            String path = (file == null ? "missing file" : file.getAbsolutePath())
-            logger.error("Cannot read from '{}' -- file is missing or not readable", path)
-            throw new IllegalArgumentException("NiFi properties file missing or unreadable")
-        }
-
-        Properties rawProperties = new Properties()
-
-        InputStream inStream = null
-        try {
-            inStream = new BufferedInputStream(new FileInputStream(file))
-            rawProperties.load(inStream)
-            logger.info("Loaded {} properties from {}", rawProperties.size(), file.getAbsolutePath())
-
-            ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
-
-            // If it has protected keys, inject the SPP
-            if (protectedNiFiProperties.hasProtectedKeys()) {
-                protectedNiFiProperties.addSensitivePropertyProvider(sensitivePropertyProviderFactory
-                        .getProvider(PropertyProtectionScheme.AES_GCM))
-            }
-
-            return protectedNiFiProperties
-        } catch (final Exception ex) {
-            logger.error("Cannot load properties file due to " + ex.getLocalizedMessage())
-            throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex)
-        } finally {
-            if (null != inStream) {
-                try {
-                    inStream.close()
-                } catch (final Exception ex) {
-                    /**
-                     * do nothing *
-                     */
-                }
-            }
-        }
-    }
-
-    @Test
-    void testConstructorShouldCreateNewInstance() throws Exception {
-        // Arrange
-
-        // Act
-        NiFiProperties niFiProperties = new NiFiProperties()
-        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-
-        // Assert
-        assert niFiProperties.size() == 0
-        assert niFiProperties.getPropertyKeys() == [] as Set
-    }
-
-    @Test
-    void testConstructorShouldAcceptRawProperties() throws Exception {
-        // Arrange
-        Properties rawProperties = new Properties()
-        rawProperties.setProperty("key", "value")
-        logger.info("rawProperties has ${rawProperties.size()} properties: ${rawProperties.stringPropertyNames()}")
-        assert rawProperties.size() == 1
-
-        // Act
-        NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
-        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-
-        // Assert
-        assert niFiProperties.size() == 1
-        assert niFiProperties.getPropertyKeys() == ["key"] as Set
-    }
-
-    @Test
-    void testConstructorShouldAcceptNiFiProperties() throws Exception {
-        // Arrange
-        Properties rawProperties = new Properties()
-        rawProperties.setProperty("key", "value")
-        rawProperties.setProperty("key.protected", "value2")
-        NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
-        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-        assert niFiProperties.size() == 2
-
-        // Act
-        ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(niFiProperties)
-        logger.info("protectedNiFiProperties has ${protectedNiFiProperties.size()} properties: ${protectedNiFiProperties.getPropertyKeys()}")
-
-        // Assert
-        def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
-        assert allKeys == ["key", "key.protected"] as Set
-        assert allKeys.size() == niFiProperties.size()
-
-    }
-
-    @Test
-    void testShouldAllowMultipleInstances() throws Exception {
-        // Arrange
-        Properties rawProperties = new Properties()
-        rawProperties.setProperty("key", "value")
-        logger.info("rawProperties has ${rawProperties.size()} properties: ${rawProperties.stringPropertyNames()}")
-        assert rawProperties.size() == 1
-
-        // Act
-        NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
-        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-        NiFiProperties emptyProperties = new NiFiProperties()
-        logger.info("emptyProperties has ${emptyProperties.size()} properties: ${emptyProperties.getPropertyKeys()}")
-
-        // Assert
-        assert niFiProperties.size() == 1
-        assert niFiProperties.getPropertyKeys() == ["key"] as Set
-
-        assert emptyProperties.size() == 0
-        assert emptyProperties.getPropertyKeys() == [] as Set
-    }
-
-    @Test
-    void testShouldDetectIfPropertyIsSensitive() throws Exception {
-        // Arrange
-        final String INSENSITIVE_PROPERTY_KEY = "nifi.ui.banner.text"
-        final String SENSITIVE_PROPERTY_KEY = "nifi.security.keystorePasswd"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
-        // Act
-        boolean bannerIsSensitive = properties.isPropertySensitive(INSENSITIVE_PROPERTY_KEY)
-        logger.info("${INSENSITIVE_PROPERTY_KEY} is ${bannerIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        boolean passwordIsSensitive = properties.isPropertySensitive(SENSITIVE_PROPERTY_KEY)
-        logger.info("${SENSITIVE_PROPERTY_KEY} is ${passwordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-
-        // Assert
-        assert !bannerIsSensitive
-        assert passwordIsSensitive
-    }
-
-    @Test
-    void testShouldGetDefaultSensitiveProperties() throws Exception {
-        // Arrange
-        logger.expected("${DEFAULT_SENSITIVE_PROPERTIES.size()} default sensitive properties: ${DEFAULT_SENSITIVE_PROPERTIES.join(", ")}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
-        // Act
-        List defaultSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${defaultSensitiveProperties.size()} default sensitive properties: ${defaultSensitiveProperties.join(", ")}")
-
-        // Assert
-        assert defaultSensitiveProperties.size() == DEFAULT_SENSITIVE_PROPERTIES.size()
-        assert defaultSensitiveProperties.containsAll(DEFAULT_SENSITIVE_PROPERTIES)
-    }
-
-    @Test
-    void testShouldGetAdditionalSensitiveProperties() throws Exception {
-        // Arrange
-        def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.ui.banner.text", "nifi.version"]
-        logger.expected("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_additional_sensitive_keys.properties")
-
-        // Act
-        List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
-
-        // Assert
-        assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
-        assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
-    }
-
-    // TODO: Add negative tests (fuzz additional.keys property, etc.)
-
-    @Test
-    void testGetAdditionalSensitivePropertiesShouldNotIncludeSelf() throws Exception {
-        // Arrange
-        def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.ui.banner.text", "nifi.version"]
-        logger.expected("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_additional_sensitive_keys.properties")
-
-        // Act
-        List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
-
-        // Assert
-        assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
-        assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
-    }
-
-    /**
-     * In the default (no protection enabled) scenario, a call to retrieve a sensitive property should return the raw value transparently.
-     * @throws Exception
-     */
-    @Test
-    void testShouldGetUnprotectedValueOfSensitiveProperty() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-        final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        String retrievedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
-        // Assert
-        assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
-        assert isSensitive
-        assert !isProtected
-    }
-
-    /**
-     * In the default (no protection enabled) scenario, a call to retrieve a sensitive property (which is empty) should return the raw value transparently.
-     * @throws Exception
-     */
-    @Test
-    void testShouldGetEmptyUnprotectedValueOfSensitiveProperty() throws Exception {
-        // Arrange
-        final String TRUSTSTORE_PASSWORD_KEY = "nifi.security.truststorePasswd"
-        final String EXPECTED_TRUSTSTORE_PASSWORD = ""
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-        String retrievedTruststorePassword = unprotectedProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
-
-        // Assert
-        assert retrievedTruststorePassword == EXPECTED_TRUSTSTORE_PASSWORD
-        assert isSensitive
-        assert !isProtected
-    }
-
-    /**
-     * The new model no longer needs to maintain the protected state -- it is used as a wrapper/decorator during load to unprotect the sensitive properties and then return an instance of raw properties.
-     *
-     * @throws Exception
-     */
-    @Test
-    void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtected() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-        final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-        String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
-        // Assert
-        assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
-        assert isSensitive
-        assert isProtected
-    }
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is protected with an unknown protection scheme.
-     * @throws Exception
-     */
-    @Test
-    void testGetValueOfSensitivePropertyShouldFailOnUnknownProtectionScheme() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
-        // Raw properties
-        Properties rawProperties = new Properties()
-        rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_unknown.properties").newInputStream())
-        final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_unknown.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-
-        // While the value is "protected", the scheme is not registered, so throw an exception
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        def msg = shouldFail(IllegalStateException) {
-            NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-        }
-
-        // Assert
-        assert msg == "No provider available for nifi.sensitive.props.key"
-        assert isSensitive
-        assert isProtected
-    }
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
-     * @throws Exception
-     */
-    @Test
-    void testGetValueOfSensitivePropertyShouldHandleSingleMalformedValue() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
-        // Raw properties
-        Properties rawProperties = new Properties()
-        rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_single_malformed.properties").newInputStream())
-        final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_single_malformed.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
-        assert isSensitive
-        assert isProtected
-    }
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
-     * @throws Exception
-     */
-    @Test
-    void testGetValueOfSensitivePropertyShouldHandleMultipleMalformedValues() throws Exception {
-        // Arrange
-
-        // Raw properties
-        Properties rawProperties = new Properties()
-        rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_multiple_malformed.properties").newInputStream())
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_multiple_malformed.properties")
-
-        // Iterate over the protected keys and track the ones that fail to decrypt
-        SensitivePropertyProvider spp = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.AES_GCM)
-        Set<String> malformedKeys = properties.getProtectedPropertyKeys()
-                .findAll { String key, String scheme -> scheme == spp.identifierKey }
-                .keySet().collect { String key ->
-            try {
-                String rawValue = spp.unprotect(properties.getProperty(key), ProtectedPropertyContext.defaultContext(key))
-                return
-            } catch (SensitivePropertyProtectionException e) {
-                logger.expected("Caught a malformed value for ${key}")
-                return key
-            }
-        }
-
-        logger.expected("Malformed keys: ${malformedKeys.join(", ")}")
-
-        // Act
-        def e = groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
-            NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-        }
-        logger.expected(e.getMessage())
-
-        // Assert
-        assert e instanceof MultipleSensitivePropertyProtectionException
-        assert e.getMessage() =~ "Failed to unprotect keys"
-        assert e.getFailedKeys() == malformedKeys
-
-    }
-
-    /**
-     * In the default (no protection enabled) scenario, a call to retrieve a sensitive property (which is empty) should return the raw value transparently.
-     * @throws Exception
-     */
-    @Test
-    void testShouldGetEmptyUnprotectedValueOfSensitivePropertyWithDefault() throws Exception {
-        // Arrange
-        final String TRUSTSTORE_PASSWORD_KEY = "nifi.security.truststorePasswd"
-        final String EXPECTED_TRUSTSTORE_PASSWORD = ""
-        final String DEFAULT_VALUE = "defaultValue"
-
-        // Raw properties
-        Properties rawProperties = new Properties()
-        rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_unprotected.properties").newInputStream())
-        final String RAW_TRUSTSTORE_PASSWORD = rawProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("Raw value for ${TRUSTSTORE_PASSWORD_KEY}: ${RAW_TRUSTSTORE_PASSWORD}")
-        assert RAW_TRUSTSTORE_PASSWORD == EXPECTED_TRUSTSTORE_PASSWORD
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        String retrievedTruststorePassword = properties.getProperty(TRUSTSTORE_PASSWORD_KEY, DEFAULT_VALUE)
-        logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
-
-        // Assert
-        assert retrievedTruststorePassword == DEFAULT_VALUE
-        assert isSensitive
-        assert !isProtected
-    }
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should return the raw value transparently.
-     * @throws Exception
-     */
-    @Test
-    void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtectedWithDefault() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-        final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-        final String DEFAULT_VALUE = "defaultValue"
-
-        // Raw properties
-        Properties rawProperties = new Properties()
-        rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes.properties").newInputStream())
-        final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-        String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY, DEFAULT_VALUE)
-        logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
-        // Assert
-        assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
-        assert isSensitive
-        assert isProtected
-    }
-
-    // TODO: Test getProtected with multiple providers
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should fail if the internal cache of providers is empty.
-     * @throws Exception
-     */
-    @Test
-    void testGetValueOfSensitivePropertyShouldFailOnInvalidatedInternalCache() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-        final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
-        final String RAW_PASSWORD = properties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Read raw value from properties: ${RAW_PASSWORD}")
-
-        // Overwrite the internal cache
-        properties.getSensitivePropertyProviders().clear()
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        def msg = shouldFail(IllegalStateException) {
-            NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-        }
-
-        // Assert
-        assert msg == "No provider available for nifi.sensitive.props.key"
-        assert isSensitive
-        assert isProtected
-    }
-
-    @Test
-    void testShouldDetectIfPropertyIsProtected() throws Exception {
-        // Arrange
-        final String UNPROTECTED_PROPERTY_KEY = "nifi.security.truststorePasswd"
-        final String PROTECTED_PROPERTY_KEY = "nifi.security.keystorePasswd"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
-        // Act
-        boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(UNPROTECTED_PROPERTY_KEY)
-        boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(UNPROTECTED_PROPERTY_KEY)
-        logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
-        boolean protectedPasswordIsSensitive = properties.isPropertySensitive(PROTECTED_PROPERTY_KEY)
-        boolean protectedPasswordIsProtected = properties.isPropertyProtected(PROTECTED_PROPERTY_KEY)
-        logger.info("${PROTECTED_PROPERTY_KEY} is ${protectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${PROTECTED_PROPERTY_KEY} is ${protectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
-
-        // Assert
-        assert unprotectedPasswordIsSensitive
-        assert !unprotectedPasswordIsProtected
-
-        assert protectedPasswordIsSensitive
-        assert protectedPasswordIsProtected
-    }
-
-    @Test
-    void testShouldDetectIfPropertyWithEmptyProtectionSchemeIsProtected() throws Exception {
-        // Arrange
-        final String UNPROTECTED_PROPERTY_KEY = "nifi.sensitive.props.key"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected_extra_line.properties")
-
-        // Act
-        boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(UNPROTECTED_PROPERTY_KEY)
-        boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(UNPROTECTED_PROPERTY_KEY)
-        logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
-
-        // Assert
-        assert unprotectedPasswordIsSensitive
-        assert !unprotectedPasswordIsProtected
-    }
-
-    private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiProperties properties) {
-        return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
-    }
-
-    @Test
-    void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
-        double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
-        // Assert
-        assert percentProtected == 0.0
-    }
-
-    @Test
-    void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
-        double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
-        // Assert
-        assert percentProtected == 75.0
-    }
-
-    @Test
-    void testShouldGetPercentageOfSensitivePropertiesProtected_100() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_all_sensitive_properties_protected_aes.properties")
-
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
-        double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
-        // Assert
-        assert percentProtected == 100.0
-    }
-
-    @Test
-    void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
-        assert !properties.hasProtectedKeys()
-
-        // Act
-        Map localCache = properties.getSensitivePropertyProviders()
-        logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
-
-        // Assert
-        assert localCache.isEmpty()
-    }
-
-    @Test
-    void testShouldAddSensitivePropertyProvider() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        SensitivePropertyProvider mockProvider =
-                [unprotect       : { String input ->
-                    logger.mock("Mock call to #unprotect(${input})")
-                    input.reverse()
-                },
-                 getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
-
-        // Act
-        properties.addSensitivePropertyProvider(mockProvider)
-
-        // Assert
-        assert properties.getSensitivePropertyProviders().size() == 1
-    }
-
-    @Test
-    void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        // Act
-        def msg = shouldFail(NullPointerException) {
-            properties.addSensitivePropertyProvider(null)
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert properties.getSensitivePropertyProviders().size() == 0
-        assert msg == "Cannot add null SensitivePropertyProvider"
-    }
-
-    @Test
-    void testShouldNotAllowOverwriteOfProvider() throws Exception {
-        // Arrange
-        ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        SensitivePropertyProvider mockProvider =
-                [unprotect       : { String input ->
-                    logger.mock("Mock call to 1#unprotect(${input})")
-                    input.reverse()
-                },
-                 getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
-        properties.addSensitivePropertyProvider(mockProvider)
-        assert properties.getSensitivePropertyProviders().size() == 1
-
-        SensitivePropertyProvider mockProvider2 =
-                [unprotect       : { String input ->
-                    logger.mock("Mock call to 2#unprotect(${input})")
-                    input.reverse()
-                },
-                 getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
-
-        // Act
-        def msg = shouldFail(UnsupportedOperationException) {
-            properties.addSensitivePropertyProvider(mockProvider2)
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg == "Cannot overwrite existing sensitive property provider registered for mockProvider"
-        assert properties.getSensitivePropertyProviders().size() == 1
-    }
-
-    @Test
-    void testGetUnprotectedPropertiesShouldReturnInternalInstanceWhenNoneProtected() {
-        // Arrange
-        String noProtectedPropertiesPath = "/conf/nifi.properties"
-        ProtectedNiFiProperties protectedNiFiProperties = loadFromFile(noProtectedPropertiesPath)
-        logger.info("Loaded ${protectedNiFiProperties.size()} properties from ${noProtectedPropertiesPath}")
-
-        int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
-        logger.info("Hash code of internal instance: ${hashCode}")
-
-        // Act
-        NiFiProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
-        logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
-
-        // Assert
-        assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
-        assert unprotectedNiFiProperties.getPropertyKeys().every {
-            !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
-        }
-        logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
-        assert unprotectedNiFiProperties.hashCode() == hashCode
-    }
-
-    @Test
-    void testGetUnprotectedPropertiesShouldDecryptProtectedProperties() {
-        // Arrange
-        String noProtectedPropertiesPath = "/conf/nifi_with_sensitive_properties_protected_aes.properties"
-        ProtectedNiFiProperties protectedNiFiProperties = loadFromFile(noProtectedPropertiesPath)
-        logger.info("Loaded ${protectedNiFiProperties.size()} properties from ${noProtectedPropertiesPath}")
-
-        int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
-        int protectionSchemeCount = protectedNiFiProperties
-                .getPropertyKeys().findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
-                .size()
-        int expectedUnprotectedPropertyCount = protectedNiFiProperties.size() - protectionSchemeCount
-
-        String protectedProps = protectedNiFiProperties
-                .getProtectedPropertyKeys()
-                .collectEntries {
-            [(it.key): protectedNiFiProperties.getProperty(it.key)]
-        }.entrySet()
-                .join("\n")
-
-        logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
-        logger.info("Protected properties: \n${protectedProps}")
-
-        logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
-        int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
-        logger.info("Hash code of internal instance: ${hashCode}")
-
-        // Act
-        NiFiProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
-        logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
-
-        // Assert
-        assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
-        assert unprotectedNiFiProperties.getPropertyKeys().every {
-            !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
-        }
-        logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
-        assert unprotectedNiFiProperties.hashCode() != hashCode
-    }
-
-    @Test
-    void testShouldCalculateSize() {
-        // Arrange
-        Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
-        ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
-        // Act
-        int protectedSize = protectedNiFiProperties.size()
-        logger.info("Protected properties (${protectedNiFiProperties.size()}): ${protectedNiFiProperties.getPropertyKeys().join(", ")}")
-
-        // Assert
-        assert protectedSize == rawProperties.size() - 1
-    }
-
-    @Test
-    void testGetPropertyKeysShouldMatchSize() {
-        // Arrange
-        Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
-        ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
-        // Act
-        def filteredKeys = protectedNiFiProperties.getPropertyKeys()
-        logger.info("Protected properties (${protectedNiFiProperties.size()}): ${filteredKeys.join(", ")}")
-
-        // Assert
-        assert protectedNiFiProperties.size() == rawProperties.size() - 1
-        assert filteredKeys == rawProperties.keySet() - "key.protected"
-    }
-
-    @Test
-    void testShouldGetPropertyKeysIncludingProtectionSchemes() {
-        // Arrange
-        Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
-        ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
-        // Act
-        def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
-        logger.info("Protected properties with schemes (${allKeys.size()}): ${allKeys.join(", ")}")
-
-        // Assert
-        assert allKeys.size() == rawProperties.size()
-        assert allKeys == rawProperties.keySet()
-    }
-
-    @Test
-    void testShouldThrowExceptionWhenImproperValueGiven() throws Exception {
-        // Arrange
-        final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
-        ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_improper_delimiter_value.properties")
-
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
-        assert msg =~ "The cipher text does not contain the delimiter ||"
-    }
-
-    // TODO: Add tests for protectPlainProperties
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/pom.xml
index ea14106..c7a673d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/pom.xml
@@ -147,7 +147,12 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.nifi</groupId>
-                <artifactId>nifi-sensitive-property-provider</artifactId>
+                <artifactId>nifi-property-protection-api</artifactId>
+                <version>1.16.0-SNAPSHOT</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.nifi</groupId>
+                <artifactId>nifi-property-protection-factory</artifactId>
                 <version>1.16.0-SNAPSHOT</version>
             </dependency>
             <dependency>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
index 3854a34..73436c8 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
@@ -406,6 +406,11 @@
             <version>2.0.0-M24</version>
             <scope>test</scope>
 	    </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
index 6c78a32..3eddf9b 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
@@ -17,10 +17,10 @@
 package org.apache.nifi.registry.security.authentication;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.properties.PropertyProtectionScheme;
 import org.apache.nifi.properties.SensitivePropertyProtectionException;
 import org.apache.nifi.properties.SensitivePropertyProvider;
 import org.apache.nifi.properties.SensitivePropertyProviderFactory;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
 import org.apache.nifi.registry.extension.ExtensionManager;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.security.authentication.annotation.IdentityProviderContext;
@@ -28,8 +28,6 @@ import org.apache.nifi.registry.security.authentication.generated.IdentityProvid
 import org.apache.nifi.registry.security.authentication.generated.Property;
 import org.apache.nifi.registry.security.authentication.generated.Provider;
 import org.apache.nifi.registry.security.util.XmlUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
@@ -57,8 +55,6 @@ import java.util.Map;
 
 @Configuration
 public class IdentityProviderFactory implements IdentityProviderLookup, DisposableBean {
-
-    private static final Logger logger = LoggerFactory.getLogger(IdentityProviderFactory.class);
     private static final String LOGIN_IDENTITY_PROVIDERS_XSD = "/identity-providers.xsd";
     private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.security.authentication.generated";
     private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
@@ -71,9 +67,9 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
         }
     }
 
-    private NiFiRegistryProperties properties;
-    private ExtensionManager extensionManager;
-    private SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
+    private final NiFiRegistryProperties properties;
+    private final ExtensionManager extensionManager;
+    private final SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
     private IdentityProvider identityProvider;
     private final Map<String, IdentityProvider> identityProviders = new HashMap<>();
 
@@ -136,10 +132,8 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
     }
 
     @Override
-    public void destroy() throws Exception {
-        if (identityProviders != null) {
-            identityProviders.entrySet().stream().forEach(e -> e.getValue().preDestruction());
-        }
+    public void destroy() {
+        identityProviders.forEach((key, value) -> value.preDestruction());
     }
 
     private IdentityProviders loadLoginIdentityProvidersConfiguration() throws Exception {
@@ -279,7 +273,7 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
                     "detected and configured during the bootstrap startup sequence. Contact the system administrator.");
         }
         try {
-            final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(encryptionScheme));
+            final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(encryptionScheme));
             return sensitivePropertyProvider.unprotect(cipherText, sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName));
         } catch (final IllegalArgumentException e) {
             throw new SensitivePropertyProtectionException(String.format("Identity Provider configuration XML was protected using %s, which is not supported. " +
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
index 5704c4a..738f003 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
@@ -18,10 +18,10 @@ package org.apache.nifi.registry.security.authorization;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
-import org.apache.nifi.properties.PropertyProtectionScheme;
 import org.apache.nifi.properties.SensitivePropertyProtectionException;
 import org.apache.nifi.properties.SensitivePropertyProvider;
 import org.apache.nifi.properties.SensitivePropertyProviderFactory;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
 import org.apache.nifi.registry.extension.ExtensionClassLoader;
 import org.apache.nifi.registry.extension.ExtensionCloseable;
 import org.apache.nifi.registry.extension.ExtensionManager;
@@ -517,7 +517,7 @@ public class AuthorizerFactory implements UserGroupProviderLookup, AccessPolicyP
                     "detected and configured during the bootstrap startup sequence. Contact the system administrator.");
         }
         try {
-            final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(encryptionScheme));
+            final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(encryptionScheme));
             return sensitivePropertyProvider.unprotect(cipherText, sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName));
         } catch (final IllegalArgumentException e) {
             throw new SensitivePropertyProtectionException(String.format("Authorizer configuration XML was protected using %s, which is not supported. " +
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
index f1aacd6..52e9c9b 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
@@ -68,7 +68,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-sensitive-property-provider</artifactId>
+            <artifactId>nifi-property-protection-factory</artifactId>
             <version>1.16.0-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
index a05c0f7..c5f5a75 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
@@ -35,10 +35,6 @@ public class NiFiRegistryPropertiesLoader {
 
     private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryPropertiesLoader.class);
 
-    private static final String APPLICATION_PATH = "nifi.registry";
-
-    private static final String RELATIVE_PATH = "conf/nifi-registry.properties";
-
     private String keyHex;
 
     // Future enhancement: allow for external registration of new providers
@@ -111,8 +107,7 @@ public class NiFiRegistryPropertiesLoader {
             rawProperties.load(reader);
             final NiFiRegistryProperties innerProperties = new NiFiRegistryProperties(rawProperties);
             logger.info("Loaded {} properties from {}", rawProperties.size(), file.getAbsolutePath());
-            ProtectedNiFiRegistryProperties protectedNiFiRegistryProperties = new ProtectedNiFiRegistryProperties(innerProperties);
-            return protectedNiFiRegistryProperties;
+            return new ProtectedNiFiRegistryProperties(innerProperties);
         } catch (final IOException ioe) {
             logger.error("Cannot load properties file due to " + ioe.getLocalizedMessage());
             throw new RuntimeException("Cannot load properties file due to " + ioe.getLocalizedMessage(), ioe);
@@ -132,7 +127,7 @@ public class NiFiRegistryPropertiesLoader {
         if (protectedNiFiProperties.hasProtectedKeys()) {
             Security.addProvider(new BouncyCastleProvider());
             getSensitivePropertyProviderFactory()
-                    .getSupportedSensitivePropertyProviders()
+                    .getSupportedProviders()
                     .forEach(protectedNiFiProperties::addSensitivePropertyProvider);
         }
 
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
index 57e1af2..234e724 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.registry.properties;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.properties.ApplicationPropertiesProtector;
 import org.apache.nifi.properties.ProtectedProperties;
 import org.apache.nifi.properties.SensitivePropertyProtectionException;
@@ -175,11 +174,6 @@ class ProtectedNiFiRegistryProperties extends NiFiRegistryProperties implements
     }
 
     @Override
-    public Set<String> getProtectionSchemes() {
-        return propertyProtectionDelegate.getProtectionSchemes();
-    }
-
-    @Override
     public boolean isPropertySensitive(final String key) {
         return propertyProtectionDelegate.isPropertySensitive(key);
     }
@@ -200,20 +194,7 @@ class ProtectedNiFiRegistryProperties extends NiFiRegistryProperties implements
     }
 
     @Override
-    public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
-        return propertyProtectionDelegate.getSensitivePropertyProviders();
-    }
-
-    @Override
     public String toString() {
-        final Set<String> providers = getSensitivePropertyProviders().keySet();
-        return new StringBuilder("ProtectedNiFiRegistryProperties instance with ")
-                .append(size()).append(" properties (")
-                .append(getProtectedPropertyKeys().size())
-                .append(" protected) and ")
-                .append(providers.size())
-                .append(" sensitive property providers: ")
-                .append(StringUtils.join(providers, ", "))
-                .toString();
+        return String.format("%s Size [%d]", getClass().getSimpleName(), size());
     }
 }
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
index 1361a27..18869d5 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
@@ -17,31 +17,18 @@
 package org.apache.nifi.registry.properties
 
 import org.apache.nifi.properties.ApplicationPropertiesProtector
-import org.apache.nifi.properties.MultipleSensitivePropertyProtectionException
-import org.apache.nifi.properties.PropertyProtectionScheme
-import org.apache.nifi.properties.ProtectedPropertyContext
 import org.apache.nifi.properties.SensitivePropertyProtectionException
 import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
 import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
 
 import javax.crypto.Cipher
-import java.security.Security
 
 @RunWith(JUnit4.class)
 class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiRegistryPropertiesGroovyTest.class)
-
     private static final String KEYSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD
     private static final String KEY_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEY_PASSWD
     private static final String TRUSTSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD
@@ -56,27 +43,6 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
     private static final String KEY_HEX_256 = KEY_HEX_128 * 2
     private static final String KEY_HEX = Cipher.getMaxAllowedKeyLength("AES") < 256 ? KEY_HEX_128 : KEY_HEX_256
 
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-    }
-
-    @Before
-    void setUp() throws Exception {
-    }
-
-    @After
-    void tearDown() throws Exception {
-    }
-
-    @AfterClass
-    static void tearDownOnce() {
-    }
-
     private static ProtectedNiFiRegistryProperties loadFromResourceFile(String propertiesFilePath) {
         return loadFromResourceFile(propertiesFilePath, KEY_HEX)
     }
@@ -85,8 +51,6 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
         File file = fileForResource(propertiesFilePath)
 
         if (file == null || !file.exists() || !file.canRead()) {
-            String path = (file == null ? "missing file" : file.getAbsolutePath())
-            logger.error("Cannot read from '{}' -- file is missing or not readable", path)
             throw new IllegalArgumentException("NiFi Registry properties file missing or unreadable")
         }
 
@@ -96,19 +60,17 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
             final Properties props = new Properties()
             props.load(reader)
             NiFiRegistryProperties properties = new NiFiRegistryProperties(props)
-            logger.info("Loaded {} properties from {}", properties.size(), file.getAbsolutePath())
 
             ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(properties)
 
             // If it has protected keys, inject the SPP
             if (protectedNiFiProperties.hasProtectedKeys()) {
                 protectedNiFiProperties.addSensitivePropertyProvider(StandardSensitivePropertyProviderFactory.withKey(keyHex)
-                        .getProvider(PropertyProtectionScheme.AES_GCM))
+                        .getProvider(new StandardProtectionScheme("aes/gcm")))
             }
 
             return protectedNiFiProperties
         } catch (final Exception ex) {
-            logger.error("Cannot load properties file due to " + ex.getLocalizedMessage())
             throw new RuntimeException("Cannot load properties file due to " +
                     ex.getLocalizedMessage(), ex)
         }
@@ -131,69 +93,46 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
 
     @Test
     void testShouldDetectIfPropertyIsSensitive() throws Exception {
-        // Arrange
         final String INSENSITIVE_PROPERTY_KEY = "nifi.registry.web.http.port"
         final String SENSITIVE_PROPERTY_KEY = "nifi.registry.security.keystorePasswd"
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
 
-        // Act
         boolean bannerIsSensitive = properties.isPropertySensitive(INSENSITIVE_PROPERTY_KEY)
-        logger.info("${INSENSITIVE_PROPERTY_KEY} is ${bannerIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
         boolean passwordIsSensitive = properties.isPropertySensitive(SENSITIVE_PROPERTY_KEY)
-        logger.info("${SENSITIVE_PROPERTY_KEY} is ${passwordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
 
-        // Assert
         assert !bannerIsSensitive
         assert passwordIsSensitive
     }
 
     @Test
     void testShouldGetDefaultSensitiveProperties() throws Exception {
-        // Arrange
-        logger.info("${DEFAULT_SENSITIVE_PROPERTIES.size()} default sensitive properties: ${DEFAULT_SENSITIVE_PROPERTIES.join(", ")}")
-
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
 
-        // Act
         List defaultSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${defaultSensitiveProperties.size()} default sensitive properties: ${defaultSensitiveProperties.join(", ")}")
 
-        // Assert
         assert defaultSensitiveProperties.size() == DEFAULT_SENSITIVE_PROPERTIES.size()
         assert defaultSensitiveProperties.containsAll(DEFAULT_SENSITIVE_PROPERTIES)
     }
 
     @Test
     void testShouldGetAdditionalSensitiveProperties() throws Exception {
-        // Arrange
         def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
-        logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
-
-        // Act
         List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
 
-        // Assert
         assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
         assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
     }
 
     @Test
     void testGetAdditionalSensitivePropertiesShouldNotIncludeSelf() throws Exception {
-        // Arrange
         def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
-        logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
 
-        // Act
         List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
-        logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
 
-        // Assert
         assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
         assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
     }
@@ -204,20 +143,15 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testShouldGetUnprotectedValueOfSensitiveProperty() throws Exception {
-        // Arrange
         final String expectedKeystorePassword = "thisIsABadKeystorePassword"
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
 
         boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
         boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
 
-        // Act
         String retrievedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
 
-        // Assert
         assert retrievedKeystorePassword == expectedKeystorePassword
         assert isSensitive
         assert !isProtected
@@ -229,21 +163,16 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testShouldGetEmptyUnprotectedValueOfSensitiveProperty() throws Exception {
-        // Arrange
         final String expectedTruststorePassword = ""
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
 
         boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
         boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
 
-        // Act
         NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
         String retrievedTruststorePassword = unprotectedProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
-        logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
 
-        // Assert
         assert retrievedTruststorePassword == expectedTruststorePassword
         assert isSensitive
         assert !isProtected
@@ -256,21 +185,16 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtected() throws Exception {
-        // Arrange
         final String expectedKeystorePassword = "thisIsABadPassword"
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
 
         boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
         boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
 
-        // Act
         NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
         String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
 
-        // Assert
         assert retrievedKeystorePassword == expectedKeystorePassword
         assert isSensitive
         assert isProtected
@@ -282,33 +206,17 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testGetValueOfSensitivePropertyShouldHandleUnknownProtectionScheme() throws Exception {
-        // Arrange
-
-        // Raw properties
         Properties rawProperties = new Properties()
         rawProperties.load(new FileReader(fileForResource("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")))
-        final String expectedKeystorePassword = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${expectedKeystorePassword}")
 
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")
 
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
+        assert properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
+        assert properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
 
-        // While the value is "protected", the scheme is not registered
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        def msg = shouldFail(IllegalStateException) {
-            NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+        shouldFail(SensitivePropertyProtectionException) {
+            properties.getUnprotectedProperties()
         }
-
-        // Assert
-        assert msg == "No provider available for nifi.registry.security.keyPasswd"
-        assert isSensitive
-        assert isProtected
     }
 
     /**
@@ -317,22 +225,14 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testGetValueOfSensitivePropertyShouldHandleSingleMalformedValue() throws Exception {
-        // Arrange
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_single_malformed.properties", KEY_HEX_128)
         boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
         boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
 
-        // Act
-        def msg = shouldFail(SensitivePropertyProtectionException) {
-            NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+        shouldFail(SensitivePropertyProtectionException) {
+            properties.getUnprotectedProperties()
         }
-        logger.info(msg)
 
-        // Assert
-        assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
         assert isSensitive
         assert isProtected
     }
@@ -343,97 +243,27 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
      */
     @Test
     void testGetValueOfSensitivePropertyShouldHandleMultipleMalformedValues() throws Exception {
-        // Arrange
-
-        // Raw properties
         ProtectedNiFiRegistryProperties properties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_multiple_malformed.properties", KEY_HEX_128)
 
-        // Iterate over the protected keys and track the ones that fail to decrypt
-        SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128)
-                .getProvider(PropertyProtectionScheme.AES_GCM)
-        Set<String> malformedKeys = properties.getProtectedPropertyKeys()
-                .findAll { String key, String scheme -> scheme == spp.identifierKey }
-                .keySet()
-                .findAll { String key ->
-            try {
-                spp.unprotect(properties.getProperty(key), ProtectedPropertyContext.defaultContext(key))
-                return false
-            } catch (SensitivePropertyProtectionException e) {
-                logger.expected("Caught a malformed value for ${key}")
-                return true
-            }
-        }
-
-        logger.expected("Malformed keys: ${malformedKeys.join(", ")}")
-
-        // Act
-        def e = groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
-            NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-        }
-        logger.expected(e.getMessage())
-
-        // Assert
-        assert e instanceof MultipleSensitivePropertyProtectionException
-        assert e.getMessage() =~ "Failed to unprotect keys"
-        assert e.getFailedKeys() == malformedKeys
-
-    }
-
-    /**
-     * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the internal cache of providers is empty.
-     * @throws Exception
-     */
-    @Test
-    void testGetValueOfSensitivePropertyShouldHandleInvalidatedInternalCache() throws Exception {
-        // Arrange
-        ProtectedNiFiRegistryProperties properties =
-                loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
-        final String expectedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
-        logger.info("Read raw value from properties: ${expectedKeystorePassword}")
-
-        // Overwrite the internal cache
-        properties.getSensitivePropertyProviders().clear()
-
-        boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
-        boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-        logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
-        // Act
-        def msg = shouldFail(IllegalStateException) {
-            NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
-            String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
-            logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+        groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
+            properties.getUnprotectedProperties()
         }
-
-        // Assert
-        assert msg == "No provider available for nifi.registry.security.keyPasswd"
-        assert isSensitive
-        assert isProtected
     }
 
     @Test
     void testShouldDetectIfPropertyIsProtected() throws Exception {
-        // Arrange
         final String unprotectedPropertyKey = TRUSTSTORE_PASSWORD_KEY
         final String protectedPropertyKey = KEYSTORE_PASSWORD_KEY
 
         ProtectedNiFiRegistryProperties properties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
 
-        // Act
         boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
         boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
-        logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
         boolean protectedPasswordIsSensitive = properties.isPropertySensitive(protectedPropertyKey)
         boolean protectedPasswordIsProtected = properties.isPropertyProtected(protectedPropertyKey)
-        logger.info("${protectedPropertyKey} is ${protectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${protectedPropertyKey} is ${protectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
 
-        // Assert
         assert unprotectedPasswordIsSensitive
         assert !unprotectedPasswordIsProtected
 
@@ -443,315 +273,165 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
 
     @Test
     void testShouldDetectIfPropertyWithEmptyProtectionSchemeIsProtected() throws Exception {
-        // Arrange
         final String unprotectedPropertyKey = KEY_PASSWORD_KEY
         ProtectedNiFiRegistryProperties properties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected_extra_line.properties")
 
-        // Act
         boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
         boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
-        logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-        logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
 
-        // Assert
         assert unprotectedPasswordIsSensitive
         assert !unprotectedPasswordIsProtected
     }
 
     @Test
     void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception {
-        // Arrange
         ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
-
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
         double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
 
-        // Assert
-        assert percentProtected == 0.0
+        assert percentProtected == 0.0D
     }
 
     private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiRegistryProperties properties) {
-        return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
+        return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100)
     }
 
     @Test
     void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
-        // Arrange
         ProtectedNiFiRegistryProperties properties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
 
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
         double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
-        // Assert
-        assert percentProtected == 67.0
+        assert percentProtected == 67.0D
     }
 
     @Test
     void testShouldGetPercentageOfSensitivePropertiesProtected_100() throws Exception {
-        // Arrange
         ProtectedNiFiRegistryProperties properties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_fully_protected_aes_128.properties", KEY_HEX_128)
 
-        logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
-        logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
-        // Act
         double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
-        logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
-        // Assert
-        assert percentProtected == 100.0
-    }
-
-    @Test
-    void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
-        // Arrange
-        ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
-        assert !properties.hasProtectedKeys()
-
-        // Act
-        Map localCache = properties.getSensitivePropertyProviders()
-        logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
 
-        // Assert
-        assert localCache.isEmpty()
+        assert percentProtected == 100.0D
     }
 
     @Test
     void testShouldAddSensitivePropertyProvider() throws Exception {
-        // Arrange
         ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
 
         SensitivePropertyProvider mockProvider =
                 [unprotect       : { String input ->
-                    logger.mock("Mock call to #unprotect(${input})")
                     input.reverse()
                 },
                  getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
 
-        // Act
         properties.addSensitivePropertyProvider(mockProvider)
-
-        // Assert
-        assert properties.getSensitivePropertyProviders().size() == 1
-    }
-
-    @Test
-    void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
-        // Arrange
-        ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
-
-        // Act
-        def msg = shouldFail(NullPointerException) {
-            properties.addSensitivePropertyProvider(null)
-        }
-        logger.info(msg)
-
-        // Assert
-        assert properties.getSensitivePropertyProviders().size() == 0
-        assert msg == "Cannot add null SensitivePropertyProvider"
     }
 
     @Test
     void testShouldNotAllowOverwriteOfProvider() throws Exception {
         // Arrange
         ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
-        assert properties.getSensitivePropertyProviders().isEmpty()
 
         SensitivePropertyProvider mockProvider =
                 [unprotect       : { String input ->
-                    logger.mock("Mock call to 1#unprotect(${input})")
                     input.reverse()
                 },
                  getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
         properties.addSensitivePropertyProvider(mockProvider)
-        assert properties.getSensitivePropertyProviders().size() == 1
 
         SensitivePropertyProvider mockProvider2 =
                 [unprotect       : { String input ->
-                    logger.mock("Mock call to 2#unprotect(${input})")
                     input.reverse()
                 },
                  getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
 
-        // Act
-        def msg = shouldFail(UnsupportedOperationException) {
+        shouldFail(UnsupportedOperationException) {
             properties.addSensitivePropertyProvider(mockProvider2)
         }
-        logger.info(msg)
-
-        // Assert
-        assert msg == "Cannot overwrite existing sensitive property provider registered for mockProvider"
-        assert properties.getSensitivePropertyProviders().size() == 1
     }
 
     @Test
     void testGetUnprotectedPropertiesShouldReturnInternalInstanceWhenNoneProtected() {
-        // Arrange
         ProtectedNiFiRegistryProperties protectedNiFiProperties = loadFromResourceFile("/conf/nifi-registry.properties")
-        logger.info("Loaded ${protectedNiFiProperties.size()} properties from conf/nifi.properties")
 
         int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
-        logger.info("Hash code of internal instance: ${hashCode}")
 
-        // Act
         NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
-        logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
 
-        // Assert
         assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
         assert unprotectedNiFiProperties.getPropertyKeys().every {
             !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
         }
-        logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
         assert unprotectedNiFiProperties.hashCode() == hashCode
     }
 
     @Test
     void testGetUnprotectedPropertiesShouldDecryptProtectedProperties() {
-        // Arrange
         ProtectedNiFiRegistryProperties protectedNiFiProperties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
 
-        int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
-        int protectionSchemeCount = protectedNiFiProperties
-                .getPropertyKeysIncludingProtectionSchemes()
-                .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
-                .size()
         int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
 
-        String protectedProps = protectedNiFiProperties
-                .getProtectedPropertyKeys()
-                .collectEntries {
-            [(it.key): protectedNiFiProperties.getProperty(it.key)]
-        }.entrySet()
-                .join("\n")
-
-        logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
-        logger.info("Protected properties: \n${protectedProps}")
-
-        logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
         int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
-        logger.info("Hash code of internal instance: ${hashCode}")
 
-        // Act
         NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
-        logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
 
-        // Assert
         assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
         assert unprotectedNiFiProperties.getPropertyKeys().every {
             !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
         }
-        logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
         assert unprotectedNiFiProperties.hashCode() != hashCode
     }
 
     @Test
     void testGetUnprotectedPropertiesShouldDecryptProtectedPropertiesWith256Bit() {
-        // Arrange
-        Assume.assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
         ProtectedNiFiRegistryProperties protectedNiFiProperties =
                 loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_256.properties", KEY_HEX_256)
 
-        int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
-        int protectionSchemeCount = protectedNiFiProperties
-                .getPropertyKeysIncludingProtectionSchemes()
-                .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
-                .size()
         int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
 
-        String protectedProps = protectedNiFiProperties
-                .getProtectedPropertyKeys()
-                .collectEntries {
-            [(it.key): protectedNiFiProperties.getProperty(it.key)]
-        }.entrySet()
-                .join("\n")
-
-        logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
-        logger.info("Protected properties: \n${protectedProps}")
-
-        logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
         int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
-        logger.info("Hash code of internal instance: ${hashCode}")
 
-        // Act
         NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
-        logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
 
-        // Assert
         assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
         assert unprotectedNiFiProperties.getPropertyKeys().every {
             !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
         }
-        logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
         assert unprotectedNiFiProperties.hashCode() != hashCode
     }
 
     @Test
     void testShouldCalculateSize() {
-        // Arrange
         Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
         NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
         ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
 
-        // Act
         int protectedSize = protectedNiFiProperties.size()
-        logger.info("Protected properties (${protectedNiFiProperties.size()}): " +
-                "${protectedNiFiProperties.getPropertyKeys().join(", ")}")
-
-        // Assert
         assert protectedSize == rawProperties.size() - 1
     }
 
     @Test
     void testGetPropertyKeysShouldMatchSize() {
-        // Arrange
         Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
         NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
         ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
 
-        // Act
         def filteredKeys = protectedNiFiProperties.getPropertyKeys()
-        logger.info("Protected properties (${protectedNiFiProperties.size()}): ${filteredKeys.join(", ")}")
 
-        // Assert
         assert protectedNiFiProperties.size() == rawProperties.size() - 1
         assert filteredKeys == rawProperties.getPropertyKeys() - "key.protected"
     }
 
     @Test
     void testShouldGetPropertyKeysIncludingProtectionSchemes() {
-        // Arrange
         Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
         NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
         ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
-        logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
 
-        // Act
         def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
-        logger.info("Protected properties with schemes (${allKeys.size()}): ${allKeys.join(", ")}")
 
-        // Assert
         assert allKeys.size() == rawProperties.size()
         assert allKeys == rawProperties.getPropertyKeys()
     }
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
index 988b596..9085751 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
@@ -35,6 +35,16 @@
             <version>1.16.0-SNAPSHOT</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-property-protection-factory</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.nifi.registry</groupId>
             <artifactId>nifi-registry-properties</artifactId>
             <version>1.16.0-SNAPSHOT</version>
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
index 4fdfbf0..3a76ba3 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
@@ -31,6 +31,9 @@ import org.apache.nifi.encrypt.PropertyEncryptor
 import org.apache.nifi.encrypt.PropertyEncryptorFactory
 import org.apache.nifi.flow.encryptor.FlowEncryptor
 import org.apache.nifi.flow.encryptor.StandardFlowEncryptor
+import org.apache.nifi.properties.scheme.ProtectionScheme
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
 import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
 import org.apache.nifi.toolkit.tls.commandLine.ExitCode
 import org.apache.nifi.util.NiFiBootstrapUtils
@@ -71,10 +74,10 @@ class ConfigEncryptionTool {
     public static flowXmlPath
     public String outputFlowXmlPath
 
-    static final PropertyProtectionScheme DEFAULT_PROTECTION_SCHEME = PropertyProtectionScheme.AES_GCM
+    static final ProtectionScheme DEFAULT_PROTECTION_SCHEME = new StandardProtectionScheme("aes/gcm")
 
-    private PropertyProtectionScheme protectionScheme = DEFAULT_PROTECTION_SCHEME
-    private PropertyProtectionScheme migrationProtectionScheme = DEFAULT_PROTECTION_SCHEME
+    private ProtectionScheme protectionScheme = DEFAULT_PROTECTION_SCHEME
+    private ProtectionScheme migrationProtectionScheme = DEFAULT_PROTECTION_SCHEME
     private String keyHex
     private String migrationKeyHex
     private String password
@@ -128,8 +131,8 @@ class ConfigEncryptionTool {
     private static final String NEW_FLOW_PROVIDER_ARG = "newFlowProvider"
     private static final String TRANSLATE_CLI_ARG = "translateCli"
 
-    private static final String PROTECTION_SCHEME_DESC = String.format("Selects the protection scheme for encrypted properties.  " +
-            "Valid values are: [%s] (default is %s)", PropertyProtectionScheme.values().join(", "), DEFAULT_PROTECTION_SCHEME.name())
+    private static final StandardProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+    private static final String PROTECTION_SCHEME_DESC = String.format("Selects the protection scheme for encrypted properties. Default is AES_GCM. Valid values: %s", PROTECTION_SCHEME_RESOLVER.supportedProtectionSchemes)
 
     // Static holder to avoid re-generating the options object multiple times in an invocation
     private static Options staticOptions
@@ -245,7 +248,7 @@ class ConfigEncryptionTool {
         options.addOption(Option.builder("S").longOpt(PROTECTION_SCHEME_ARG).hasArg(true).argName("protectionScheme").desc(PROTECTION_SCHEME_DESC).build())
         options.addOption(Option.builder("k").longOpt(KEY_ARG).hasArg(true).argName("keyhex").desc("The raw hexadecimal key to use to encrypt the sensitive properties").build())
         options.addOption(Option.builder("e").longOpt(KEY_MIGRATION_ARG).hasArg(true).argName("keyhex").desc("The old raw hexadecimal key to use during key migration").build())
-        options.addOption(Option.builder("H").longOpt(PROTECTION_SCHEME_MIGRATION_ARG).hasArg(true).argName("protectionScheme").desc("The old protection scheme to use during encryption migration (see --protectionScheme for possible values).  Default is " + DEFAULT_PROTECTION_SCHEME.name()).build())
+        options.addOption(Option.builder("H").longOpt(PROTECTION_SCHEME_MIGRATION_ARG).hasArg(true).argName("protectionScheme").desc("The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is AES_GCM").build())
         options.addOption(Option.builder("p").longOpt(PASSWORD_ARG).hasArg(true).argName("password").desc("The password from which to derive the key to use to encrypt the sensitive properties").build())
         options.addOption(Option.builder("w").longOpt(PASSWORD_MIGRATION_ARG).hasArg(true).argName("password").desc("The old password from which to derive the key during migration").build())
         options.addOption(Option.builder("r").longOpt(USE_KEY_ARG).hasArg(false).desc("If provided, the secure console will prompt for the raw key value in hexadecimal form").build())
@@ -326,7 +329,7 @@ class ConfigEncryptionTool {
             }
 
             if (commandLine.hasOption(PROTECTION_SCHEME_ARG)) {
-                protectionScheme = PropertyProtectionScheme.valueOf(commandLine.getOptionValue(PROTECTION_SCHEME_ARG))
+                protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(commandLine.getOptionValue(PROTECTION_SCHEME_ARG))
             }
 
             // If translating nifi.properties to CLI format, none of the remaining parsing is necessary
@@ -428,22 +431,20 @@ class ConfigEncryptionTool {
                     logger.info("Key migration mode activated")
                 }
                 if (commandLine.hasOption(PROTECTION_SCHEME_MIGRATION_ARG)) {
-                    migrationProtectionScheme = PropertyProtectionScheme.valueOf(commandLine.getOptionValue(PROTECTION_SCHEME_MIGRATION_ARG))
+                    migrationProtectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(commandLine.getOptionValue(PROTECTION_SCHEME_MIGRATION_ARG))
                 }
 
-                if (migrationProtectionScheme.requiresSecretKey()) {
-                    if (commandLine.hasOption(PASSWORD_MIGRATION_ARG)) {
-                        usingPasswordMigration = true
-                        if (commandLine.hasOption(KEY_MIGRATION_ARG)) {
-                            printUsageAndThrow("Only one of '-w'/'--${PASSWORD_MIGRATION_ARG}' and '-e'/'--${KEY_MIGRATION_ARG}' can be used", ExitCode.INVALID_ARGS)
-                        } else {
-                            migrationPassword = commandLine.getOptionValue(PASSWORD_MIGRATION_ARG)
-                        }
+                if (commandLine.hasOption(PASSWORD_MIGRATION_ARG)) {
+                    usingPasswordMigration = true
+                    if (commandLine.hasOption(KEY_MIGRATION_ARG)) {
+                        printUsageAndThrow("Only one of '-w'/'--${PASSWORD_MIGRATION_ARG}' and '-e'/'--${KEY_MIGRATION_ARG}' can be used", ExitCode.INVALID_ARGS)
                     } else {
-                        migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG)
-                        // Use the "migration password" value if the migration key hex is absent
-                        usingPasswordMigration = !migrationKeyHex
+                        migrationPassword = commandLine.getOptionValue(PASSWORD_MIGRATION_ARG)
                     }
+                } else {
+                    migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG)
+                    // Use the "migration password" value if the migration key hex is absent
+                    usingPasswordMigration = !migrationKeyHex
                 }
             } else {
                 if (commandLine.hasOption(PASSWORD_MIGRATION_ARG) || commandLine.hasOption(KEY_MIGRATION_ARG)) {
@@ -451,25 +452,23 @@ class ConfigEncryptionTool {
                 }
             }
 
-            if (protectionScheme.requiresSecretKey()) {
-                if (commandLine.hasOption(PASSWORD_ARG)) {
-                    usingPassword = true
-                    if (commandLine.hasOption(KEY_ARG)) {
-                        printUsageAndThrow("Only one of '-p'/'--${PASSWORD_ARG}' and '-k'/'--${KEY_ARG}' can be used", ExitCode.INVALID_ARGS)
-                    } else {
-                        password = commandLine.getOptionValue(PASSWORD_ARG)
-                    }
+            if (commandLine.hasOption(PASSWORD_ARG)) {
+                usingPassword = true
+                if (commandLine.hasOption(KEY_ARG)) {
+                    printUsageAndThrow("Only one of '-p'/'--${PASSWORD_ARG}' and '-k'/'--${KEY_ARG}' can be used", ExitCode.INVALID_ARGS)
                 } else {
-                    keyHex = commandLine.getOptionValue(KEY_ARG)
-                    usingPassword = !keyHex
+                    password = commandLine.getOptionValue(PASSWORD_ARG)
                 }
+            } else {
+                keyHex = commandLine.getOptionValue(KEY_ARG)
+                usingPassword = !keyHex
+            }
 
-                if (commandLine.hasOption(USE_KEY_ARG)) {
-                    if (keyHex || password) {
-                        logger.warn("If the key or password is provided in the arguments, '-r'/'--${USE_KEY_ARG}' is ignored")
-                    } else {
-                        usingPassword = false
-                    }
+            if (commandLine.hasOption(USE_KEY_ARG)) {
+                if (keyHex || password) {
+                    logger.warn("If the key or password is provided in the arguments, '-r'/'--${USE_KEY_ARG}' is ignored")
+                } else {
+                    usingPassword = false
                 }
             }
 
@@ -575,16 +574,9 @@ class ConfigEncryptionTool {
      *
      * @param rawKey the unprocessed key input
      * @return the formatted hex string in uppercase
-     * @throws KeyException if the key is not a valid length after parsing
      */
-    private static String parseKey(String rawKey) throws KeyException {
+    private static String parseKey(String rawKey) {
         String hexKey = rawKey.replaceAll("[^0-9a-fA-F]", "")
-        def validKeyLengths = getValidKeyLengths()
-        List<Integer> validHexCharLengths = validKeyLengths.collect {it / 4 }
-        if (!validKeyLengths.contains(hexKey.size() * 4)) {
-            throw new KeyException("The key (${hexKey.size()} hex chars) must be of length ${validKeyLengths} " +
-                    "bits (${validHexCharLengths} hex characters)")
-        }
         hexKey.toUpperCase()
     }
 
@@ -597,9 +589,8 @@ class ConfigEncryptionTool {
         Cipher.getMaxAllowedKeyLength("AES") > 128 ? [128, 192, 256] : [128]
     }
 
-    private NiFiPropertiesLoader getNiFiPropertiesLoader(final String keyHex) {
-        return protectionScheme.requiresSecretKey() || migrationProtectionScheme.requiresSecretKey()
-                ? NiFiPropertiesLoader.withKey(keyHex) : new NiFiPropertiesLoader()
+    private static NiFiPropertiesLoader getNiFiPropertiesLoader(final String keyHex) {
+        keyHex == null ? new NiFiPropertiesLoader() : NiFiPropertiesLoader.withKey(keyHex)
     }
 
     /**
@@ -695,6 +686,7 @@ class ConfigEncryptionTool {
             try {
                 return new GZIPInputStream(new FileInputStream(filePath))
             } catch (ZipException e) {
+                logger.debug("GZIP Compression not found: {}", e.getMessage())
                 return new FileInputStream(filePath)
             } catch (RuntimeException e) {
                 if (isVerbose) {
@@ -742,7 +734,7 @@ class ConfigEncryptionTool {
         loadFlowXml(outputFlowXmlPath)
     }
 
-    private OutputStream getFlowOutputStream(File outputFlowXmlPath, boolean isFileGZipped) {
+    private static OutputStream getFlowOutputStream(File outputFlowXmlPath, boolean isFileGZipped) {
         OutputStream flowOutputStream = new FileOutputStream(outputFlowXmlPath)
         if(isFileGZipped) {
             flowOutputStream = new GZIPOutputStream(flowOutputStream)
@@ -751,7 +743,7 @@ class ConfigEncryptionTool {
     }
 
     // Create a temporary output file we can write the stream to
-    private Path getTemporaryFlowXmlFile(String originalOutputFlowXmlPath) {
+    private static Path getTemporaryFlowXmlFile(String originalOutputFlowXmlPath) {
         String outputFilename = Paths.get(originalOutputFlowXmlPath).getFileName().toString()
         String migratedFileName = "migrated-${outputFilename}"
         Paths.get(originalOutputFlowXmlPath).resolveSibling(migratedFileName)
@@ -782,7 +774,7 @@ class ConfigEncryptionTool {
 
             passwords.each { password ->
                 final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
-                        .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
+                        .getProvider(new StandardProtectionScheme((String) password.@encryption))
                 if (isVerbose) {
                     logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
                 }
@@ -798,6 +790,9 @@ class ConfigEncryptionTool {
             logger.info("Updated XML content: ${updatedXml}")
             updatedXml
         } catch (Exception e) {
+            if (isVerbose) {
+                logger.error("Processing XML failed", e)
+            }
             printUsageAndThrow("Cannot decrypt login identity providers XML content", ExitCode.SERVICE_ERROR)
         }
     }
@@ -830,7 +825,7 @@ class ConfigEncryptionTool {
                     logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
                 }
                 final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
-                        .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
+                        .getProvider(new StandardProtectionScheme((String) password.@encryption))
                 final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
                 String decryptedValue = sensitivePropertyProvider.unprotect((String) password.text().trim(), context)
                 password.replaceNode {
@@ -845,15 +840,18 @@ class ConfigEncryptionTool {
             }
             updatedXml
         } catch (Exception e) {
+            if (isVerbose) {
+                logger.error("Processor Authorizers failed", e)
+            }
             printUsageAndThrow("Cannot decrypt authorizers XML content", ExitCode.SERVICE_ERROR)
         }
     }
 
-    ProtectedPropertyContext getContext(final SensitivePropertyProviderFactory providerFactory, final String propertyName, final String groupIdentifier) {
-        providerFactory.getPropertyContext(groupIdentifier, propertyName);
+    static ProtectedPropertyContext getContext(final SensitivePropertyProviderFactory providerFactory, final String propertyName, final String groupIdentifier) {
+        providerFactory.getPropertyContext(groupIdentifier, propertyName)
     }
 
-    String encryptLoginIdentityProviders(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+    String encryptLoginIdentityProviders(final String plainXml, final String newKeyHex = keyHex) {
         final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
 
         // TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
@@ -872,11 +870,11 @@ class ConfigEncryptionTool {
                 }
                 return plainXml
             }
-            final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
+            final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(protectionScheme)
 
             passwords.each { password ->
                 if (isVerbose) {
-                    logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
+                    logger.info("Attempting to encrypt ${password.name()} using protection scheme ${protectionScheme}")
                 }
                 final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
                 String encryptedValue = sensitivePropertyProvider.protect((String) password.text().trim(), context)
@@ -897,7 +895,7 @@ class ConfigEncryptionTool {
         }
     }
 
-    String encryptAuthorizers(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+    String encryptAuthorizers(final String plainXml, final String newKeyHex = keyHex) {
         final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
 
         // TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
@@ -920,11 +918,11 @@ class ConfigEncryptionTool {
                 }
                 return plainXml
             }
-            final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
+            final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(protectionScheme)
 
             passwords.each { password ->
                 if (isVerbose) {
-                    logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
+                    logger.info("Attempting to encrypt ${password.name()} using protection scheme ${protectionScheme}")
                 }
                 final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
                 String encryptedValue = sensitivePropertyProvider.protect((String) password.text().trim(), context)
@@ -984,7 +982,7 @@ class ConfigEncryptionTool {
 
                 // Add the encrypted value
                 encryptedProperties.setProperty(key, protectedValue)
-                logger.info("Protected ${key} with ${spp.getIdentifierKey()} -> \t${protectedValue}")
+                logger.info("Protected ${key} with ${protectionScheme} -> \t${protectedValue}")
 
                 // Add the protection key ("x.y.z.protected" -> "aes/gcm/{128,256}")
                 String protectionKey = ApplicationPropertiesProtector.getProtectionKey(key)
@@ -1239,7 +1237,7 @@ class ConfigEncryptionTool {
             }
         } catch (SAXException e) {
             logger.error("No provider element with class {} found in XML content; " +
-                    "the file could be empty or the element may be missing or commented out", LDAP_PROVIDER_CLASS)
+                    "the file could be empty or the element may be missing or commented out: {}", LDAP_PROVIDER_CLASS, e.getMessage())
             return fileContents.split("\n")
         }
     }
@@ -1259,7 +1257,7 @@ class ConfigEncryptionTool {
             }
         } catch (SAXException e) {
             logger.error("No provider element with class {} found in XML content; " +
-                    "the file could be empty or the element may be missing or commented out", LDAP_USER_GROUP_PROVIDER_CLASS)
+                    "the file could be empty or the element may be missing or commented out: {}", LDAP_USER_GROUP_PROVIDER_CLASS, e.getMessage())
             return fileContents.split("\n")
         }
     }
@@ -1287,7 +1285,7 @@ class ConfigEncryptionTool {
 
         // Generate a 128 bit salt
         byte[] salt = generateScryptSaltForKeyDerivation()
-        int keyLengthInBytes = getValidKeyLengths().max() / 8
+        int keyLengthInBytes = (int) (getValidKeyLengths().max() / 8)
         byte[] derivedKeyBytes = SCrypt.generate(password.getBytes(StandardCharsets.UTF_8), salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, keyLengthInBytes)
         Hex.encodeHexString(derivedKeyBytes).toUpperCase()
     }
@@ -1322,6 +1320,7 @@ class ConfigEncryptionTool {
                 def nfp = getNiFiPropertiesLoader(keyHex).readProtectedPropertiesFromDisk(new File(niFiPropertiesPath))
                 return nfp.hasProtectedKeys()
             } catch (SensitivePropertyProtectionException | IOException e) {
+                logger.debug("Read Protected Properties failed {}", e.getMessage())
                 return true
             }
         } else {
@@ -1397,21 +1396,18 @@ class ConfigEncryptionTool {
                         tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
                     }
 
-                    if (tool.migration && tool.migrationProtectionScheme.requiresSecretKey()) {
+                    if (tool.migration) {
                         String migrationKeyHex = tool.getMigrationKey()
-
-                        if (!migrationKeyHex) {
-                            tool.printUsageAndThrow("Original hex key must be provided for migration", ExitCode.INVALID_ARGS)
-                        }
-
-                        try {
-                            // Validate the length and format
-                            tool.migrationKeyHex = parseKey(migrationKeyHex)
-                        } catch (KeyException e) {
-                            if (tool.isVerbose) {
-                                logger.error("Encountered an error", e)
+                        if (migrationKeyHex) {
+                            try {
+                                // Validate the length and format
+                                tool.migrationKeyHex = parseKey(migrationKeyHex)
+                            } catch (KeyException e) {
+                                if (tool.isVerbose) {
+                                    logger.error("Encountered an error", e)
+                                }
+                                tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
                             }
-                            tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
                         }
                     }
                 }
@@ -1422,6 +1418,7 @@ class ConfigEncryptionTool {
                     try {
                         tool.niFiProperties = tool.loadNiFiProperties(existingKeyHex)
                     } catch (Exception e) {
+                        logger.error("Load Properties failed", e)
                         tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_READING_NIFI_PROPERTIES)
                     }
                 }
@@ -1430,6 +1427,7 @@ class ConfigEncryptionTool {
                     try {
                         tool.loginIdentityProviders = tool.loadLoginIdentityProviders(existingKeyHex)
                     } catch (Exception e) {
+                        logger.error("Load Login Identify Providers failed", e)
                         tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS)
                     }
                     tool.loginIdentityProviders = tool.encryptLoginIdentityProviders(tool.loginIdentityProviders)
@@ -1439,6 +1437,7 @@ class ConfigEncryptionTool {
                     try {
                         tool.authorizers = tool.loadAuthorizers(existingKeyHex)
                     } catch (Exception e) {
+                        logger.error("Load Authorizers failed", e)
                         tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS)
                     }
                     tool.authorizers = tool.encryptAuthorizers(tool.authorizers)
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
index 1fbf8e7..888a0c2 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
@@ -20,9 +20,11 @@ import groovy.cli.commons.CliBuilder
 import groovy.cli.commons.OptionAccessor
 import org.apache.commons.cli.HelpFormatter
 import org.apache.nifi.properties.ConfigEncryptionTool
-import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionSchemeResolver
 import org.apache.nifi.properties.SensitivePropertyProvider
 import org.apache.nifi.properties.SensitivePropertyProviderFactory
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
 import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
 import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
 import org.apache.nifi.toolkit.encryptconfig.util.PropertiesEncryptor
@@ -36,6 +38,8 @@ class DecryptMode implements ToolMode {
 
     private static final Logger logger = LoggerFactory.getLogger(DecryptMode.class)
 
+    private static final ProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
     static enum FileType {
         properties,
         xml
@@ -209,7 +213,7 @@ class DecryptMode implements ToolMode {
         OptionAccessor rawOptions
 
         Configuration.KeySource keySource
-        PropertyProtectionScheme protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
+        ProtectionScheme protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
         String key
         SensitivePropertyProvider decryptionProvider
         SensitivePropertyProviderFactory providerFactory
@@ -233,12 +237,8 @@ class DecryptMode implements ToolMode {
 
             determineProtectionScheme()
             determineBootstrapProperties()
-            if (protectionScheme.requiresSecretKey()) {
-                determineKey()
-                if (!key) {
-                    throw new RuntimeException("Failed to configure tool, could not determine key.")
-                }
-            }
+            determineKey()
+
             providerFactory = StandardSensitivePropertyProviderFactory
                     .withKeyAndBootstrapSupplier(key, ConfigEncryptionTool.getBootstrapSupplier(inputBootstrapPath))
             decryptionProvider = providerFactory.getProvider(protectionScheme)
@@ -286,7 +286,7 @@ class DecryptMode implements ToolMode {
         private void determineProtectionScheme() {
 
             if (rawOptions.S) {
-                protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+                protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.S)
             }
         }
 
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
index 4fc52aa..36248cd 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
@@ -17,7 +17,8 @@
 package org.apache.nifi.toolkit.encryptconfig
 
 import groovy.cli.commons.CliBuilder
-import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionSchemeResolver
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
 import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
 import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
 import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
@@ -31,6 +32,8 @@ class NiFiRegistryDecryptMode extends DecryptMode {
 
     private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryDecryptMode.class)
 
+    private static final ProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
     CliBuilder cli
     boolean verboseEnabled
 
@@ -73,7 +76,7 @@ class NiFiRegistryDecryptMode extends DecryptMode {
             config.fileType = FileType.properties  // disables auto-detection, which is still experimental
 
             if (options.S) {
-                config.protectionScheme = PropertyProtectionScheme.valueOf((String) options.S)
+                config.protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme((String) options.S)
             }
 
             // one of [-p, -k, -b]
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
index a4f8c90..3f76f59 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
@@ -22,11 +22,11 @@ import org.apache.commons.cli.HelpFormatter
 import org.apache.commons.cli.Options
 import org.apache.nifi.properties.BootstrapProperties
 import org.apache.nifi.properties.ConfigEncryptionTool
-import org.apache.nifi.properties.PropertyProtectionScheme
-import org.apache.nifi.properties.ProtectedPropertyContext
+import org.apache.nifi.properties.scheme.ProtectionScheme
 import org.apache.nifi.properties.SensitivePropertyProtectionException
 import org.apache.nifi.properties.SensitivePropertyProvider
 import org.apache.nifi.properties.SensitivePropertyProviderFactory
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
 import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
 import org.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils
 import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
@@ -44,6 +44,8 @@ class NiFiRegistryMode implements ToolMode {
 
     private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryMode.class)
 
+    private static final StandardProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
     CliBuilder cli
     boolean verboseEnabled
 
@@ -59,7 +61,7 @@ class NiFiRegistryMode implements ToolMode {
                 try {
                     NiFiRegistryBootstrapUtils.loadBootstrapProperties(bootstrapConfPath)
                 } catch (final IOException e) {
-                    throw new SensitivePropertyProtectionException(e.getCause(), e)
+                    throw new SensitivePropertyProtectionException("Loading Bootstrap Properties failed", e)
                 }
             }
         }
@@ -210,7 +212,7 @@ class NiFiRegistryMode implements ToolMode {
         cli.S(longOpt: 'protectionScheme',
                 args: 1,
                 argName: 'protectionScheme',
-                "Selects the protection scheme for encrypted properties.  Valid values are: [${PropertyProtectionScheme.values().join(", ")}] (default is ${ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME.name()})")
+                String.format("Selects the protection scheme for encrypted properties. Default is AES_GCM. Valid values: %s", PROTECTION_SCHEME_RESOLVER.supportedProtectionSchemes))
 
         // Options for the old password or key, if running the tool to migrate keys
         cli._(longOpt: 'oldPassword',
@@ -224,7 +226,7 @@ class NiFiRegistryMode implements ToolMode {
         cli.H(longOpt: 'oldProtectionScheme',
                 args: 1,
                 argName: 'protectionScheme',
-                "The old protection scheme to use during encryption migration (see --protectionScheme for possible values).  Default is ${ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME.name()}.")
+                "The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is AES_GCM.")
 
         // Options for output bootstrap.conf file
         cli.b(longOpt: 'bootstrapConf',
@@ -278,9 +280,9 @@ class NiFiRegistryMode implements ToolMode {
         boolean usingPassword
         boolean usingBootstrapKey
 
-        PropertyProtectionScheme protectionScheme
+        ProtectionScheme protectionScheme
         String encryptionKey
-        PropertyProtectionScheme oldProtectionScheme
+        ProtectionScheme oldProtectionScheme
         String decryptionKey
 
         SensitivePropertyProvider encryptionProvider
@@ -371,7 +373,7 @@ class NiFiRegistryMode implements ToolMode {
         private void determineProtectionScheme() {
 
             if (rawOptions.S) {
-                protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+                protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.S)
             } else {
                 protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
             }
@@ -379,7 +381,7 @@ class NiFiRegistryMode implements ToolMode {
         private void determineOldProtectionScheme() {
 
             if (rawOptions.H) {
-                oldProtectionScheme = PropertyProtectionScheme.valueOf(rawOptions.H)
+                oldProtectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.H)
             } else {
                 oldProtectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
             }
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
index d43f826..c5d374a 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
@@ -89,17 +89,6 @@ class PropertiesEncryptor {
                     "Usually this means a decryption password / key was not provided to the tool.")
         }
 
-        String supportedDecryptionScheme = decryptionProvider.getIdentifierKey()
-        if (supportedDecryptionScheme) {
-            propertiesToDecrypt.entrySet().each { entry ->
-                if (!supportedDecryptionScheme.equals(entry.getValue())) {
-                    throw new IllegalStateException("Decryption capability not supported by this tool. " +
-                            "This tool supports ${supportedDecryptionScheme}, but this properties file contains " +
-                            "${entry.getKey()} protected by ${entry.getValue()}")
-                }
-            }
-        }
-
         Properties unprotectedProperties = new Properties()
 
         for (String propertyName : properties.stringPropertyNames()) {
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
index c35f129..40a7c03 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
@@ -83,17 +83,11 @@ abstract class XmlEncryptor {
                 throw new IllegalStateException("Input XML is encrypted, but decryption capability is not enabled. " +
                         "Usually this means a decryption password / key was not provided to the tool.")
             }
-            String supportedDecryptionScheme = decryptionProvider.getIdentifierKey()
 
             logger.debug("Found ${encryptedNodes.size()} encrypted XML elements. Will attempt to decrypt using the provided decryption key.")
 
             encryptedNodes.each { node ->
                 logger.debug("Attempting to decrypt ${node.text()}")
-                if (node.@encryption != supportedDecryptionScheme) {
-                    throw new IllegalStateException("Decryption capability not supported by this tool. " +
-                            "This tool supports ${supportedDecryptionScheme}, but this xml file contains " +
-                            "${node.toString()} protected by ${node.@encryption}")
-                }
                 String groupIdentifier = (String) node.parent().identifier
                 String propertyName = (String) node.@name
                 String decryptedValue = decryptionProvider.unprotect(node.text().trim(), providerFactory.getPropertyContext(groupIdentifier, propertyName))
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
index 6821565..90a801b 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
@@ -93,8 +93,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     private final String PASSWORD_PROP_REGEX = "<property[^>]* name=\".* Password\""
 
     private static final EncryptionMethod DEFAULT_ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES
-    private static final String DEFAULT_ALGORITHM = DEFAULT_ENCRYPTION_METHOD.algorithm
-    private static final String DEFAULT_PROVIDER = DEFAULT_ENCRYPTION_METHOD.provider
     private static final String WFXCTR = ConfigEncryptionTool.WRAPPED_FLOW_XML_CIPHER_TEXT_REGEX
     private final String DEFAULT_LEGACY_SENSITIVE_PROPS_KEY = "nififtw!"
 
@@ -129,16 +127,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         keyHex?.size() * 4
     }
 
-    private static void printProperties(NiFiProperties properties) {
-        if (!(properties instanceof ProtectedNiFiProperties)) {
-            properties = new ProtectedNiFiProperties(properties)
-        }
-
-        (properties as ProtectedNiFiProperties).getPropertyKeysIncludingProtectionSchemes().sort().each { String key ->
-            logger.info("${key}\t\t${properties.getProperty(key)}")
-        }
-    }
-
     /**
      * OS-agnostic method for setting file permissions. On POSIX-compliant systems, accurately sets the provided permissions. On Windows, sets the corresponding permissions for the file owner only.
      *
@@ -624,36 +612,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    void testParseKeyShouldThrowExceptionForInvalidKeys() {
-        // Arrange
-        List<String> keyValues = [
-                "0123 4567",
-                "non-hex-chars",
-                KEY_HEX[0..<-1],
-                "&ITD SF^FI&&%SDIF"
-        ]
-
-        def validKeyLengths = ConfigEncryptionTool.getValidKeyLengths()
-        def bitLengths = validKeyLengths.collect { it / 4 }
-        String secondHalf = /\[${validKeyLengths.join(", ")}\] bits / +
-                /\(\[${bitLengths.join(", ")}\]/ + / hex characters\)/.toString()
-
-        // Act
-        keyValues.each { String key ->
-            logger.info("Reading key: [${key}]")
-            def msg = shouldFail(KeyException) {
-                String parsedKey = ConfigEncryptionTool.parseKey(key)
-                logger.info("Parsed key:  [${parsedKey}]")
-            }
-            logger.expected(msg)
-            int trimmedKeySize = key.replaceAll("[^0-9a-fA-F]", "").size()
-
-            // Assert
-            assert msg =~ "The key \\(${trimmedKeySize} hex chars\\) must be of length ${secondHalf}"
-        }
-    }
-
-    @Test
     void testShouldActuallyDeriveKeyFromPassword() {
         // Arrange
         logger.info("Using password: [${PASSWORD}]")
@@ -770,16 +728,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", workingFile.path, "-k", KEY_HEX]
         tool.parse(args)
 
-        // Act
-        def msg = shouldFail(IOException) {
+        shouldFail(IOException) {
             tool.loadNiFiProperties()
-            logger.info("Read nifi.properties")
         }
-        logger.expected(msg)
-
-        // Assert
-        assert msg == "Cannot load NiFiProperties from [${workingFile.path}]".toString()
-
         workingFile.deleteOnExit()
     }
 
@@ -874,11 +825,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 "${ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX}="
         ]
 
-        // Act
         List<String> updatedLines = tool.updateBootstrapContentsWithKey(originalLines.clone() as List<String>)
-        logger.info("Updated bootstrap.conf lines: ${updatedLines}")
 
-        // Assert
         assert updatedLines.size() == originalLines.size() + 1
         assert updatedLines.first() == ConfigEncryptionTool.BOOTSTRAP_KEY_COMMENT
         assert updatedLines.last() == EXPECTED_KEY_LINE
@@ -940,40 +888,19 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
 
     @Test
     void testShouldEncryptNiFiPropertiesWithEmptyProtectionScheme() {
-        // Arrange
         String originalNiFiPropertiesPath = "src/test/resources/nifi_with_sensitive_properties_unprotected_and_empty_protection_schemes.properties"
 
-        File originalFile = new File(originalNiFiPropertiesPath)
-        List<String> originalLines = originalFile.readLines()
-        logger.info("Read ${originalLines.size()} lines from ${originalNiFiPropertiesPath}")
-        logger.info("\n" + originalLines[0..3].join("\n") + "...")
-
         NiFiProperties plainProperties = NiFiPropertiesLoader.withKey(KEY_HEX).load(originalNiFiPropertiesPath)
-        logger.info("Loaded NiFiProperties from ${originalNiFiPropertiesPath}")
-
         ProtectedNiFiProperties protectedWrapper = new ProtectedNiFiProperties(plainProperties)
-        logger.info("Loaded ${plainProperties.size()} properties")
-        logger.info("There are ${protectedWrapper.getSensitivePropertyKeys().size()} sensitive properties")
-
         ConfigEncryptionTool tool = new ConfigEncryptionTool(keyHex: KEY_HEX)
 
         final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
         int protectedPropertyCount = protectedWrapper.getProtectedPropertyKeys().size()
-        logger.info("Counted ${protectedPropertyCount} protected keys")
         assert protectedPropertyCount < protectedWrapper.getSensitivePropertyKeys().size()
 
-
-        // Act
         NiFiProperties encryptedProperties = tool.encryptSensitiveProperties(plainProperties)
 
-        // Assert
         ProtectedNiFiProperties encryptedWrapper = new ProtectedNiFiProperties(encryptedProperties)
-        encryptedWrapper.getProtectedPropertyKeys().every { String key, String protectionScheme ->
-            logger.info("${key} is protected by ${protectionScheme}")
-            assert protectionScheme == spp.identifierKey
-        }
-
-        printProperties(encryptedWrapper)
 
         assert encryptedWrapper.getProtectedPropertyKeys().size() == encryptedWrapper.getSensitivePropertyKeys().findAll {
             encryptedWrapper.getProperty(it)
@@ -1037,48 +964,26 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
 
     @Test
     void testShouldSerializeNiFiPropertiesAndPreserveFormatWithExistingProtectionSchemes() {
-        // Arrange
         String originalNiFiPropertiesPath = "src/test/resources/nifi_with_few_sensitive_properties_protected_aes.properties"
 
         File originalFile = new File(originalNiFiPropertiesPath)
         List<String> originalLines = originalFile.readLines()
-        logger.info("Read ${originalLines.size()} lines from ${originalNiFiPropertiesPath}")
-        logger.info("\n" + originalLines[0..3].join("\n") + "...")
 
         ProtectedNiFiProperties protectedProperties = NiFiPropertiesLoader.withKey(KEY_HEX).readProtectedPropertiesFromDisk(new File(originalNiFiPropertiesPath))
-        logger.info("Loaded NiFiProperties from ${originalNiFiPropertiesPath}")
-
-        logger.info("Loaded ${protectedProperties.getPropertyKeys().size()} properties")
-        logger.info("There are ${protectedProperties.getSensitivePropertyKeys().size()} sensitive properties")
-        logger.info("There are ${protectedProperties.getProtectedPropertyKeys().size()} protected properties")
         int originalProtectedPropertyCount = protectedProperties.getProtectedPropertyKeys().size()
 
         protectedProperties.addSensitivePropertyProvider(DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME))
         NiFiProperties encryptedProperties = protectedProperties.getApplicationProperties()
         int protectedPropertyCount = ProtectedNiFiProperties.countProtectedProperties(encryptedProperties)
-        logger.info("Counted ${protectedPropertyCount} protected keys")
 
         int protectedCountChange = protectedPropertyCount - originalProtectedPropertyCount
-        logger.info("Expected line count change: ${protectedCountChange}")
-
-        // Act
         List<String> lines = ConfigEncryptionTool.serializeNiFiPropertiesAndPreserveFormat(protectedProperties, originalFile)
-        logger.info("Serialized NiFiProperties to ${lines.size()} lines")
-        lines.eachWithIndex { String entry, int i ->
-            logger.debug("${(i + 1).toString().padLeft(3)}: ${entry}")
-        }
 
-        // Assert
-
-        // Added n new lines for the encrypted properties
         assert lines.size() == originalLines.size() + protectedCountChange
 
         protectedProperties.getPropertyKeys().every { String key ->
             assert lines.contains("${key}=${protectedProperties.getProperty(key)}".toString())
         }
-
-        logger.info("Updated nifi.properties:")
-        logger.info("\n" * 2 + lines.join("\n"))
     }
 
     @Test
@@ -1299,13 +1204,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
             }
         })
 
-        // Act
         ConfigEncryptionTool.main(args)
-        logger.info("Invoked #main with ${args.join(" ")}")
-
-        // Assert
-
-        // Assertions defined above
     }
 
     @Test
@@ -1381,13 +1280,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
             }
         })
 
-        // Act
         ConfigEncryptionTool.main(args)
-        logger.info("Invoked #main with ${args.join(" ")}")
-
-        // Assert
-
-        // Assertions defined above
     }
 
     @Test
@@ -1475,12 +1368,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
             }
         })
 
-        logger.info("Invoked #main second time with ${args.join(" ")}")
         ConfigEncryptionTool.main(args)
-
-        // Assert
-
-        // Assertions defined above
     }
 
     /**
@@ -1595,12 +1483,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
             }
         })
 
-        logger.info("Migrating key (${scenario}) with ${localArgs.join(" ")}")
         ConfigEncryptionTool.main(localArgs as String[])
-
-        // Assert
-
-        // Assertions defined above
     }
 
     /**
@@ -1608,58 +1491,34 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
      */
     @Test
     void testShouldMigrateFromPasswordToPassword() {
-        // Arrange
         String scenario = "password to password"
         def args = ["-w", PASSWORD, "-p", PASSWORD.reverse()]
 
-        // Act
         performKeyMigration(scenario, args, PASSWORD, PASSWORD.reverse())
-
-        // Assert
-
-        // Assertions in common method above
     }
 
     @Test
     void testShouldMigrateFromPasswordToKey() {
-        // Arrange
         String scenario = "password to key"
         def args = ["-w", PASSWORD, "-k", KEY_HEX]
 
-        // Act
         performKeyMigration(scenario, args, PASSWORD, "", "", KEY_HEX)
-
-        // Assert
-
-        // Assertions in common method above
     }
 
     @Test
     void testShouldMigrateFromKeyToPassword() {
-        // Arrange
         String scenario = "key to password"
         def args = ["-e", PASSWORD_KEY_HEX, "-p", PASSWORD.reverse()]
 
-        // Act
         performKeyMigration(scenario, args, "", PASSWORD.reverse(), PASSWORD_KEY_HEX, "")
-
-        // Assert
-
-        // Assertions in common method above
     }
 
     @Test
     void testShouldMigrateFromKeyToKey() {
-        // Arrange
         String scenario = "key to key"
         def args = ["-e", PASSWORD_KEY_HEX, "-k", KEY_HEX]
 
-        // Act
         performKeyMigration(scenario, args, "", "", PASSWORD_KEY_HEX, KEY_HEX)
-
-        // Assert
-
-        // Assertions in common method above
     }
 
     @Test
@@ -2424,13 +2283,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
             }
         })
 
-        // Act
         ConfigEncryptionTool.main(args)
-        logger.info("Invoked #main with ${args.join(" ")}")
-
-        // Assert
-
-        // Assertions defined above
     }
 
     @Test
@@ -3555,11 +3408,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
      */
     @Test
     void testShouldPerformFullOperationOnFlowXmlWithSameSensitivePropsKey() {
-        // Arrange
         exit.expectSystemExitWithStatus(0)
 
-        File tmpDir = setupTmpDir()
-
         File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_root_key.conf")
         File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
         bootstrapFile.delete()
@@ -3569,7 +3419,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String originalKeyLine = originalBootstrapLines.find {
             it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
         }
-        logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
         assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
 
         final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
@@ -3594,10 +3443,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
 
         NiFiProperties inputProperties = new NiFiPropertiesLoader().load(workingNiFiPropertiesFile)
-        logger.info("Loaded ${inputProperties.size()} properties from input file")
         ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
         def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
-        logger.info("Original sensitive values: ${originalSensitiveValues}")
 
         String newFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY
 
@@ -3606,8 +3453,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         exit.checkAssertionAfterwards(new Assertion() {
             void checkAssertion() {
                 final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
-                logger.info("Updated nifi.properties:")
-                logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
 
                 // Check that the output values for everything is the same including the sensitive props key
                 NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
@@ -3621,7 +3466,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 String updatedKeyLine = updatedBootstrapLines.find {
                     it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
                 }
-                logger.info("Updated key line: ${updatedKeyLine}")
 
                 assert updatedKeyLine == EXPECTED_KEY_LINE
                 assert originalBootstrapLines.size() == updatedBootstrapLines.size()
@@ -3635,20 +3479,12 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 assert migratedFlowXmlContent != ORIGINAL_FLOW_XML_CONTENT
 
                 // Verify that the cipher texts decrypt correctly
-                logger.info("Original flow.xml.gz cipher texts: ${originalFlowCipherTexts}")
                 def migratedFlowCipherTexts = findFieldsInStream(migratedFlowXmlContent, WFXCTR)
-                logger.info("Updated  flow.xml.gz cipher texts: ${migratedFlowCipherTexts}")
                 assert migratedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
             }
         })
 
-        // Act
         ConfigEncryptionTool.main(args)
-        logger.info("Invoked #main with ${args.join(" ")}")
-
-        // Assert
-
-        // Assertions defined above
     }
 
     /**
@@ -4381,37 +4217,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
 
         String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-c"]
 
-        exit.checkAssertionAfterwards(new Assertion() {
-            void checkAssertion() {
-                final String standardOutput = systemOutRule.getLog()
-                List<String> lines = standardOutput.split("\n")
-
-                // The SystemRule log also includes STDERR, so truncate after 9 lines
-                def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
-                logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
-
-                // Split the output into lines and create a map of the keys and values
-                def parsedCli = stdoutLines.collectEntries { String line ->
-                    def components = line.split("=", 2)
-                    components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
-                }
-
-                assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
-                assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
-
-                // Clean up
-                bootstrapFile.deleteOnExit()
-                tmpDir.deleteOnExit()
-            }
-        })
-
-        // Act
         ConfigEncryptionTool.main(args)
-        logger.info("Invoked #main with ${args.join(" ")}")
-
-        // Assert
-
-        // Assertions defined above
     }
 
     @Test
@@ -4720,14 +4526,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         invalidOpts.each { String invalid ->
             tool = new ConfigEncryptionTool()
             def args = (invalid + " -c").split(" ")
-            logger.info("Testing with ${args}")
-            def msg = shouldFail(CommandLineParseException) {
+            shouldFail(CommandLineParseException) {
                 tool.parse(args as String[])
             }
-
-            // Assert
-            assert msg == "When '-c'/'--translateCli' is specified, only '-h', '-v', and '-n'/'-b' with the relevant files are allowed"
-            assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
         }
     }
 
@@ -4744,14 +4545,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         // Act
         invalidOpts.each { String invalid ->
             def args = (invalid + " -c").split(" ")
-            logger.info("Testing with ${args}")
-            def msg = shouldFail(CommandLineParseException) {
+            shouldFail(CommandLineParseException) {
                 tool.parse(args as String[])
             }
-
-            // Assert
-            assert msg == "When '-c'/'--translateCli' is specified, '-n'/'--niFiProperties' is required (and '-b'/'--bootstrapConf' is required if the properties are encrypted)"
-            assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
         }
     }
 
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
index 72c3a0f..74b2151 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
@@ -18,9 +18,9 @@ package org.apache.nifi.toolkit.encryptconfig
 
 
 import org.apache.nifi.properties.NiFiPropertiesLoader
-import org.apache.nifi.properties.PropertyProtectionScheme
 import org.apache.nifi.properties.ProtectedPropertyContext
 import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
 import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
 import org.apache.nifi.util.NiFiProperties
 import org.bouncycastle.jce.provider.BouncyCastleProvider
@@ -185,7 +185,7 @@ class EncryptConfigMainTest extends GroovyTestCase {
                 "-v"]
 
         SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(TestUtil.KEY_HEX)
-                .getProvider(PropertyProtectionScheme.AES_GCM)
+                .getProvider(new StandardProtectionScheme("aes/gcm"))
 
         exit.checkAssertionAfterwards(new Assertion() {
             void checkAssertion() {
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy
deleted file mode 100644
index e8f86f4..0000000
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.toolkit.encryptconfig
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.Assume
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import spock.lang.Specification
-
-import java.security.Security
-
-import static org.apache.nifi.toolkit.encryptconfig.TestUtil.*
-
-class NiFiRegistryDecryptModeSpec extends Specification {
-    private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryDecryptModeSpec.class)
-
-    ByteArrayOutputStream toolStdOutContent
-    PrintStream origSystemOut
-
-    // runs before every feature method
-    def setup() {
-        origSystemOut = System.out
-        toolStdOutContent = new ByteArrayOutputStream();
-        System.setOut(new PrintStream(toolStdOutContent));
-    }
-
-    // runs after every feature method
-    def cleanup() {
-        toolStdOutContent.flush()
-        System.setOut(origSystemOut);
-        toolStdOutContent.close()
-    }
-
-    // runs before the first feature method
-    def setupSpec() {
-        Security.addProvider(new BouncyCastleProvider())
-        setupTmpDir()
-    }
-
-    // runs after the last feature method
-    def cleanupSpec() {
-        cleanupTmpDir()
-    }
-
-    def "decrypt protected nifi-registry.properties file using -k"() {
-
-        setup:
-        NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
-        def inRegistryProperties1 = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, "nifi-registry.properties")
-        File outRegistryProperties1 = generateTmpFile()
-
-        when: "run with args: -k <key> -r <file>"
-        tool.run("-k ${KEY_HEX_128} -r ${inRegistryProperties1}".split(" "))
-        toolStdOutContent.flush()
-        outRegistryProperties1.text = toolStdOutContent.toString()
-        then: "decrypted properties file was printed to std out"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties1.getAbsolutePath(), true)
-        and: "input properties file is still encrypted"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, inRegistryProperties1, true)
-
-    }
-
-    def "decrypt protected nifi-registry.properties file using -p [256-bit]"() {
-
-        Assume.assumeTrue("Test only runs when unlimited strength crypto is available", isUnlimitedStrengthCryptoAvailable())
-
-        setup:
-        NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
-        def inRegistryProperties1 = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_PASSWORD_256, "nifi-registry.properties")
-        File outRegistryProperties1 = generateTmpFile()
-
-        when: "run with args: -p <password> -r <file>"
-        tool.run("-p ${PASSWORD} -r ${inRegistryProperties1}".split(" "))
-        toolStdOutContent.flush()
-        outRegistryProperties1.text = toolStdOutContent.toString()
-        then: "decrypted properties file was printed to std out"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties1.getAbsolutePath(), true)
-        and: "input properties file is still encrypted"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_PASSWORD_256, inRegistryProperties1, true)
-
-    }
-
-    def "decrypt protected nifi-registry.properties file using -b"() {
-
-        setup:
-        NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
-        def inRegistryProperties = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, "nifi-registry.properties")
-        def inBootstrap = copyFileToTempFile(RESOURCE_REGISTRY_BOOTSTRAP_KEY_128)
-        File outRegistryProperties = generateTmpFile()
-
-        when: "run with args: -b <file> -r <file>"
-        tool.run("-b ${inBootstrap} -r ${inRegistryProperties}".split(" "))
-        toolStdOutContent.flush()
-        outRegistryProperties.text = toolStdOutContent.toString()
-        then: "decrypted properties file was printed to std out"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties.getAbsolutePath(), true)
-        and: "input properties file is still encrypted"
-        assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, inRegistryProperties, true)
-
-    }
-
-}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
index 12f6e3a..349e7ad 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
@@ -17,8 +17,8 @@
 package org.apache.nifi.toolkit.encryptconfig
 
 import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.properties.PropertyProtectionScheme
 import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
 import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryAuthorizersXmlEncryptor
 import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryIdentityProvidersXmlEncryptor
 
@@ -312,7 +312,7 @@ class TestUtil {
         assert populatedSensitiveProperties.size() == protectedSensitiveProperties.size()
 
         SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(expectedKey)
-                .getProvider(PropertyProtectionScheme.AES_GCM)
+                .getProvider(new StandardProtectionScheme("aes/gcm"))
 
         protectedSensitiveProperties.each {
             String value = it.text()