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/02/25 16:04:12 UTC

[nifi] branch main updated: NIFI-9724 Added set-sensitive-properties-algorithm command

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 49d1c74  NIFI-9724 Added set-sensitive-properties-algorithm command
49d1c74 is described below

commit 49d1c747cac465f8f40758142e5c5c549d5d618f
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Thu Feb 24 22:10:02 2022 -0500

    NIFI-9724 Added set-sensitive-properties-algorithm command
    
    Signed-off-by: Joe Gresock <jg...@gmail.com>
    
    This closes #5801.
---
 ...ropertiesKey.java => FlowEncryptorCommand.java} |  71 +++++-----
 .../command/SetSensitivePropertiesAlgorithm.java   |  35 +++++
 .../command/SetSensitivePropertiesKey.java         | 155 +--------------------
 ...sKeyTest.java => FlowEncryptorCommandTest.java} | 120 ++++++++++------
 .../SetSensitivePropertiesAlgorithmTest.java       |  48 +++++++
 .../command/SetSensitivePropertiesKeyTest.java     | 122 +---------------
 .../src/main/asciidoc/administration-guide.adoc    |  19 +++
 .../nifi-resources/src/main/resources/bin/nifi.sh  |  14 +-
 8 files changed, 239 insertions(+), 345 deletions(-)

diff --git a/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommand.java
similarity index 73%
copy from nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java
copy to nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommand.java
index 1a4ea8f..fb9af73 100644
--- a/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java
+++ b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommand.java
@@ -35,15 +35,16 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.stream.Collectors;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
 /**
- * Set Sensitive Properties Key for NiFi Properties and update encrypted Flow Configuration
+ * Flow Encryptor Command capable of updating Sensitive Properties Key or Algorithm as well as Flow Configuration
  */
-public class SetSensitivePropertiesKey {
+class FlowEncryptorCommand implements Runnable {
     protected static final String PROPERTIES_FILE_PATH = "nifi.properties.file.path";
 
     protected static final String PROPS_KEY = "nifi.sensitive.props.key";
@@ -56,8 +57,6 @@ public class SetSensitivePropertiesKey {
 
     private static final List<String> CONFIGURATION_FILES = Arrays.asList(CONFIGURATION_FILE, CONFIGURATION_JSON_FILE);
 
-    private static final int MINIMUM_REQUIRED_LENGTH = 12;
-
     private static final String FLOW_PREFIX = "nifi.flow.";
 
     private static final String GZ_EXTENSION = ".gz";
@@ -68,39 +67,47 @@ public class SetSensitivePropertiesKey {
 
     private static final String SENSITIVE_PROPERTIES_KEY = String.format("%s=", PROPS_KEY);
 
-    public static void main(final String[] arguments) {
-        if (arguments.length == 1) {
-            final String outputPropertiesKey = arguments[0];
-            if (outputPropertiesKey.length() < MINIMUM_REQUIRED_LENGTH) {
-                System.err.printf("Sensitive Properties Key length less than required [%d]%n", MINIMUM_REQUIRED_LENGTH);
-            } else {
-                run(outputPropertiesKey);
-            }
-        } else {
-            System.err.printf("Unexpected number of arguments [%d]%n", arguments.length);
-            System.err.printf("Usage: %s <sensitivePropertiesKey>%n", SetSensitivePropertiesKey.class.getSimpleName());
-        }
+    private static final String SENSITIVE_PROPERTIES_ALGORITHM = String.format("%s=", PROPS_ALGORITHM);
+
+    private String requestedPropertiesKey;
+
+    private String requestedPropertiesAlgorithm;
+
+    void setRequestedPropertiesKey(final String requestedPropertiesKey) {
+        this.requestedPropertiesKey = Objects.requireNonNull(requestedPropertiesKey, "Key required");
+    }
+
+    void setRequestedPropertiesAlgorithm(final String requestedPropertiesAlgorithm) {
+        this.requestedPropertiesAlgorithm = Objects.requireNonNull(requestedPropertiesAlgorithm, "Algorithm required");
     }
 
-    private static void run(final String outputPropertiesKey) {
+    /**
+     * Run command using nifi.properties location read from System Properties
+     */
+    @Override
+    public void run() {
         final String propertiesFilePath = System.getProperty(PROPERTIES_FILE_PATH);
+        if (propertiesFilePath == null) {
+            throw new IllegalStateException(String.format("System property not defined [%s]", PROPERTIES_FILE_PATH));
+        }
         final File propertiesFile = new File(propertiesFilePath);
         final Properties properties = loadProperties(propertiesFile);
 
+        processFlowConfigurationFiles(properties);
+
         try {
-            storeProperties(propertiesFile, outputPropertiesKey);
+            storeProperties(propertiesFile);
             System.out.printf("NiFi Properties Processed [%s]%n", propertiesFilePath);
         } catch (final IOException e) {
             final String message = String.format("Failed to Process NiFi Properties [%s]", propertiesFilePath);
             throw new UncheckedIOException(message, e);
         }
-
-        processFlowConfigurationFiles(properties, outputPropertiesKey);
     }
 
-    private static void processFlowConfigurationFiles(final Properties properties, final String outputPropertiesKey) {
-        final String algorithm = getAlgorithm(properties);
-        final PropertyEncryptor outputEncryptor = getPropertyEncryptor(outputPropertiesKey, algorithm);
+    private void processFlowConfigurationFiles(final Properties properties) {
+        final String outputAlgorithm = requestedPropertiesAlgorithm == null ? getAlgorithm(properties) : requestedPropertiesAlgorithm;
+        final String outputKey = requestedPropertiesKey == null ? getKey(properties) : requestedPropertiesKey;
+        final PropertyEncryptor outputEncryptor = getPropertyEncryptor(outputKey, outputAlgorithm);
 
         for (final String configurationFilePropertyName : CONFIGURATION_FILES) {
             final String configurationFileProperty = properties.getProperty(configurationFilePropertyName);
@@ -115,7 +122,7 @@ public class SetSensitivePropertiesKey {
         }
     }
 
-    private static void processFlowConfiguration(final File flowConfigurationFile, final Properties properties, final PropertyEncryptor outputEncryptor) {
+    private void processFlowConfiguration(final File flowConfigurationFile, final Properties properties, final PropertyEncryptor outputEncryptor) {
         try (final InputStream flowInputStream = new GZIPInputStream(new FileInputStream(flowConfigurationFile))) {
             final File flowOutputFile = getFlowOutputFile();
             final Path flowOutputPath = flowOutputFile.toPath();
@@ -137,7 +144,7 @@ public class SetSensitivePropertiesKey {
         }
     }
 
-    private static String getAlgorithm(final Properties properties) {
+    private String getAlgorithm(final Properties properties) {
         String algorithm = properties.getProperty(PROPS_ALGORITHM, DEFAULT_PROPERTIES_ALGORITHM);
         if (algorithm.length() == 0) {
             algorithm = DEFAULT_PROPERTIES_ALGORITHM;
@@ -145,7 +152,7 @@ public class SetSensitivePropertiesKey {
         return algorithm;
     }
 
-    private static String getKey(final Properties properties) {
+    private String getKey(final Properties properties) {
         String key = properties.getProperty(PROPS_KEY, DEFAULT_PROPERTIES_KEY);
         if (key.length() == 0) {
             key = DEFAULT_PROPERTIES_KEY;
@@ -153,13 +160,13 @@ public class SetSensitivePropertiesKey {
         return key;
     }
 
-    private static File getFlowOutputFile() throws IOException {
+    private File getFlowOutputFile() throws IOException {
         final File flowOutputFile = File.createTempFile(FLOW_PREFIX, GZ_EXTENSION);
         flowOutputFile.deleteOnExit();
         return flowOutputFile;
     }
 
-    private static Properties loadProperties(final File propertiesFile) {
+    private Properties loadProperties(final File propertiesFile) {
         final Properties properties = new Properties();
         try (final FileReader reader = new FileReader(propertiesFile)) {
             properties.load(reader);
@@ -170,12 +177,14 @@ public class SetSensitivePropertiesKey {
         return properties;
     }
 
-    private static void storeProperties(final File propertiesFile, final String propertiesKey) throws IOException {
+    private void storeProperties(final File propertiesFile) throws IOException {
         final Path propertiesFilePath = propertiesFile.toPath();
         final List<String> lines = Files.readAllLines(propertiesFilePath);
         final List<String> updatedLines = lines.stream().map(line -> {
             if (line.startsWith(SENSITIVE_PROPERTIES_KEY)) {
-                return SENSITIVE_PROPERTIES_KEY + propertiesKey;
+                return requestedPropertiesKey == null ? line : SENSITIVE_PROPERTIES_KEY + requestedPropertiesKey;
+            } else if (line.startsWith(SENSITIVE_PROPERTIES_ALGORITHM)) {
+                return requestedPropertiesAlgorithm == null ? line : SENSITIVE_PROPERTIES_ALGORITHM + requestedPropertiesAlgorithm;
             } else {
                 return line;
             }
@@ -183,7 +192,7 @@ public class SetSensitivePropertiesKey {
         Files.write(propertiesFilePath, updatedLines);
     }
 
-    private static PropertyEncryptor getPropertyEncryptor(final String propertiesKey, final String propertiesAlgorithm) {
+    private PropertyEncryptor getPropertyEncryptor(final String propertiesKey, final String propertiesAlgorithm) {
         return new PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
     }
 }
diff --git a/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithm.java b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithm.java
new file mode 100644
index 0000000..6e2d15f
--- /dev/null
+++ b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithm.java
@@ -0,0 +1,35 @@
+/*
+ * 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.flow.encryptor.command;
+
+/**
+ * Set Sensitive Properties Algorithm for NiFi Properties and update encrypted Flow Configuration
+ */
+public class SetSensitivePropertiesAlgorithm {
+
+    public static void main(final String[] arguments) {
+        if (arguments.length == 1) {
+            final String algorithm = arguments[0];
+            final FlowEncryptorCommand command = new FlowEncryptorCommand();
+            command.setRequestedPropertiesAlgorithm(algorithm);
+            command.run();
+        } else {
+            System.err.printf("Unexpected number of arguments [%d]%n", arguments.length);
+            System.err.printf("Usage: %s <algorithm>%n", SetSensitivePropertiesAlgorithm.class.getSimpleName());
+        }
+    }
+}
diff --git a/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java
index 1a4ea8f..65f3f28 100644
--- a/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java
+++ b/nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKey.java
@@ -16,174 +16,25 @@
  */
 package org.apache.nifi.flow.encryptor.command;
 
-import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
-import org.apache.nifi.flow.encryptor.FlowEncryptor;
-import org.apache.nifi.flow.encryptor.StandardFlowEncryptor;
-import org.apache.nifi.security.util.EncryptionMethod;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-import java.util.stream.Collectors;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
 /**
  * Set Sensitive Properties Key for NiFi Properties and update encrypted Flow Configuration
  */
 public class SetSensitivePropertiesKey {
-    protected static final String PROPERTIES_FILE_PATH = "nifi.properties.file.path";
-
-    protected static final String PROPS_KEY = "nifi.sensitive.props.key";
-
-    protected static final String PROPS_ALGORITHM = "nifi.sensitive.props.algorithm";
-
-    protected static final String CONFIGURATION_FILE = "nifi.flow.configuration.file";
-
-    protected static final String CONFIGURATION_JSON_FILE = "nifi.flow.configuration.json.file";
-
-    private static final List<String> CONFIGURATION_FILES = Arrays.asList(CONFIGURATION_FILE, CONFIGURATION_JSON_FILE);
-
     private static final int MINIMUM_REQUIRED_LENGTH = 12;
 
-    private static final String FLOW_PREFIX = "nifi.flow.";
-
-    private static final String GZ_EXTENSION = ".gz";
-
-    private static final String DEFAULT_PROPERTIES_ALGORITHM = EncryptionMethod.MD5_256AES.getAlgorithm();
-
-    private static final String DEFAULT_PROPERTIES_KEY = "nififtw!";
-
-    private static final String SENSITIVE_PROPERTIES_KEY = String.format("%s=", PROPS_KEY);
-
     public static void main(final String[] arguments) {
         if (arguments.length == 1) {
             final String outputPropertiesKey = arguments[0];
             if (outputPropertiesKey.length() < MINIMUM_REQUIRED_LENGTH) {
                 System.err.printf("Sensitive Properties Key length less than required [%d]%n", MINIMUM_REQUIRED_LENGTH);
             } else {
-                run(outputPropertiesKey);
+                final FlowEncryptorCommand command = new FlowEncryptorCommand();
+                command.setRequestedPropertiesKey(outputPropertiesKey);
+                command.run();
             }
         } else {
             System.err.printf("Unexpected number of arguments [%d]%n", arguments.length);
             System.err.printf("Usage: %s <sensitivePropertiesKey>%n", SetSensitivePropertiesKey.class.getSimpleName());
         }
     }
-
-    private static void run(final String outputPropertiesKey) {
-        final String propertiesFilePath = System.getProperty(PROPERTIES_FILE_PATH);
-        final File propertiesFile = new File(propertiesFilePath);
-        final Properties properties = loadProperties(propertiesFile);
-
-        try {
-            storeProperties(propertiesFile, outputPropertiesKey);
-            System.out.printf("NiFi Properties Processed [%s]%n", propertiesFilePath);
-        } catch (final IOException e) {
-            final String message = String.format("Failed to Process NiFi Properties [%s]", propertiesFilePath);
-            throw new UncheckedIOException(message, e);
-        }
-
-        processFlowConfigurationFiles(properties, outputPropertiesKey);
-    }
-
-    private static void processFlowConfigurationFiles(final Properties properties, final String outputPropertiesKey) {
-        final String algorithm = getAlgorithm(properties);
-        final PropertyEncryptor outputEncryptor = getPropertyEncryptor(outputPropertiesKey, algorithm);
-
-        for (final String configurationFilePropertyName : CONFIGURATION_FILES) {
-            final String configurationFileProperty = properties.getProperty(configurationFilePropertyName);
-            if (configurationFileProperty == null || configurationFileProperty.isEmpty()) {
-                System.out.printf("Flow Configuration Property not specified [%s]%n", configurationFileProperty);
-            } else {
-                final File configurationFile = new File(configurationFileProperty);
-                if (configurationFile.exists()) {
-                    processFlowConfiguration(configurationFile, properties, outputEncryptor);
-                }
-            }
-        }
-    }
-
-    private static void processFlowConfiguration(final File flowConfigurationFile, final Properties properties, final PropertyEncryptor outputEncryptor) {
-        try (final InputStream flowInputStream = new GZIPInputStream(new FileInputStream(flowConfigurationFile))) {
-            final File flowOutputFile = getFlowOutputFile();
-            final Path flowOutputPath = flowOutputFile.toPath();
-            try (final OutputStream flowOutputStream = new GZIPOutputStream(new FileOutputStream(flowOutputFile))) {
-                final String inputAlgorithm = getAlgorithm(properties);
-                final String inputPropertiesKey = getKey(properties);
-                final PropertyEncryptor inputEncryptor = getPropertyEncryptor(inputPropertiesKey, inputAlgorithm);
-
-                final FlowEncryptor flowEncryptor = new StandardFlowEncryptor();
-                flowEncryptor.processFlow(flowInputStream, flowOutputStream, inputEncryptor, outputEncryptor);
-            }
-
-            final Path flowConfigurationPath = flowConfigurationFile.toPath();
-            Files.move(flowOutputPath, flowConfigurationPath, StandardCopyOption.REPLACE_EXISTING);
-            System.out.printf("Flow Configuration Processed [%s]%n", flowConfigurationPath);
-        } catch (final IOException | RuntimeException e) {
-            System.err.printf("Failed to process Flow Configuration [%s]%n", flowConfigurationFile);
-            e.printStackTrace();
-        }
-    }
-
-    private static String getAlgorithm(final Properties properties) {
-        String algorithm = properties.getProperty(PROPS_ALGORITHM, DEFAULT_PROPERTIES_ALGORITHM);
-        if (algorithm.length() == 0) {
-            algorithm = DEFAULT_PROPERTIES_ALGORITHM;
-        }
-        return algorithm;
-    }
-
-    private static String getKey(final Properties properties) {
-        String key = properties.getProperty(PROPS_KEY, DEFAULT_PROPERTIES_KEY);
-        if (key.length() == 0) {
-            key = DEFAULT_PROPERTIES_KEY;
-        }
-        return key;
-    }
-
-    private static File getFlowOutputFile() throws IOException {
-        final File flowOutputFile = File.createTempFile(FLOW_PREFIX, GZ_EXTENSION);
-        flowOutputFile.deleteOnExit();
-        return flowOutputFile;
-    }
-
-    private static Properties loadProperties(final File propertiesFile) {
-        final Properties properties = new Properties();
-        try (final FileReader reader = new FileReader(propertiesFile)) {
-            properties.load(reader);
-        } catch (final IOException e) {
-            final String message = String.format("Failed to read NiFi Properties [%s]", propertiesFile);
-            throw new UncheckedIOException(message, e);
-        }
-        return properties;
-    }
-
-    private static void storeProperties(final File propertiesFile, final String propertiesKey) throws IOException {
-        final Path propertiesFilePath = propertiesFile.toPath();
-        final List<String> lines = Files.readAllLines(propertiesFilePath);
-        final List<String> updatedLines = lines.stream().map(line -> {
-            if (line.startsWith(SENSITIVE_PROPERTIES_KEY)) {
-                return SENSITIVE_PROPERTIES_KEY + propertiesKey;
-            } else {
-                return line;
-            }
-        }).collect(Collectors.toList());
-        Files.write(propertiesFilePath, updatedLines);
-    }
-
-    private static PropertyEncryptor getPropertyEncryptor(final String propertiesKey, final String propertiesAlgorithm) {
-        return new PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
-    }
 }
diff --git a/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommandTest.java
similarity index 52%
copy from nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java
copy to nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommandTest.java
index 5675bb9..494c1f1 100644
--- a/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java
+++ b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/FlowEncryptorCommandTest.java
@@ -36,9 +36,10 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 
 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 SetSensitivePropertiesKeyTest {
+public class FlowEncryptorCommandTest {
     private static final String TEMP_FILE_PREFIX = SetSensitivePropertiesKeyTest.class.getSimpleName();
 
     private static final String FLOW_CONTENTS_JSON = "{\"property\":\"value\"}";
@@ -57,69 +58,104 @@ public class SetSensitivePropertiesKeyTest {
 
     private static final String LEGACY_BLANK_PROPERTIES = "/legacy-blank.nifi.properties";
 
+    private static final String REQUESTED_ALGORITHM = "NIFI_PBKDF2_AES_GCM_256";
+
     @AfterEach
     public void clearProperties() {
-        System.clearProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH);
+        System.clearProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH);
     }
 
     @Test
-    public void testMainNoArguments() {
-        SetSensitivePropertiesKey.main(new String[]{});
+    public void testRunSystemPropertyNotDefined() {
+        final FlowEncryptorCommand command = new FlowEncryptorCommand();
+        assertThrows(IllegalStateException.class, command::run);
     }
 
     @Test
-    public void testMainBlankKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, flowConfigurationJson, BLANK_PROPERTIES);
+    public void testRunPropertiesKeyBlankProperties() throws IOException, URISyntaxException {
+        final Path propertiesPath = getBlankNiFiProperties();
+        System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
 
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
+        final FlowEncryptorCommand command = new FlowEncryptorCommand();
 
-        final String sensitivePropertiesKey = UUID.randomUUID().toString();
-        SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
+        final String propertiesKey = UUID.randomUUID().toString();
+        command.setRequestedPropertiesKey(propertiesKey);
+        command.run();
 
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
+        assertPropertiesKeyUpdated(propertiesPath, propertiesKey);
     }
 
     @Test
-    public void testMainPopulatedKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, flowConfigurationJson, POPULATED_PROPERTIES);
+    public void testRunPropertiesKeyLegacyPropertiesWithoutFlowJson() throws IOException, URISyntaxException {
+        final Path propertiesPath = getLegacyNiFiPropertiesWithoutFlowJson();
+        System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
 
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
+        final FlowEncryptorCommand command = new FlowEncryptorCommand();
 
-        final String sensitivePropertiesKey = UUID.randomUUID().toString();
-        SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
+        final String propertiesKey = UUID.randomUUID().toString();
+        command.setRequestedPropertiesKey(propertiesKey);
+        command.run();
 
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
+        assertPropertiesKeyUpdated(propertiesPath, propertiesKey);
     }
 
     @Test
-    public void testMainLegacyBlankKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, null, LEGACY_BLANK_PROPERTIES);
+    public void testRunPropertiesAlgorithmWithPropertiesKeyPopulatedProperties() throws IOException, URISyntaxException {
+        final Path propertiesPath = getPopulatedNiFiProperties();
+        System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
+
+        final FlowEncryptorCommand command = new FlowEncryptorCommand();
+
+        final String propertiesKey = UUID.randomUUID().toString();
+
+        command.setRequestedPropertiesAlgorithm(REQUESTED_ALGORITHM);
+        command.setRequestedPropertiesKey(propertiesKey);
+        command.run();
 
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
+        assertPropertiesKeyUpdated(propertiesPath, propertiesKey);
+        assertPropertiesAlgorithmUpdated(propertiesPath, REQUESTED_ALGORITHM);
+    }
 
-        final String sensitivePropertiesKey = UUID.randomUUID().toString();
-        SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
+    protected static void assertPropertiesAlgorithmUpdated(final Path propertiesPath, final String sensitivePropertiesAlgorithm) throws IOException {
+        final Optional<String> keyProperty = Files.readAllLines(propertiesPath)
+                .stream()
+                .filter(line -> line.startsWith(FlowEncryptorCommand.PROPS_ALGORITHM))
+                .findFirst();
+        assertTrue(keyProperty.isPresent(), "Sensitive Algorithm Property not found");
 
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
+        final String expectedProperty = String.format("%s=%s", FlowEncryptorCommand.PROPS_ALGORITHM, sensitivePropertiesAlgorithm);
+        assertEquals(expectedProperty, keyProperty.get(), "Sensitive Algorithm Property not updated");
     }
 
-    private void assertPropertiesKeyUpdated(final Path propertiesPath, final String sensitivePropertiesKey) throws IOException {
+    protected static void assertPropertiesKeyUpdated(final Path propertiesPath, final String sensitivePropertiesKey) throws IOException {
         final Optional<String> keyProperty = Files.readAllLines(propertiesPath)
                 .stream()
-                .filter(line -> line.startsWith(SetSensitivePropertiesKey.PROPS_KEY))
+                .filter(line -> line.startsWith(FlowEncryptorCommand.PROPS_KEY))
                 .findFirst();
         assertTrue(keyProperty.isPresent(), "Sensitive Key Property not found");
 
-        final String expectedProperty = String.format("%s=%s", SetSensitivePropertiesKey.PROPS_KEY, sensitivePropertiesKey);
+        final String expectedProperty = String.format("%s=%s", FlowEncryptorCommand.PROPS_KEY, sensitivePropertiesKey);
         assertEquals(expectedProperty, keyProperty.get(), "Sensitive Key Property not updated");
     }
 
-    private Path getNiFiProperties(
+    protected static Path getBlankNiFiProperties() throws IOException, URISyntaxException {
+        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
+        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
+        return getNiFiProperties(flowConfiguration, flowConfigurationJson, BLANK_PROPERTIES);
+    }
+
+    protected static Path getPopulatedNiFiProperties() throws IOException, URISyntaxException {
+        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
+        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
+        return getNiFiProperties(flowConfiguration, flowConfigurationJson, POPULATED_PROPERTIES);
+    }
+
+    protected static Path getLegacyNiFiPropertiesWithoutFlowJson() throws IOException, URISyntaxException {
+        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
+        return getNiFiProperties(flowConfiguration, null, LEGACY_BLANK_PROPERTIES);
+    }
+
+    private static Path getNiFiProperties(
             final Path flowConfigurationPath,
             final Path flowConfigurationJsonPath,
             String propertiesResource
@@ -127,9 +163,9 @@ public class SetSensitivePropertiesKeyTest {
         final Path sourcePropertiesPath = Paths.get(getResourceUrl(propertiesResource).toURI());
         final List<String> sourceProperties = Files.readAllLines(sourcePropertiesPath);
         final List<String> flowProperties = sourceProperties.stream().map(line -> {
-            if (line.startsWith(SetSensitivePropertiesKey.CONFIGURATION_FILE)) {
+            if (line.startsWith(FlowEncryptorCommand.CONFIGURATION_FILE)) {
                 return line + flowConfigurationPath;
-            } else if (line.startsWith(SetSensitivePropertiesKey.CONFIGURATION_JSON_FILE)) {
+            } else if (line.startsWith(FlowEncryptorCommand.CONFIGURATION_JSON_FILE)) {
                 return flowConfigurationJsonPath == null ? line : line + flowConfigurationJsonPath;
             } else {
                 return line;
@@ -142,7 +178,15 @@ public class SetSensitivePropertiesKeyTest {
         return propertiesPath;
     }
 
-    private Path getFlowConfiguration(final String contents, final String extension) throws IOException {
+    private static URL getResourceUrl(String resource) throws FileNotFoundException {
+        final URL resourceUrl = FlowEncryptorCommand.class.getResource(resource);
+        if (resourceUrl == null) {
+            throw new FileNotFoundException(String.format("Resource [%s] not found", resource));
+        }
+        return resourceUrl;
+    }
+
+    private static Path getFlowConfiguration(final String contents, final String extension) throws IOException {
         final Path flowConfigurationPath = Files.createTempFile(TEMP_FILE_PREFIX, extension);
         final File flowConfigurationFile = flowConfigurationPath.toFile();
         flowConfigurationFile.deleteOnExit();
@@ -152,12 +196,4 @@ public class SetSensitivePropertiesKeyTest {
         }
         return flowConfigurationPath;
     }
-
-    private URL getResourceUrl(String resource) throws FileNotFoundException {
-        final URL resourceUrl = SetSensitivePropertiesKey.class.getResource(resource);
-        if (resourceUrl == null) {
-            throw new FileNotFoundException(String.format("Resource [%s] not found", resource));
-        }
-        return resourceUrl;
-    }
 }
diff --git a/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithmTest.java b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithmTest.java
new file mode 100644
index 0000000..8c2eeeb
--- /dev/null
+++ b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesAlgorithmTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor.command;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+
+public class SetSensitivePropertiesAlgorithmTest {
+    private static final String REQUESTED_ALGORITHM = "NIFI_PBKDF2_AES_GCM_256";
+
+    @AfterEach
+    public void clearProperties() {
+        System.clearProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH);
+    }
+
+    @Test
+    public void testMainNoArguments() {
+        SetSensitivePropertiesAlgorithm.main(new String[]{});
+    }
+
+    @Test
+    public void testMainPopulatedKeyAndAlgorithm() throws IOException, URISyntaxException {
+        final Path propertiesPath = FlowEncryptorCommandTest.getPopulatedNiFiProperties();
+        System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
+
+        SetSensitivePropertiesAlgorithm.main(new String[]{REQUESTED_ALGORITHM});
+
+        FlowEncryptorCommandTest.assertPropertiesAlgorithmUpdated(propertiesPath, REQUESTED_ALGORITHM);
+    }
+}
diff --git a/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java
index 5675bb9..c2c89a0 100644
--- a/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java
+++ b/nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/command/SetSensitivePropertiesKeyTest.java
@@ -16,50 +16,19 @@
  */
 package org.apache.nifi.flow.encryptor.command;
 
-import org.apache.nifi.stream.io.GZIPOutputStream;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Optional;
 import java.util.UUID;
-import java.util.stream.Collectors;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class SetSensitivePropertiesKeyTest {
-    private static final String TEMP_FILE_PREFIX = SetSensitivePropertiesKeyTest.class.getSimpleName();
-
-    private static final String FLOW_CONTENTS_JSON = "{\"property\":\"value\"}";
-
-    private static final String FLOW_CONTENTS_XML = "<property><value>PROPERTY</value></property>";
-
-    private static final String JSON_GZ = ".json.gz";
-
-    private static final String XML_GZ = ".xml.gz";
-
-    private static final String PROPERTIES_EXTENSION = ".properties";
-
-    private static final String BLANK_PROPERTIES = "/blank.nifi.properties";
-
-    private static final String POPULATED_PROPERTIES = "/populated.nifi.properties";
-
-    private static final String LEGACY_BLANK_PROPERTIES = "/legacy-blank.nifi.properties";
 
     @AfterEach
     public void clearProperties() {
-        System.clearProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH);
+        System.clearProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH);
     }
 
     @Test
@@ -69,95 +38,12 @@ public class SetSensitivePropertiesKeyTest {
 
     @Test
     public void testMainBlankKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, flowConfigurationJson, BLANK_PROPERTIES);
-
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
+        final Path propertiesPath = FlowEncryptorCommandTest.getBlankNiFiProperties();
+        System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
 
         final String sensitivePropertiesKey = UUID.randomUUID().toString();
         SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
 
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
-    }
-
-    @Test
-    public void testMainPopulatedKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path flowConfigurationJson = getFlowConfiguration(FLOW_CONTENTS_JSON, JSON_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, flowConfigurationJson, POPULATED_PROPERTIES);
-
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
-
-        final String sensitivePropertiesKey = UUID.randomUUID().toString();
-        SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
-
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
-    }
-
-    @Test
-    public void testMainLegacyBlankKeyAndAlgorithm() throws IOException, URISyntaxException {
-        final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
-        final Path propertiesPath = getNiFiProperties(flowConfiguration, null, LEGACY_BLANK_PROPERTIES);
-
-        System.setProperty(SetSensitivePropertiesKey.PROPERTIES_FILE_PATH, propertiesPath.toString());
-
-        final String sensitivePropertiesKey = UUID.randomUUID().toString();
-        SetSensitivePropertiesKey.main(new String[]{sensitivePropertiesKey});
-
-        assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
-    }
-
-    private void assertPropertiesKeyUpdated(final Path propertiesPath, final String sensitivePropertiesKey) throws IOException {
-        final Optional<String> keyProperty = Files.readAllLines(propertiesPath)
-                .stream()
-                .filter(line -> line.startsWith(SetSensitivePropertiesKey.PROPS_KEY))
-                .findFirst();
-        assertTrue(keyProperty.isPresent(), "Sensitive Key Property not found");
-
-        final String expectedProperty = String.format("%s=%s", SetSensitivePropertiesKey.PROPS_KEY, sensitivePropertiesKey);
-        assertEquals(expectedProperty, keyProperty.get(), "Sensitive Key Property not updated");
-    }
-
-    private Path getNiFiProperties(
-            final Path flowConfigurationPath,
-            final Path flowConfigurationJsonPath,
-            String propertiesResource
-    ) throws IOException, URISyntaxException {
-        final Path sourcePropertiesPath = Paths.get(getResourceUrl(propertiesResource).toURI());
-        final List<String> sourceProperties = Files.readAllLines(sourcePropertiesPath);
-        final List<String> flowProperties = sourceProperties.stream().map(line -> {
-            if (line.startsWith(SetSensitivePropertiesKey.CONFIGURATION_FILE)) {
-                return line + flowConfigurationPath;
-            } else if (line.startsWith(SetSensitivePropertiesKey.CONFIGURATION_JSON_FILE)) {
-                return flowConfigurationJsonPath == null ? line : line + flowConfigurationJsonPath;
-            } else {
-                return line;
-            }
-        }).collect(Collectors.toList());
-
-        final Path propertiesPath = Files.createTempFile(TEMP_FILE_PREFIX, PROPERTIES_EXTENSION);
-        propertiesPath.toFile().deleteOnExit();
-        Files.write(propertiesPath, flowProperties);
-        return propertiesPath;
-    }
-
-    private Path getFlowConfiguration(final String contents, final String extension) throws IOException {
-        final Path flowConfigurationPath = Files.createTempFile(TEMP_FILE_PREFIX, extension);
-        final File flowConfigurationFile = flowConfigurationPath.toFile();
-        flowConfigurationFile.deleteOnExit();
-
-        try (final GZIPOutputStream outputStream = new GZIPOutputStream(new FileOutputStream(flowConfigurationFile))) {
-            outputStream.write(contents.getBytes(StandardCharsets.UTF_8));
-        }
-        return flowConfigurationPath;
-    }
-
-    private URL getResourceUrl(String resource) throws FileNotFoundException {
-        final URL resourceUrl = SetSensitivePropertiesKey.class.getResource(resource);
-        if (resourceUrl == null) {
-            throw new FileNotFoundException(String.format("Resource [%s] not found", resource));
-        }
-        return resourceUrl;
+        FlowEncryptorCommandTest.assertPropertiesKeyUpdated(propertiesPath, sensitivePropertiesKey);
     }
 }
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 793cc70..ba16a55 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1757,6 +1757,8 @@ These algorithms use a strong Key Derivation Function to derive a secret key of
 Each Key Derivation Function uses a static salt in order to support flow configuration comparison across cluster nodes.
 Each Key Derivation Function also uses default iteration and cost parameters as defined in the associated secure hashing implementation class.
 
+[[property-encryption-algorithms]]
+=== Property Encryption Algorithms
 The following strong encryption methods can be configured in the `nifi.sensitive.props.algorithm` property:
 
 * `NIFI_ARGON2_AES_GCM_128`
@@ -4332,6 +4334,23 @@ where:
 
 For more information see the <<toolkit-guide.adoc#encrypt_config_tool,Encrypt-Config Tool>> section in the NiFi Toolkit Guide.
 
+==== Updating the Sensitive Properties Algorithm
+
+The following command can be used to read an existing flow configuration and set a new sensitive properties algorithm in _nifi.properties_:
+
+```
+$ ./bin/nifi.sh set-sensitive-properties-algorithm <algorithm>
+```
+
+The command reads the following flow configuration file properties from _nifi.properties_:
+
+- `nifi.flow.configuration.file`
+- `nifi.flow.configuration.json.file`
+
+The command checks for the existence of each file and updates the sensitive property values found.
+
+See <<property-encryption-algorithms>> for supported values.
+
 ==== Updating the Sensitive Properties Key
 
 Starting with version 1.14.0, NiFi requires a value for `nifi.sensitive.props.key` in _nifi.properties_.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
index eae9ba6..d784a5b 100755
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
@@ -344,6 +344,16 @@ run() {
       run_nifi_cmd="exec ${run_nifi_cmd}"
     fi
 
+    if [ "$1" = "set-sensitive-properties-algorithm" ]; then
+        run_command="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' '-Dnifi.properties.file.path=${NIFI_HOME}/conf/nifi.properties' 'org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesAlgorithm'"
+        eval "cd ${NIFI_HOME}"
+        shift
+        eval "${run_command}" '"$@"'
+        EXIT_STATUS=$?
+        echo
+        return;
+    fi
+
     if [ "$1" = "set-sensitive-properties-key" ]; then
         run_command="'${JAVA}' -cp '${BOOTSTRAP_CLASSPATH}' '-Dnifi.properties.file.path=${NIFI_HOME}/conf/nifi.properties' 'org.apache.nifi.flow.encryptor.command.SetSensitivePropertiesKey'"
         eval "cd ${NIFI_HOME}"
@@ -460,7 +470,7 @@ case "$1" in
         install "$@"
         ;;
 
-    start|stop|decommission|run|status|is_loaded|dump|diagnostics|status-history|env|stateless|set-sensitive-properties-key|set-single-user-credentials)
+    start|stop|decommission|run|status|is_loaded|dump|diagnostics|status-history|env|stateless|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials)
         main "$@"
         ;;
 
@@ -470,6 +480,6 @@ case "$1" in
         run "start"
         ;;
     *)
-        echo "Usage nifi {start|stop|decommission|run|restart|status|dump|diagnostics|status-history|install|stateless|set-sensitive-properties-key|set-single-user-credentials}"
+        echo "Usage nifi {start|stop|decommission|run|restart|status|dump|diagnostics|status-history|install|stateless|set-sensitive-properties-algorithm|set-sensitive-properties-key|set-single-user-credentials}"
         ;;
 esac