NIFI-6617 Refactored Encrypted Repository configuration

- Updated documentation with new properties
- Refactored cipher operations to common RepositoryEncryptor classes
- Abstracted record metadata serialization for better compatibility

Signed-off-by: Joe Gresock <jgresock@gmail.com>

This closes #5407.
diff --git a/nifi-commons/nifi-data-provenance-utils/pom.xml b/nifi-commons/nifi-data-provenance-utils/pom.xml
index 0dcdcbb..05e43e8 100644
--- a/nifi-commons/nifi-data-provenance-utils/pom.xml
+++ b/nifi-commons/nifi-data-provenance-utils/pom.xml
@@ -37,14 +37,5 @@
             <artifactId>nifi-utils</artifactId>
             <version>1.15.0-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-security-utils</artifactId>
-            <version>1.15.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk15on</artifactId>
-        </dependency>
     </dependencies>
 </project>
diff --git a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/AESProvenanceEventEncryptor.java b/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/AESProvenanceEventEncryptor.java
deleted file mode 100644
index 27dc2fa..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/AESProvenanceEventEncryptor.java
+++ /dev/null
@@ -1,239 +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.provenance;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.security.KeyManagementException;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.SecretKey;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.util.EncryptionMethod;
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class AESProvenanceEventEncryptor implements ProvenanceEventEncryptor {
-    private static final Logger logger = LoggerFactory.getLogger(AESProvenanceEventEncryptor.class);
-    private static final String ALGORITHM = "AES/GCM/NoPadding";
-    private static final int IV_LENGTH = 16;
-    private static final byte[] EMPTY_IV = new byte[IV_LENGTH];
-    private static final String VERSION = "v1";
-    private static final List<String> SUPPORTED_VERSIONS = Arrays.asList(VERSION);
-    private static final int MIN_METADATA_LENGTH = IV_LENGTH + 3 + 3; // 3 delimiters and 3 non-zero elements
-    private static final int METADATA_DEFAULT_LENGTH = (20 + ALGORITHM.length() + IV_LENGTH + VERSION.length()) * 2; // Default to twice the expected length
-    private static final byte[] SENTINEL = new byte[]{0x01};
-
-    private KeyProvider keyProvider;
-
-    private AESKeyedCipherProvider aesKeyedCipherProvider = new AESKeyedCipherProvider();
-
-    /**
-     * Initializes the encryptor with a {@link KeyProvider}.
-     *
-     * @param keyProvider the key provider which will be responsible for accessing keys
-     * @throws KeyManagementException if there is an issue configuring the key provider
-     */
-    @Override
-    public void initialize(KeyProvider keyProvider) throws KeyManagementException {
-        this.keyProvider = keyProvider;
-
-        if (this.aesKeyedCipherProvider == null) {
-            this.aesKeyedCipherProvider = new AESKeyedCipherProvider();
-        }
-
-        if (Security.getProvider("BC") == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
-    }
-
-    /**
-     * Available for dependency injection to override the default {@link AESKeyedCipherProvider} if necessary.
-     *
-     * @param cipherProvider the AES cipher provider to use
-     */
-    void setCipherProvider(AESKeyedCipherProvider cipherProvider) {
-        this.aesKeyedCipherProvider = cipherProvider;
-    }
-
-    /**
-     * Encrypts the provided {@link ProvenanceEventRecord}, serialized to a byte[] by the RecordWriter.
-     *
-     * @param plainRecord the plain record, serialized to a byte[]
-     * @param recordId    an identifier for this record (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return the encrypted record
-     * @throws EncryptionException if there is an issue encrypting this record
-     */
-    @Override
-    public byte[] encrypt(byte[] plainRecord, String recordId, String keyId) throws EncryptionException {
-        if (plainRecord == null || CryptoUtils.isEmpty(keyId)) {
-            throw new EncryptionException("The provenance record and key ID cannot be missing");
-        }
-
-        if (keyProvider == null || !keyProvider.keyExists(keyId)) {
-            throw new EncryptionException("The requested key ID is not available");
-        } else {
-            byte[] ivBytes = new byte[IV_LENGTH];
-            new SecureRandom().nextBytes(ivBytes);
-            try {
-                logger.debug("Encrypting provenance record " + recordId + " with key ID " + keyId);
-                Cipher cipher = initCipher(EncryptionMethod.AES_GCM, Cipher.ENCRYPT_MODE, keyProvider.getKey(keyId), ivBytes);
-                ivBytes = cipher.getIV();
-
-                // Perform the actual encryption
-                byte[] cipherBytes = cipher.doFinal(plainRecord);
-
-                // Serialize and concat encryption details fields (keyId, algo, IV, version, CB length) outside of encryption
-                EncryptionMetadata metadata = new EncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION, cipherBytes.length);
-                byte[] serializedEncryptionMetadata = serializeEncryptionMetadata(metadata);
-
-                // Add the sentinel byte of 0x01
-                logger.debug("Encrypted provenance event record " + recordId + " with key ID " + keyId);
-                return CryptoUtils.concatByteArrays(SENTINEL, serializedEncryptionMetadata, cipherBytes);
-            } catch (EncryptionException | BadPaddingException | IllegalBlockSizeException | IOException | KeyManagementException e) {
-                final String msg = "Encountered an exception encrypting provenance record " + recordId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    private byte[] serializeEncryptionMetadata(EncryptionMetadata metadata) throws IOException {
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ObjectOutputStream outputStream = new ObjectOutputStream(baos);
-        outputStream.writeObject(metadata);
-        outputStream.close();
-        return baos.toByteArray();
-    }
-
-    private Cipher initCipher(EncryptionMethod method, int mode, SecretKey key, byte[] ivBytes) throws EncryptionException {
-        try {
-            if (method == null || key == null || ivBytes == null) {
-                throw new IllegalArgumentException("Missing critical information");
-            }
-            return aesKeyedCipherProvider.getCipher(method, key, ivBytes, mode == Cipher.ENCRYPT_MODE);
-        } catch (Exception e) {
-            logger.error("Encountered an exception initializing the cipher", e);
-            throw new EncryptionException(e);
-        }
-    }
-
-    /**
-     * Decrypts the provided byte[] (an encrypted record with accompanying metadata).
-     *
-     * @param encryptedRecord the encrypted record in byte[] form
-     * @param recordId        an identifier for this record (eventId, generated, etc.)
-     * @return the decrypted record
-     * @throws EncryptionException if there is an issue decrypting this record
-     */
-    @Override
-    public byte[] decrypt(byte[] encryptedRecord, String recordId) throws EncryptionException {
-        if (encryptedRecord == null) {
-            throw new EncryptionException("The encrypted provenance record cannot be missing");
-        }
-
-        EncryptionMetadata metadata;
-        try {
-            metadata = extractEncryptionMetadata(encryptedRecord);
-        } catch (IOException | ClassNotFoundException e) {
-            final String msg = "Encountered an error reading the encryption metadata: ";
-            logger.error(msg, e);
-            throw new EncryptionException(msg, e);
-        }
-
-        if (!SUPPORTED_VERSIONS.contains(metadata.version)) {
-            throw new EncryptionException("The event was encrypted with version " + metadata.version + " which is not in the list of supported versions " + StringUtils.join(SUPPORTED_VERSIONS, ","));
-        }
-
-        // TODO: Actually use the version to determine schema, etc.
-
-        if (keyProvider == null || !keyProvider.keyExists(metadata.keyId) || CryptoUtils.isEmpty(metadata.keyId)) {
-            throw new EncryptionException("The requested key ID " + metadata.keyId + " is not available");
-        } else {
-            try {
-                logger.debug("Decrypting provenance record " + recordId + " with key ID " + metadata.keyId);
-                EncryptionMethod method = EncryptionMethod.forAlgorithm(metadata.algorithm);
-                Cipher cipher = initCipher(method, Cipher.DECRYPT_MODE, keyProvider.getKey(metadata.keyId), metadata.ivBytes);
-
-                // Strip the metadata away to get just the cipher bytes
-                byte[] cipherBytes = extractCipherBytes(encryptedRecord, metadata);
-
-                // Perform the actual decryption
-                byte[] plainBytes = cipher.doFinal(cipherBytes);
-
-                logger.debug("Decrypted provenance event record " + recordId + " with key ID " + metadata.keyId);
-                return plainBytes;
-            } catch (EncryptionException | BadPaddingException | IllegalBlockSizeException | KeyManagementException e) {
-                final String msg = "Encountered an exception decrypting provenance record " + recordId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    @Override
-    public String getNextKeyId() throws KeyManagementException {
-        if (keyProvider != null) {
-            List<String> availableKeyIds = keyProvider.getAvailableKeyIds();
-            if (!availableKeyIds.isEmpty()) {
-                return availableKeyIds.get(0);
-            }
-        }
-        throw new KeyManagementException("No available key IDs");
-    }
-
-    private EncryptionMetadata extractEncryptionMetadata(byte[] encryptedRecord) throws EncryptionException, IOException, ClassNotFoundException {
-        if (encryptedRecord == null || encryptedRecord.length < MIN_METADATA_LENGTH) {
-            throw new EncryptionException("The encrypted record is too short to contain the metadata");
-        }
-
-        // Skip the first byte (SENTINEL) and don't need to copy all the serialized record
-        ByteArrayInputStream bais = new ByteArrayInputStream(encryptedRecord);
-        bais.read();
-        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
-            return (EncryptionMetadata) ois.readObject();
-        }
-    }
-
-    private byte[] extractCipherBytes(byte[] encryptedRecord, EncryptionMetadata metadata) {
-        return Arrays.copyOfRange(encryptedRecord, encryptedRecord.length - metadata.cipherByteLength, encryptedRecord.length);
-    }
-
-    @Override
-    public String toString() {
-        return "AES Provenance Event Encryptor with Key Provider: " + keyProvider.toString();
-    }
-}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionException.java b/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionException.java
deleted file mode 100644
index 05c52e5..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionException.java
+++ /dev/null
@@ -1,92 +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.provenance;
-
-public class EncryptionException extends Throwable {
-    /**
-     * Constructs a new EncryptionException 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 exception.
-     */
-    public EncryptionException() {
-        super();
-    }
-
-    /**
-     * Constructs a new EncryptionException 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 exception.
-     *
-     * @param message the detail message. The detail message is saved for
-     *                later retrieval by the {@link #getMessage()} method.
-     */
-    public EncryptionException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new EncryptionException 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 exception'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 EncryptionException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Constructs a new EncryptionException 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 exceptions that are little more than
-     * wrappers for other exceptions.
-     * <p>
-     * <p>The {@link #fillInStackTrace()} method is called to initialize
-     * the stack trace data in the newly created exception.
-     *
-     * @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 EncryptionException(Throwable cause) {
-        super(cause);
-    }
-
-    @Override
-    public String toString() {
-        return "EncryptionException " + getLocalizedMessage();
-    }
-}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionMetadata.java b/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionMetadata.java
deleted file mode 100644
index ffba9a5..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/EncryptionMetadata.java
+++ /dev/null
@@ -1,39 +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.provenance;
-
-import org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata;
-
-public class EncryptionMetadata extends RepositoryObjectEncryptionMetadata {
-    EncryptionMetadata() {
-    }
-
-    EncryptionMetadata(String keyId, String algorithm, byte[] ivBytes, String version, int cipherByteLength) {
-        this.keyId = keyId;
-        this.ivBytes = ivBytes;
-        this.algorithm = algorithm;
-        this.version = version;
-        this.cipherByteLength = cipherByteLength;
-    }
-
-    @Override
-    public String toString() {
-        String sb = "Provenance Record Encryption Metadata: " +
-                super.toString();
-        return sb;
-    }
-}
\ No newline at end of file
diff --git a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/ProvenanceEventEncryptor.java b/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/ProvenanceEventEncryptor.java
deleted file mode 100644
index 26df307..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/main/java/org/apache/nifi/provenance/ProvenanceEventEncryptor.java
+++ /dev/null
@@ -1,60 +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.provenance;
-
-import java.security.KeyManagementException;
-import org.apache.nifi.security.kms.KeyProvider;
-
-public interface ProvenanceEventEncryptor {
-
-    /**
-     * Initializes the encryptor with a {@link KeyProvider}.
-     *
-     * @param keyProvider the key provider which will be responsible for accessing keys
-     * @throws KeyManagementException if there is an issue configuring the key provider
-     */
-    void initialize(KeyProvider keyProvider) throws KeyManagementException;
-
-    /**
-     * Encrypts the provided {@link ProvenanceEventRecord}, serialized to a byte[] by the RecordWriter.
-     *
-     * @param plainRecord the plain record, serialized to a byte[]
-     * @param recordId    an identifier for this record (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return the encrypted record
-     * @throws EncryptionException if there is an issue encrypting this record
-     */
-    byte[] encrypt(byte[] plainRecord, String recordId, String keyId) throws EncryptionException;
-
-    /**
-     * Decrypts the provided byte[] (an encrypted record with accompanying metadata).
-     *
-     * @param encryptedRecord the encrypted record in byte[] form
-     * @param recordId        an identifier for this record (eventId, generated, etc.)
-     * @return the decrypted record
-     * @throws EncryptionException if there is an issue decrypting this record
-     */
-    byte[] decrypt(byte[] encryptedRecord, String recordId) throws EncryptionException;
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    String getNextKeyId() throws KeyManagementException;
-}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/groovy/org/apache/nifi/provenance/AESProvenanceEventEncryptorTest.groovy b/nifi-commons/nifi-data-provenance-utils/src/test/groovy/org/apache/nifi/provenance/AESProvenanceEventEncryptorTest.groovy
deleted file mode 100644
index 2916de1..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/test/groovy/org/apache/nifi/provenance/AESProvenanceEventEncryptorTest.groovy
+++ /dev/null
@@ -1,305 +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.provenance
-
-import org.apache.nifi.security.kms.CryptoUtils
-import org.apache.nifi.security.kms.KeyProvider
-import org.apache.nifi.security.util.EncryptionMethod
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-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 javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.KeyManagementException
-import java.security.SecureRandom
-import java.security.Security
-
-import static groovy.test.GroovyAssert.shouldFail
-
-@RunWith(JUnit4.class)
-class AESProvenanceEventEncryptorTest {
-    private static final Logger logger = LoggerFactory.getLogger(AESProvenanceEventEncryptorTest.class)
-
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static KeyProvider mockKeyProvider
-    private static AESKeyedCipherProvider mockCipherProvider
-
-    private static String ORIGINAL_LOG_LEVEL
-
-    private ProvenanceEventEncryptor encryptor
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        ORIGINAL_LOG_LEVEL = System.getProperty("org.slf4j.simpleLogger.log.org.apache.nifi.provenance")
-        System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi.provenance", "DEBUG")
-
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-
-        mockKeyProvider = [
-                getKey   : { String keyId ->
-                    logger.mock("Requesting key ID: ${keyId}")
-                    new SecretKeySpec(Hex.decode(KEY_HEX), "AES")
-                },
-                keyExists: { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    true
-                }] as KeyProvider
-
-        mockCipherProvider = [
-                getCipher: { EncryptionMethod em, SecretKey key, byte[] ivBytes, boolean encryptMode ->
-                    logger.mock("Getting cipher for ${em} with IV ${Hex.toHexString(ivBytes)} encrypt ${encryptMode}")
-                    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding")
-                    cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, key, new IvParameterSpec(ivBytes))
-                    cipher
-                }
-        ] as AESKeyedCipherProvider
-    }
-
-    @Before
-    void setUp() throws Exception {
-
-    }
-
-    @After
-    void tearDown() throws Exception {
-
-    }
-
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi.provenance", ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    /**
-     * Given arbitrary bytes, encrypt them and persist with the encryption metadata, then recover
-     */
-    @Test
-    void testShouldEncryptAndDecryptArbitraryBytes() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        encryptor = new AESProvenanceEventEncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-
-        // Act
-        byte[] metadataAndCipherBytes = encryptor.encrypt(SERIALIZED_BYTES, recordId, keyId)
-        logger.info("Encrypted data to: \n\t${Hex.toHexString(metadataAndCipherBytes)}")
-
-        byte[] recoveredBytes = encryptor.decrypt(metadataAndCipherBytes, recordId)
-        logger.info("Decrypted data to: \n\t${Hex.toHexString(recoveredBytes)}")
-
-        // Assert
-        assert recoveredBytes == SERIALIZED_BYTES
-        logger.info("Decoded (usually would be serialized schema record): ${new String(recoveredBytes, StandardCharsets.UTF_8)}")
-    }
-
-    @Test
-    void testShouldInitializeNullCipherProvider() {
-        // Arrange
-        encryptor = new AESProvenanceEventEncryptor()
-        encryptor.setCipherProvider(null)
-        assert !encryptor.aesKeyedCipherProvider
-        
-        // Act
-        encryptor.initialize(mockKeyProvider)
-        logger.info("Created ${encryptor}")
-
-        // Assert
-        assert encryptor.aesKeyedCipherProvider instanceof AESKeyedCipherProvider
-    }
-
-    @Test
-    void testShouldFailOnMissingKeyId() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        KeyProvider emptyKeyProvider = [
-                getKey   : { String kid ->
-                    throw new KeyManagementException("No key found for ${kid}")
-                },
-                keyExists: { String kid -> false }
-        ] as KeyProvider
-
-        encryptor = new AESProvenanceEventEncryptor()
-        encryptor.initialize(emptyKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-
-        // Act
-        def msg = shouldFail(EncryptionException) {
-            byte[] metadataAndCipherBytes = encryptor.encrypt(SERIALIZED_BYTES, recordId, keyId)
-            logger.info("Encrypted data to: \n\t${Hex.toHexString(metadataAndCipherBytes)}")
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg.getMessage() == "The requested key ID is not available"
-    }
-
-    @Test
-    void testShouldUseDifferentIVsForSequentialEncryptions() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        encryptor = new AESProvenanceEventEncryptor()
-        encryptor.initialize(mockKeyProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId1 = "R1"
-        logger.info("Using record ID ${recordId1} and key ID ${keyId}")
-
-        // Act
-        byte[] metadataAndCipherBytes1 = encryptor.encrypt(SERIALIZED_BYTES, recordId1, keyId)
-        logger.info("Encrypted data to: \n\t${Hex.toHexString(metadataAndCipherBytes1)}")
-        EncryptionMetadata metadata1 = encryptor.extractEncryptionMetadata(metadataAndCipherBytes1)
-        logger.info("Record ${recordId1} IV: ${Hex.toHexString(metadata1.ivBytes)}")
-
-        byte[] recoveredBytes1 = encryptor.decrypt(metadataAndCipherBytes1, recordId1)
-        logger.info("Decrypted data to: \n\t${Hex.toHexString(recoveredBytes1)}")
-
-        String recordId2 = "R2"
-        byte[] metadataAndCipherBytes2 = encryptor.encrypt(SERIALIZED_BYTES, recordId2, keyId)
-        logger.info("Encrypted data to: \n\t${Hex.toHexString(metadataAndCipherBytes2)}")
-        EncryptionMetadata metadata2 = encryptor.extractEncryptionMetadata(metadataAndCipherBytes2)
-        logger.info("Record ${recordId2} IV: ${Hex.toHexString(metadata2.ivBytes)}")
-
-        byte[] recoveredBytes2 = encryptor.decrypt(metadataAndCipherBytes2, recordId2)
-        logger.info("Decrypted data to: \n\t${Hex.toHexString(recoveredBytes2)}")
-
-        // Assert
-        assert metadata1.ivBytes != metadata2.ivBytes
-
-        assert recoveredBytes1 == SERIALIZED_BYTES
-        assert recoveredBytes2 == SERIALIZED_BYTES
-    }
-
-    @Test
-    void testShouldFailOnBadMetadata() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        def strictMockKeyProvider = [
-                getKey   : { String keyId ->
-                    if (keyId != "K1") {
-                        throw new KeyManagementException("No such key")
-                    }
-                    new SecretKeySpec(Hex.decode(KEY_HEX), "AES")
-                },
-                keyExists: { String keyId ->
-                    keyId == "K1"
-                }] as KeyProvider
-
-        encryptor = new AESProvenanceEventEncryptor()
-        encryptor.initialize(strictMockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-
-        final String ALGORITHM = "AES/GCM/NoPadding"
-        final byte[] ivBytes = new byte[16]
-        new SecureRandom().nextBytes(ivBytes)
-        final String VERSION = "v1"
-
-        // Perform the encryption independently of the encryptor
-        SecretKey key = mockKeyProvider.getKey(keyId)
-        Cipher cipher = new AESKeyedCipherProvider().getCipher(EncryptionMethod.AES_GCM, key, ivBytes, true)
-        byte[] cipherBytes = cipher.doFinal(SERIALIZED_BYTES)
-
-        int cipherBytesLength = cipherBytes.size()
-
-        // Construct accurate metadata
-        EncryptionMetadata goodMetadata = new EncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION, cipherBytesLength)
-        logger.info("Created good encryption metadata: ${goodMetadata}")
-
-        // Construct bad metadata instances
-        EncryptionMetadata badKeyId = new EncryptionMetadata(keyId.reverse(), ALGORITHM, ivBytes, VERSION, cipherBytesLength)
-        EncryptionMetadata badAlgorithm = new EncryptionMetadata(keyId, "ASE/GDM/SomePadding", ivBytes, VERSION, cipherBytesLength)
-        EncryptionMetadata badIV = new EncryptionMetadata(keyId, ALGORITHM, new byte[16], VERSION, cipherBytesLength)
-        EncryptionMetadata badVersion = new EncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION.reverse(), cipherBytesLength)
-        EncryptionMetadata badCBLength = new EncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION, cipherBytesLength - 5)
-
-        List badMetadata = [badKeyId, badAlgorithm, badIV, badVersion, badCBLength]
-
-        // Form the proper cipherBytes
-        byte[] completeGoodBytes = CryptoUtils.concatByteArrays([0x01] as byte[], encryptor.serializeEncryptionMetadata(goodMetadata), cipherBytes)
-
-        byte[] recoveredGoodBytes = encryptor.decrypt(completeGoodBytes, recordId)
-        logger.info("Recovered good bytes: ${Hex.toHexString(recoveredGoodBytes)}")
-
-        final List EXPECTED_MESSAGES = ["The requested key ID (\\w+)? is not available",
-                                        "Encountered an exception decrypting provenance record",
-                                        "The event was encrypted with version ${VERSION.reverse()} which is not in the list of supported versions v1"]
-
-        // Act
-        badMetadata.eachWithIndex { EncryptionMetadata metadata, int i ->
-            byte[] completeBytes = CryptoUtils.concatByteArrays([0x01] as byte[], encryptor.serializeEncryptionMetadata(metadata), cipherBytes)
-
-            def msg = shouldFail(EncryptionException) {
-                byte[] recoveredBytes = encryptor.decrypt(completeBytes, "R${i + 2}")
-                logger.info("Recovered bad bytes: ${Hex.toHexString(recoveredBytes)}")
-            }
-            logger.expected(msg)
-
-            // Assert
-            assert EXPECTED_MESSAGES.any { msg.getMessage() =~ it }
-        }
-    }
-}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/resources/logback-test.xml b/nifi-commons/nifi-data-provenance-utils/src/test/resources/logback-test.xml
deleted file mode 100644
index c10508d..0000000
--- a/nifi-commons/nifi-data-provenance-utils/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-      http://www.apache.org/licenses/LICENSE-2.0
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-
-<configuration scan="true" scanPeriod="30 seconds">
-    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>%-4r [%t] %-5p %c - %m%n</pattern>
-        </encoder>
-    </appender>
-
-    <!-- valid logging levels: TRACE, DEBUG, INFO, WARN, ERROR -->
-    <logger name="org.apache.nifi" level="DEBUG"/>
-
-    <root level="INFO">
-        <appender-ref ref="CONSOLE"/>
-    </root>
-
-</configuration>
-
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 1abf4e4..9ace2aa 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -35,6 +35,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -87,6 +88,13 @@
     public static final String BACKPRESSURE_COUNT = "nifi.queue.backpressure.count";
     public static final String BACKPRESSURE_SIZE = "nifi.queue.backpressure.size";
 
+    // Encryption Properties for all Repositories
+    public static final String REPOSITORY_ENCRYPTION_PROTOCOL_VERSION = "nifi.repository.encryption.protocol.version";
+    public static final String REPOSITORY_ENCRYPTION_KEY_ID = "nifi.repository.encryption.key.id";
+    public static final String REPOSITORY_ENCRYPTION_KEY_PROVIDER = "nifi.repository.encryption.key.provider";
+    public static final String REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_LOCATION = "nifi.repository.encryption.key.provider.keystore.location";
+    public static final String REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_PASSWORD = "nifi.repository.encryption.key.provider.keystore.password";
+
     // content repository properties
     public static final String REPOSITORY_CONTENT_PREFIX = "nifi.content.repository.directory.";
     public static final String CONTENT_REPOSITORY_IMPLEMENTATION = "nifi.content.repository.implementation";
@@ -1641,7 +1649,7 @@
     }
 
     public String getFlowFileRepoEncryptionKeyId() {
-        return getProperty(FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID);
+        return getProperty(FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID, getProperty(REPOSITORY_ENCRYPTION_KEY_ID));
     }
 
     /**
@@ -1677,33 +1685,31 @@
      * @param repositoryType "provenance", "content", or "flowfile"
      * @return the key map
      */
-    private Map<String, String> getRepositoryEncryptionKeys(String repositoryType) {
-        Map<String, String> keys = new HashMap<>();
-        List<String> keyProperties = getRepositoryEncryptionKeyProperties(repositoryType);
+    public Map<String, String> getRepositoryEncryptionKeys(final String repositoryType) {
+        Objects.requireNonNull(repositoryType, "Repository Type required");
+        final Map<String, String> keys = new HashMap<>();
+        final List<String> keyProperties = getRepositoryEncryptionKeyProperties(repositoryType);
         if (keyProperties.size() == 0) {
-            logger.warn("No " + repositoryType + " repository encryption key properties were available. Check the "
-                    + "exact format specified in the Admin Guide - Encrypted " + StringUtils.toTitleCase(repositoryType)
-                    + " Repository Properties");
+            logger.warn("Repository [{}] Encryption Key properties not found", repositoryType);
             return keys;
         }
-        final String REPOSITORY_ENCRYPTION_KEY = getRepositoryEncryptionKey(repositoryType);
-        final String REPOSITORY_ENCRYPTION_KEY_ID = getRepositoryEncryptionKeyId(repositoryType);
+        final String repositoryEncryptionKey = getRepositoryEncryptionKey(repositoryType);
+        final String repositoryEncryptionKeyId = getRepositoryEncryptionKeyId(repositoryType);
 
         // Retrieve the actual key values and store non-empty values in the map
-        for (String prop : keyProperties) {
-            logger.debug("Parsing " + prop);
-            final String value = getProperty(prop);
-            if (!StringUtils.isBlank(value)) {
+        for (final String keyProperty : keyProperties) {
+            final String keyValue = getProperty(keyProperty);
+            if (StringUtils.isNotBlank(keyValue)) {
                 // If this property is .key (the actual hex key), put it in the map under the value of .key.id (i.e. key1)
-                if (prop.equalsIgnoreCase(REPOSITORY_ENCRYPTION_KEY)) {
-                    keys.put(getProperty(REPOSITORY_ENCRYPTION_KEY_ID), value);
+                if (keyProperty.equalsIgnoreCase(repositoryEncryptionKey)) {
+                    keys.put(getProperty(repositoryEncryptionKeyId), keyValue);
                 } else {
                     // Extract nifi.*.repository.encryption.key.id.key1 -> key1
-                    String extractedKeyId = prop.substring(prop.lastIndexOf(".") + 1);
+                    final String extractedKeyId = keyProperty.substring(keyProperty.lastIndexOf(".") + 1);
                     if (keys.containsKey(extractedKeyId)) {
-                        logger.warn("The {} repository encryption key map already contains an entry for {}. Ignoring new value from {}", repositoryType, extractedKeyId, prop);
+                        logger.warn("Repository [{}] Duplicate Encryption Key ID [{}]: Ignoring Property [{}]", repositoryType, extractedKeyId, keyProperty);
                     } else {
-                        keys.put(extractedKeyId, value);
+                        keys.put(extractedKeyId, keyValue);
                     }
                 }
             }
@@ -1772,7 +1778,7 @@
     }
 
     public String getProvenanceRepoEncryptionKeyId() {
-        return getProperty(PROVENANCE_REPO_ENCRYPTION_KEY_ID);
+        return getProperty(PROVENANCE_REPO_ENCRYPTION_KEY_ID, getProperty(REPOSITORY_ENCRYPTION_KEY_ID));
     }
 
     /**
@@ -1803,7 +1809,7 @@
     }
 
     public String getContentRepositoryEncryptionKeyId() {
-        return getProperty(CONTENT_REPOSITORY_ENCRYPTION_KEY_ID);
+        return getProperty(CONTENT_REPOSITORY_ENCRYPTION_KEY_ID, getProperty(REPOSITORY_ENCRYPTION_KEY_ID));
     }
 
     /**
diff --git a/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/CipherPropertyEncryptor.java b/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/CipherPropertyEncryptor.java
index 69dbb16..6dd17ff 100644
--- a/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/CipherPropertyEncryptor.java
+++ b/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/CipherPropertyEncryptor.java
@@ -18,8 +18,8 @@
 
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
-import org.apache.nifi.security.kms.CryptoUtils;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -52,7 +52,7 @@
         final Cipher cipher = getEncryptionCipher(encodedParameters);
         try {
             final byte[] encrypted = cipher.doFinal(binary);
-            return Hex.encodeHexString(CryptoUtils.concatByteArrays(encodedParameters, encrypted));
+            return Hex.encodeHexString(Arrays.concatenate(encodedParameters, encrypted));
         } catch (final BadPaddingException | IllegalBlockSizeException e) {
             final String message = String.format("Encryption Failed with Algorithm [%s]", cipher.getAlgorithm());
             throw new EncryptionException(message, e);
diff --git a/nifi-commons/nifi-repository-encryption/pom.xml b/nifi-commons/nifi-repository-encryption/pom.xml
new file mode 100644
index 0000000..e281f81
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/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.15.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-repository-encryption</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-kms</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-utils</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-properties</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesCtrStreamRepositoryEncryptor.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesCtrStreamRepositoryEncryptor.java
new file mode 100644
index 0000000..ba79b78
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesCtrStreamRepositoryEncryptor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.repository.encryption;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+import org.apache.nifi.security.kms.KeyProvider;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Repository Encryptor implementation using AES-CTR for encrypting and decrypting streams
+ */
+public class AesCtrStreamRepositoryEncryptor extends AesSecretKeyRepositoryEncryptor<OutputStream, InputStream> {
+    /**
+     * AES-CTR Stream Repository Encryptor with required Key Provider
+     *
+     * @param keyProvider Key Provider
+     */
+    public AesCtrStreamRepositoryEncryptor(final KeyProvider keyProvider, final EncryptionMetadataHeader encryptionMetadataHeader) {
+        super(RepositoryEncryptionMethod.AES_CTR, keyProvider, encryptionMetadataHeader);
+    }
+
+    /**
+     * Decrypt Input Stream using metadata read from start of stream
+     *
+     * @param record Input Stream to be decrypted
+     * @param recordId Record Identifier
+     * @return Decrypted Cipher Input Stream
+     */
+    @Override
+    public InputStream decrypt(final InputStream record, final String recordId) {
+        final RecordMetadata metadata = readMetadata(record);
+        final Cipher cipher = getDecryptionCipher(metadata.getKeyId(), metadata.getInitializationVector());
+        return new CipherInputStream(record, cipher);
+    }
+
+    /**
+     * Encrypt Output Stream using Cipher writing serialized metadata prior to returning
+     *
+     * @param record Output Stream to be wrapper for encryption
+     * @param recordId Record Identifier
+     * @param keyId Key Identifier used for encryption
+     * @param cipher Cipher used for encryption
+     * @return Cipher Output Stream
+     */
+    @Override
+    protected OutputStream encrypt(final OutputStream record, final String recordId, final String keyId, final Cipher cipher) {
+        try {
+            final byte[] serializedMetadata = getMetadata(keyId, cipher.getIV());
+            record.write(serializedMetadata);
+            record.flush();
+            return new CipherOutputStream(record, cipher);
+        } catch (final IOException e) {
+            throw new RepositoryEncryptionException(String.format("Encryption Failed for Record ID [%s]", recordId), e);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptor.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptor.java
new file mode 100644
index 0000000..f12f3a6
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptor.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.bouncycastle.util.Arrays;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException;
+
+/**
+ * Repository Encryptor implementation using AES-GCM for encrypting and decrypting byte arrays
+ */
+public class AesGcmByteArrayRepositoryEncryptor extends AesSecretKeyRepositoryEncryptor<byte[], byte[]> {
+    /**
+     * AES-GCM Byte Array Repository Encryptor with required Key Provider
+     *
+     * @param keyProvider Key Provider
+     * @param encryptionMetadataHeader Encryption Metadata Header
+     */
+    public AesGcmByteArrayRepositoryEncryptor(final KeyProvider keyProvider, final EncryptionMetadataHeader encryptionMetadataHeader) {
+        super(RepositoryEncryptionMethod.AES_GCM, keyProvider, encryptionMetadataHeader);
+    }
+
+    /**
+     * Decrypt byte array record using metadata read from start of record
+     *
+     * @param record Record to be decrypted
+     * @param recordId Record Identifier
+     * @return Decrypted byte array record
+     */
+    @Override
+    public byte[] decrypt(final byte[] record, final String recordId) {
+        try {
+            final RecordMetadata metadata = readMetadata(new ByteArrayInputStream(record));
+            final Cipher cipher = getDecryptionCipher(metadata.getKeyId(), metadata.getInitializationVector());
+            final int cipherLength = metadata.getLength();
+            final int startIndex = record.length - cipherLength;
+            return cipher.doFinal(record, startIndex, cipherLength);
+        } catch (final GeneralSecurityException e) {
+            throw new RepositoryEncryptionException(String.format("Decryption Failed for Record ID [%s]", recordId), e);
+        }
+    }
+
+    /**
+     * Encrypt byte array record using provided Cipher writes serialized metadata and encrypted byte array
+     *
+     * @param record Record byte array to be encrypted
+     * @param recordId Record Identifier
+     * @param keyId Key Identifier used for encryption
+     * @param cipher Cipher used for encryption
+     * @return Byte array prefixed with serialized metadata followed by encrypted byte array
+     */
+    @Override
+    protected byte[] encrypt(final byte[] record, final String recordId, final String keyId, final Cipher cipher) {
+        try {
+            final byte[] encryptedRecord = cipher.doFinal(record);
+            final byte[] serializedMetadata = getMetadata(keyId, cipher.getIV(), encryptedRecord.length);
+            return Arrays.concatenate(serializedMetadata, encryptedRecord);
+        } catch (final GeneralSecurityException e) {
+            throw new RepositoryEncryptionException(String.format("Encryption Failed for Record ID [%s]", recordId), e);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesSecretKeyRepositoryEncryptor.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesSecretKeyRepositoryEncryptor.java
new file mode 100644
index 0000000..2bdc2b6
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/AesSecretKeyRepositoryEncryptor.java
@@ -0,0 +1,237 @@
+/*
+ * 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.repository.encryption;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.EncryptionProtocol;
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadataSerializer;
+import org.apache.nifi.repository.encryption.metadata.serialization.RecordMetadataObjectInputStream;
+import org.apache.nifi.repository.encryption.metadata.serialization.StandardRecordMetadataSerializer;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.apache.nifi.stream.io.NonCloseableInputStream;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Objects;
+
+/**
+ * AES Secret Key abstract implementation of Repository Encryptor with shared methods for key retrieval and cipher handling
+ *
+ * @param <I> Input Record Type for Encryption
+ * @param <O> Output Record Type for Decryption
+ */
+public abstract class AesSecretKeyRepositoryEncryptor<I, O> implements RepositoryEncryptor<I, O> {
+    private static final int INITIALIZATION_VECTOR_LENGTH = 16;
+
+    private static final int TAG_LENGTH = 128;
+
+    private static final int END_OF_STREAM = -1;
+
+    private static final int STREAM_LENGTH = -1;
+
+    private static final RecordMetadataSerializer RECORD_METADATA_SERIALIZER = new StandardRecordMetadataSerializer(EncryptionProtocol.VERSION_1);
+
+    private final SecureRandom secureRandom;
+
+    private final KeyProvider keyProvider;
+
+    private final EncryptionMetadataHeader encryptionMetadataHeader;
+
+    private final RepositoryEncryptionMethod repositoryEncryptionMethod;
+
+    AesSecretKeyRepositoryEncryptor(final RepositoryEncryptionMethod repositoryEncryptionMethod,
+                                    final KeyProvider keyProvider,
+                                    final EncryptionMetadataHeader encryptionMetadataHeader) {
+        this.repositoryEncryptionMethod = Objects.requireNonNull(repositoryEncryptionMethod, "Encryption Method required");
+        this.keyProvider = Objects.requireNonNull(keyProvider, "Key Provider required");
+        this.encryptionMetadataHeader = Objects.requireNonNull(encryptionMetadataHeader, "Encryption Metadata Header required");
+        this.secureRandom = new SecureRandom();
+    }
+
+    /**
+     * Encrypt record creates an encryption cipher for delegation to implementations
+     *
+     * @param record Record to be encrypted
+     * @param recordId Record Identifier
+     * @param keyId Key Identifier of key to be used for encryption
+     * @return Encrypted record
+     */
+    @Override
+    public I encrypt(final I record, final String recordId, final String keyId) {
+        Objects.requireNonNull(record, "Record required");
+        Objects.requireNonNull(recordId, "Record ID required");
+        Objects.requireNonNull(keyId, "Key ID required");
+
+        final Cipher cipher = getEncryptionCipher(keyId);
+        return encrypt(record, recordId, keyId, cipher);
+    }
+
+    /**
+     * Encrypt record using configured Cipher
+     *
+     * @param record Record to be encrypted
+     * @param recordId Record Identifier
+     * @param keyId Key Identifier of key to be used for encryption
+     * @param cipher Cipher to be used for encryption
+     * @return Encrypted record
+     */
+    protected abstract I encrypt(I record, String recordId, String keyId, Cipher cipher);
+
+    /**
+     * Get initialized Cipher for encryption using random initialization vector
+     *
+     * @param keyId Key Identifier for encryption
+     * @return Initialized Cipher for encryption
+     */
+    protected Cipher getEncryptionCipher(final String keyId) {
+        final byte[] initializationVector = getInitializationVector();
+        return getCipher(Cipher.ENCRYPT_MODE, keyId, initializationVector);
+    }
+
+    /**
+     * Get initialized Cipher for decryption using provided initialization vector
+     *
+     * @param keyId Key Identifier for decryption
+     * @param initializationVector Initialization Vector for Cipher
+     * @return Initialized Cipher for decryption
+     */
+    protected Cipher getDecryptionCipher(final String keyId, final byte[] initializationVector) {
+        return getCipher(Cipher.DECRYPT_MODE, keyId, initializationVector);
+    }
+
+    /**
+     * Get serialized Encryption Metadata using specified parameters with known encrypted length
+     *
+     * @param keyId Encryption Key Identifier
+     * @param initializationVector Initialization Vector for Cipher
+     * @param cipherLength Length of encrypted contents
+     * @return Serialized Repository Encryption Metadata
+     */
+    protected byte[] getMetadata(final String keyId, final byte[] initializationVector, final int cipherLength) {
+        return writeMetadata(keyId, initializationVector, cipherLength);
+    }
+
+    /**
+     * Get serialized Encryption Metadata for streaming implementations with unknown encrypted length
+     *
+     * @param keyId Encryption Key Identifier
+     * @param initializationVector Initialization Vector for Cipher
+     * @return Serialized Repository Encryption Metadata
+     */
+    protected byte[] getMetadata(final String keyId, final byte[] initializationVector) {
+        return writeMetadata(keyId, initializationVector, STREAM_LENGTH);
+    }
+
+    /**
+     * Read Encryption Metadata and header from InputStream using ObjectInputStream and avoid closing provided InputStream
+     *
+     * @param inputStream Input Stream containing metadata to be read
+     * @return Record Metadata
+     */
+    protected RecordMetadata readMetadata(final InputStream inputStream) {
+        final int headerLength = encryptionMetadataHeader.getLength();
+        try {
+            final int headerBytesRead = inputStream.read(new byte[headerLength]);
+            if (END_OF_STREAM == headerBytesRead) {
+                throw new RepositoryEncryptionException("End of InputStream while reading metadata header");
+            }
+        } catch (final IOException e) {
+            throw new RepositoryEncryptionException(String.format("Read Metadata Header bytes [%d] failed", headerLength), e);
+        }
+
+        try (final RecordMetadataObjectInputStream objectInputStream = new RecordMetadataObjectInputStream(new NonCloseableInputStream(inputStream))) {
+            return objectInputStream.getRecordMetadata();
+        } catch (final IOException e) {
+            throw new RepositoryEncryptionException("Read Encryption Metadata Failed", e);
+        }
+    }
+
+    /**
+     * Write Encryption Metadata with Header as a byte array using ObjectOutputStream
+     *
+     * @param keyId Encryption Key Identifier
+     * @param initializationVector Initialization Vector for Cipher
+     * @param cipherLength Length of encrypted contents
+     * @return Byte array containing serialized metadata
+     */
+    private byte[] writeMetadata(final String keyId, final byte[] initializationVector, final int cipherLength) {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            outputStream.write(encryptionMetadataHeader.getHeader());
+            final byte[] metadata = RECORD_METADATA_SERIALIZER.writeMetadata(keyId, initializationVector, cipherLength, repositoryEncryptionMethod);
+            outputStream.write(metadata);
+        } catch (final IOException e) {
+            throw new RepositoryEncryptionException("Write Encryption Metadata Failed", e);
+        }
+
+        return outputStream.toByteArray();
+    }
+
+    private byte[] getInitializationVector() {
+        final byte[] initializationVector = new byte[INITIALIZATION_VECTOR_LENGTH];
+        secureRandom.nextBytes(initializationVector);
+        return initializationVector;
+    }
+
+    private Cipher getCipher(final int mode, final String keyId, final byte[] initializationVector) {
+        final SecretKey secretKey = getSecretKey(keyId);
+        final String algorithm = repositoryEncryptionMethod.getAlgorithm();
+        try {
+            final Cipher cipher = Cipher.getInstance(algorithm);
+            final AlgorithmParameterSpec algorithmParameterSpec = getAlgorithmParametersSpec(initializationVector);
+            cipher.init(mode, secretKey, algorithmParameterSpec, secureRandom);
+            return cipher;
+        } catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
+            final String message = String.format("Cipher [%s] Mode [%d] Key ID [%s] configuration failed", algorithm, mode, keyId);
+            throw new RepositoryEncryptionException(message, e);
+        }
+    }
+
+    private AlgorithmParameterSpec getAlgorithmParametersSpec(final byte[] initializationVector) {
+        if (RepositoryEncryptionMethod.AES_GCM == repositoryEncryptionMethod) {
+            return new GCMParameterSpec(TAG_LENGTH, initializationVector);
+        } else {
+            return new IvParameterSpec(initializationVector);
+        }
+    }
+
+    private SecretKey getSecretKey(final String keyId) {
+        if (keyProvider.keyExists(keyId)) {
+            try {
+                return keyProvider.getKey(keyId);
+            } catch (final KeyManagementException e) {
+                throw new RepositoryEncryptionException(String.format("Key ID [%s] retrieval failed", keyId), e);
+            }
+        } else {
+            throw new RepositoryEncryptionException(String.format("Key ID [%s] not found", keyId));
+        }
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptionException.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptionException.java
new file mode 100644
index 0000000..050f489
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptionException.java
@@ -0,0 +1,41 @@
+/*
+ * 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.repository.encryption;
+
+/**
+ * Repository Encryption Exception for encryption processing failures
+ */
+public class RepositoryEncryptionException extends RuntimeException {
+    /**
+     * Repository Encryption Exception without associated cause
+     *
+     * @param message Exception message
+     */
+    public RepositoryEncryptionException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Repository Encryption Exception with associated cause
+     *
+     * @param message Exception message
+     * @param cause Throwable cause
+     */
+    public RepositoryEncryptionException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptor.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptor.java
new file mode 100644
index 0000000..5ae1e72
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/RepositoryEncryptor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.repository.encryption;
+
+/**
+ * Repository Encryptor abstracts encrypting and decrypting records
+ *
+ * @param <I> Input Record Type for Encryption
+ * @param <O> Output Record Type for Decryption
+ */
+public interface RepositoryEncryptor<I, O> {
+    /**
+     * Encrypt identified record using specified key
+     *
+     * @param record Record to be encrypted
+     * @param recordId Record Identifier
+     * @param keyId Key Identifier of key to be used for encryption
+     * @return Encrypted Record
+     */
+    I encrypt(I record, String recordId, String keyId);
+
+    /**
+     * Decrypt identified record
+     *
+     * @param record Record to be decrypted
+     * @param recordId Record Identifier
+     * @return Decrypted Record
+     */
+    O decrypt(O record, String recordId);
+}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptedRepositoryType.java
similarity index 75%
rename from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
rename to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptedRepositoryType.java
index e23d492..d5c8883 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptedRepositoryType.java
@@ -14,14 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.configuration;
 
-import org.junit.Test;
+/**
+ * Enumeration of supported Encrypted Repository Types
+ */
+public enum EncryptedRepositoryType {
+    CONTENT,
 
-public class EncryptionExceptionTest {
+    FLOWFILE,
 
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
-    }
+    PROVENANCE
 }
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionMetadataHeader.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionMetadataHeader.java
new file mode 100644
index 0000000..cd30bb2
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionMetadataHeader.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.configuration;
+
+/**
+ * Encryption Metadata Header indicator used when writing records
+ */
+public enum EncryptionMetadataHeader {
+    /**
+     * Content Repository Header
+     */
+    CONTENT(new byte[]{0x00, 0x00}),
+
+    /**
+     * Flow File Repository Header
+     */
+    FLOWFILE(new byte[]{}),
+
+    /**
+     * Provenance Repository Header
+     */
+    PROVENANCE(new byte[]{0x01});
+
+    private byte[] header;
+
+    EncryptionMetadataHeader(final byte[] header) {
+        this.header = header;
+    }
+
+    public byte[] getHeader() {
+        return header;
+    }
+
+    public int getLength() {
+        return header.length;
+    }
+}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/StreamingEncryptionMetadata.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionProtocol.java
similarity index 60%
rename from nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/StreamingEncryptionMetadata.java
rename to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionProtocol.java
index 0990097..278a82e 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/StreamingEncryptionMetadata.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/EncryptionProtocol.java
@@ -14,25 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.security.repository;
+package org.apache.nifi.repository.encryption.configuration;
 
-public class StreamingEncryptionMetadata extends RepositoryObjectEncryptionMetadata {
+/**
+ * Configuration options for Repository Encryption Protocol
+ */
+public enum EncryptionProtocol {
+    /** Repository Encryption Version 1 */
+    VERSION_1("v1", 1);
 
-    public StreamingEncryptionMetadata() {
+    private String version;
 
-    }
+    private int versionNumber;
 
-    public StreamingEncryptionMetadata(String keyId, String algorithm, byte[] ivBytes, String version) {
-        this.keyId = keyId;
-        this.ivBytes = ivBytes;
-        this.algorithm = algorithm;
+    EncryptionProtocol(final String version, final int versionNumber) {
         this.version = version;
-        this.cipherByteLength = -1;
+        this.versionNumber = versionNumber;
     }
 
-    @Override
-    public String toString() {
-        return "Streaming Encryption Metadata: " +
-                super.toString();
+    public String getVersion() {
+        return version;
+    }
+
+    public int getVersionNumber() {
+        return versionNumber;
     }
 }
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/RepositoryEncryptionMethod.java
similarity index 65%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/RepositoryEncryptionMethod.java
index e23d492..9054cc8 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/RepositoryEncryptionMethod.java
@@ -14,14 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.configuration;
 
-import org.junit.Test;
+/**
+ * Supported options for Repository Encryption
+ */
+public enum RepositoryEncryptionMethod {
+    AES_CTR("AES/CTR/NoPadding"),
 
-public class EncryptionExceptionTest {
+    AES_GCM("AES/GCM/NoPadding");
 
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
+    private final String algorithm;
+
+    RepositoryEncryptionMethod(final String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
     }
 }
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedConfigurationException.java
similarity index 65%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedConfigurationException.java
index e23d492..c351a79 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedConfigurationException.java
@@ -14,14 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.configuration.kms;
 
-import org.junit.Test;
+/**
+ * Runtime Exception for encryption configuration failures
+ */
+public class EncryptedConfigurationException extends RuntimeException {
+    public EncryptedConfigurationException(final String message) {
+        super(message);
+    }
 
-public class EncryptionExceptionTest {
-
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
+    public EncryptedConfigurationException(final String message, final Throwable cause) {
+        super(message, cause);
     }
 }
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedRepositoryProperty.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedRepositoryProperty.java
new file mode 100644
index 0000000..e2f7a83
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptedRepositoryProperty.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.configuration.kms;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+
+import java.util.Arrays;
+
+import static org.apache.nifi.util.NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
+import static org.apache.nifi.util.NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION;
+import static org.apache.nifi.util.NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD;
+import static org.apache.nifi.util.NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
+import static org.apache.nifi.util.NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION;
+import static org.apache.nifi.util.NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD;
+import static org.apache.nifi.util.NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
+import static org.apache.nifi.util.NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION;
+import static org.apache.nifi.util.NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD;
+
+/**
+ * Enumeration of configuration property names for encrypted repositories supporting backward compatibility
+ */
+enum EncryptedRepositoryProperty {
+    CONTENT(
+            EncryptedRepositoryType.CONTENT,
+            CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
+            CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION,
+            CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD
+    ),
+
+    FLOWFILE(
+            EncryptedRepositoryType.FLOWFILE,
+            FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
+            FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION,
+            FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD
+    ),
+
+    PROVENANCE(
+            EncryptedRepositoryType.PROVENANCE,
+            PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
+            PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION,
+            PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD
+    );
+
+    private EncryptedRepositoryType encryptedRepositoryType;
+
+    private String propertyType;
+
+    private String implementationClass;
+
+    private String location;
+
+    private String password;
+
+    EncryptedRepositoryProperty(final EncryptedRepositoryType encryptedRepositoryType,
+                                final String implementationClass,
+                                final String location,
+                                final String password) {
+        this.encryptedRepositoryType = encryptedRepositoryType;
+        this.propertyType = encryptedRepositoryType.toString().toLowerCase();
+        this.implementationClass = implementationClass;
+        this.location = location;
+        this.password = password;
+    }
+
+    /**
+     * Get Encrypted Repository Type
+     *
+     * @return Encrypted Repository Type
+     */
+    public EncryptedRepositoryType getEncryptedRepositoryType() {
+        return encryptedRepositoryType;
+    }
+
+    /**
+     * Get Property Type for resolving property names in NiFi Properties
+     *
+     * @return Property Type
+     */
+    public String getPropertyType() {
+        return propertyType;
+    }
+
+    /**
+     * Get implementation class property name
+     *
+     * @return Implementation class property name
+     */
+    public String getImplementationClass() {
+        return implementationClass;
+    }
+
+    /**
+     * Get location property name
+     *
+     * @return Location property name
+     */
+    public String getLocation() {
+        return location;
+    }
+
+    /**
+     * Get password property name
+     *
+     * @return Password property name
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Get Encrypted Repository Property from Encrypted Repository Type
+     *
+     * @param encryptedRepositoryType Encryption Repository Type
+     * @return Encrypted Repository Property
+     * @throws IllegalArgumentException Thrown when matching Encrypted Repository Type not found
+     */
+    public static EncryptedRepositoryProperty fromEncryptedRepositoryType(final EncryptedRepositoryType encryptedRepositoryType) {
+        return Arrays.stream(values())
+                .filter(value -> value.encryptedRepositoryType == encryptedRepositoryType)
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(encryptedRepositoryType.toString()));
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptionKeyProvider.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptionKeyProvider.java
new file mode 100644
index 0000000..f2018b5
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/EncryptionKeyProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.repository.encryption.configuration.kms;
+
+import org.apache.nifi.security.kms.FileBasedKeyProvider;
+import org.apache.nifi.security.kms.KeyStoreKeyProvider;
+import org.apache.nifi.security.kms.StaticKeyProvider;
+
+/**
+ * Configuration options for Repository Encryption Key Provider
+ */
+public enum EncryptionKeyProvider {
+    /** File Properties Key Provider */
+    FILE_PROPERTIES,
+
+    /** KeyStore Key Provider */
+    KEYSTORE,
+
+    /** NiFi Properties Key Provider */
+    NIFI_PROPERTIES;
+
+    /**
+     * Resolve Encryption Key Provider based on implementation class name ending with known names
+     *
+     * @param implementationClass Implementation class name
+     * @return Encryption Key Provider
+     * @throw IllegalArgumentException Thrown when implementation class name does not match a known class
+     */
+    public static EncryptionKeyProvider fromImplementationClass(final String implementationClass) {
+        EncryptionKeyProvider encryptionKeyProvider;
+
+        if (implementationClass.endsWith(FileBasedKeyProvider.class.getSimpleName())) {
+            encryptionKeyProvider = EncryptionKeyProvider.FILE_PROPERTIES;
+        } else if (implementationClass.endsWith(KeyStoreKeyProvider.class.getSimpleName())) {
+            encryptionKeyProvider = EncryptionKeyProvider.KEYSTORE;
+        } else if (implementationClass.endsWith(StaticKeyProvider.class.getSimpleName())) {
+            encryptionKeyProvider = EncryptionKeyProvider.NIFI_PROPERTIES;
+        } else {
+            final String message = String.format("Key Provider Class [%s] not supported", implementationClass);
+            throw new IllegalArgumentException(message);
+        }
+
+        return encryptionKeyProvider;
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/RepositoryKeyProviderFactory.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/RepositoryKeyProviderFactory.java
new file mode 100644
index 0000000..e8069b1
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/RepositoryKeyProviderFactory.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.repository.encryption.configuration.kms;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.apache.nifi.util.NiFiProperties;
+
+/**
+ * Key Provider Factory for Encrypted Repositories
+ */
+public interface RepositoryKeyProviderFactory {
+    /**
+     * Get Key Provider for specified Encrypted Repository Type using available NiFi Properties
+     *
+     * @param encryptedRepositoryType Encrypted Repository Type
+     * @param niFiProperties NiFi Properties
+     * @return Key Provider configured using applicable properties
+     */
+    KeyProvider getKeyProvider(EncryptedRepositoryType encryptedRepositoryType, NiFiProperties niFiProperties);
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactory.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactory.java
new file mode 100644
index 0000000..4e66894
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactory.java
@@ -0,0 +1,148 @@
+/*
+ * 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.repository.encryption.configuration.kms;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.apache.nifi.security.kms.KeyProviderFactory;
+import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
+import org.apache.nifi.security.util.KeyStoreUtils;
+import org.apache.nifi.security.util.KeystoreType;
+import org.apache.nifi.security.util.TlsException;
+import org.apache.nifi.util.NiFiBootstrapUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.StringUtils;
+import org.bouncycastle.util.encoders.DecoderException;
+import org.bouncycastle.util.encoders.Hex;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.nifi.util.NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER;
+import static org.apache.nifi.util.NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_LOCATION;
+import static org.apache.nifi.util.NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_PASSWORD;
+
+/**
+ * Standard implementation of Repository Key Provider Factory supporting shared and fallback properties
+ */
+public class StandardRepositoryKeyProviderFactory implements RepositoryKeyProviderFactory {
+    private static final String ROOT_KEY_ALGORITHM = "AES";
+
+    /**
+     * Get Key Provider for specified Encrypted Repository Type using shared and fallback NiFi Properties
+     *
+     * @param encryptedRepositoryType Encrypted Repository Type
+     * @param niFiProperties NiFi Properties
+     * @return Key Provider configured using applicable properties
+     */
+    @Override
+    public KeyProvider getKeyProvider(final EncryptedRepositoryType encryptedRepositoryType, final NiFiProperties niFiProperties) {
+        Objects.requireNonNull(encryptedRepositoryType, "Encrypted Repository Type required");
+        Objects.requireNonNull(niFiProperties, "NiFi Properties required");
+        final EncryptedRepositoryProperty encryptedRepositoryProperty = EncryptedRepositoryProperty.fromEncryptedRepositoryType(encryptedRepositoryType);
+        final EncryptionKeyProvider encryptionKeyProvider = getEncryptionKeyProvider(encryptedRepositoryProperty, niFiProperties);
+        final KeyProviderConfiguration<?> keyProviderConfiguration = getKeyProviderConfiguration(encryptedRepositoryProperty, encryptionKeyProvider, niFiProperties);
+        return KeyProviderFactory.getKeyProvider(keyProviderConfiguration);
+    }
+
+    private EncryptionKeyProvider getEncryptionKeyProvider(final EncryptedRepositoryProperty encryptedRepositoryProperty, final NiFiProperties niFiProperties) {
+        EncryptionKeyProvider encryptionKeyProvider;
+        final String sharedKeyProvider = niFiProperties.getProperty(REPOSITORY_ENCRYPTION_KEY_PROVIDER);
+
+        if (StringUtils.isBlank(sharedKeyProvider)) {
+            final String classProperty = encryptedRepositoryProperty.getImplementationClass();
+            final String implementationClass = niFiProperties.getProperty(classProperty);
+            if (StringUtils.isBlank(implementationClass)) {
+                final String message = String.format("Key Provider Property [%s] not configured", classProperty);
+                throw new EncryptedConfigurationException(message);
+            } else {
+                encryptionKeyProvider = EncryptionKeyProvider.fromImplementationClass(implementationClass);
+            }
+        } else {
+            try {
+                encryptionKeyProvider = EncryptionKeyProvider.valueOf(sharedKeyProvider);
+            } catch (final IllegalArgumentException e) {
+                final EncryptedRepositoryType encryptedRepositoryType = encryptedRepositoryProperty.getEncryptedRepositoryType();
+                final String message = String.format("Key Provider [%s] not supported for Repository Type [%s] ", sharedKeyProvider, encryptedRepositoryType);
+                throw new EncryptedConfigurationException(message);
+            }
+        }
+
+        if (encryptionKeyProvider == null) {
+            final EncryptedRepositoryType encryptedRepositoryType = encryptedRepositoryProperty.getEncryptedRepositoryType();
+            final String message = String.format("Key Provider [%s] not found for Repository Type [%s] ", sharedKeyProvider, encryptedRepositoryType);
+            throw new EncryptedConfigurationException(message);
+        }
+
+        return encryptionKeyProvider;
+    }
+
+    private KeyProviderConfiguration<?> getKeyProviderConfiguration(final EncryptedRepositoryProperty encryptedRepositoryProperty,
+                                                                    final EncryptionKeyProvider encryptionKeyProvider,
+                                                                    final NiFiProperties niFiProperties) {
+        if (EncryptionKeyProvider.NIFI_PROPERTIES == encryptionKeyProvider) {
+            final Map<String, String> encryptionKeys = niFiProperties.getRepositoryEncryptionKeys(encryptedRepositoryProperty.getPropertyType());
+            return new StaticKeyProviderConfiguration(encryptionKeys);
+        } else if (EncryptionKeyProvider.FILE_PROPERTIES == encryptionKeyProvider) {
+            final SecretKey rootKey = getRootKey();
+            final String location = niFiProperties.getProperty(encryptedRepositoryProperty.getLocation());
+            return new FileBasedKeyProviderConfiguration(location, rootKey);
+        } else if (EncryptionKeyProvider.KEYSTORE == encryptionKeyProvider) {
+            final String providerPassword = getProviderPassword(encryptedRepositoryProperty, niFiProperties);
+            if (StringUtils.isBlank(providerPassword)) {
+                throw new EncryptedConfigurationException("Key Provider Password not configured");
+            }
+            final char[] keyStorePassword = providerPassword.toCharArray();
+            final String location = getProviderLocation(encryptedRepositoryProperty, niFiProperties);
+            final KeystoreType keystoreType = KeyStoreUtils.getKeystoreTypeFromExtension(location);
+            try {
+                final KeyStore keyStore = KeyStoreUtils.loadSecretKeyStore(location, keyStorePassword, keystoreType.getType());
+                return new KeyStoreKeyProviderConfiguration(keyStore, keyStorePassword);
+            } catch (final TlsException e) {
+                throw new EncryptedConfigurationException("Key Store Provider loading failed", e);
+            }
+        } else {
+            throw new UnsupportedOperationException(String.format("Key Provider [%s] not supported", encryptionKeyProvider));
+        }
+    }
+
+    private String getProviderLocation(final EncryptedRepositoryProperty encryptedRepositoryProperty, final NiFiProperties niFiProperties) {
+        final String providerLocation = niFiProperties.getProperty(REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_LOCATION);
+        return niFiProperties.getProperty(encryptedRepositoryProperty.getLocation(), providerLocation);
+    }
+
+    private String getProviderPassword(final EncryptedRepositoryProperty encryptedRepositoryProperty, final NiFiProperties niFiProperties) {
+        final String providerPassword = niFiProperties.getProperty(REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_PASSWORD);
+        return niFiProperties.getProperty(encryptedRepositoryProperty.getPassword(), providerPassword);
+    }
+
+    private static SecretKey getRootKey() {
+        try {
+            String rootKeyHex = NiFiBootstrapUtils.extractKeyFromBootstrapFile();
+            return new SecretKeySpec(Hex.decode(rootKeyHex), ROOT_KEY_ALGORITHM);
+        } catch (final IOException | DecoderException e) {
+            throw new EncryptedConfigurationException("Read Root Key from Bootstrap Failed", e);
+        }
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadata.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadata.java
new file mode 100644
index 0000000..ad5e4c5
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadata.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.metadata;
+
+/**
+ * Metadata descriptor for encrypted records
+ */
+public interface RecordMetadata {
+    /**
+     * Get key identifier
+     *
+     * @return Key identifier used for encryption
+     */
+    String getKeyId();
+
+    /**
+     * Get initialization vector
+     *
+     * @return Initialization vector used for encryption
+     */
+    byte[] getInitializationVector();
+
+    /**
+     * Get length of encrypted binary
+     *
+     * @return Length of encrypted binary or -1 when unknown for streams
+     */
+    int getLength();
+
+    /**
+     * Get cipher algorithm
+     *
+     * @return Cipher algorithm
+     */
+    String getAlgorithm();
+
+    /**
+     * Get metadata version
+     *
+     * @return Metadata version
+     */
+    String getVersion();
+}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataReader.java
similarity index 66%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataReader.java
index e23d492..c26713f 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataReader.java
@@ -14,14 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.metadata;
 
-import org.junit.Test;
+import java.io.IOException;
 
-public class EncryptionExceptionTest {
-
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
-    }
+/**
+ * Reaer for Encrypted Record Metadata
+ */
+public interface RecordMetadataReader {
+    /**
+     * Get encrypted record metadata
+     *
+     * @return Record Metadata descriptor
+     * @throws IOException Thrown when failing to read metadata records
+     */
+    RecordMetadata getRecordMetadata() throws IOException;
 }
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataSerializer.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataSerializer.java
new file mode 100644
index 0000000..1241046
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/RecordMetadataSerializer.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.repository.encryption.metadata;
+
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+
+/**
+ * Serializer for Record Metadata
+ */
+public interface RecordMetadataSerializer {
+    /**
+     * Write Metadata to byte array
+     *
+     * @param keyId Key Identifier used for encryption
+     * @param initializationVector Initialization Vector
+     * @param length Length of encrypted binary
+     * @param repositoryEncryptionMethod Repository Encryption Method
+     * @return Serialized byte array
+     */
+    byte[] writeMetadata(String keyId, byte[] initializationVector, int length, RepositoryEncryptionMethod repositoryEncryptionMethod);
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStream.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStream.java
new file mode 100644
index 0000000..8d38fa5
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStream.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.metadata.serialization;
+
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadataReader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+/**
+ * Custom subclass of ObjectInputStream to support compatibility with different metadata objects
+ */
+public class RecordMetadataObjectInputStream extends ObjectInputStream implements RecordMetadataReader {
+    /** Metadata Package Name */
+    private static final String METADATA_PACKAGE_NAME = "org.apache.nifi";
+
+    public RecordMetadataObjectInputStream(final InputStream inputStream) throws IOException {
+        super(inputStream);
+    }
+
+    /**
+     * Get Record Metadata reading from serialized object
+     *
+     * @return Record Metadata
+     * @throws IOException Thrown on readObject() failures
+     */
+    @Override
+    public RecordMetadata getRecordMetadata() throws IOException {
+        try {
+            return (RecordMetadata) readObject();
+        } catch (final ClassNotFoundException e) {
+            throw new IOException("Metadata Class not found", e);
+        }
+    }
+
+    /**
+     * Read Class Descriptor returns definition of SerializableRecordMetadata for metadata implementation classes
+     *
+     * @return Object Stream Class describing Record Metadata
+     */
+    @Override
+    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
+        ObjectStreamClass classDescriptor = super.readClassDescriptor();
+        final String name = classDescriptor.getName();
+        if (name.startsWith(METADATA_PACKAGE_NAME)) {
+            if (classDescriptor.getFields().length == 0) {
+                // Return standard class for descriptor with no fields
+                classDescriptor = ObjectStreamClass.lookup(StandardRecordMetadata.class);
+            } else {
+                // Return serializable class for descriptor with fields
+                classDescriptor = ObjectStreamClass.lookup(SerializableRecordMetadata.class);
+            }
+        }
+        return classDescriptor;
+    }
+}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/SerializableRecordMetadata.java
similarity index 60%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/SerializableRecordMetadata.java
index e23d492..8ff99e1 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/SerializableRecordMetadata.java
@@ -14,14 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.metadata.serialization;
 
-import org.junit.Test;
+import java.io.Serializable;
 
-public class EncryptionExceptionTest {
+/**
+ * Record Metadata object with properties used for compatibility with serialized streams or bytes
+ */
+class SerializableRecordMetadata implements Serializable  {
+    private static final long serialVersionUID = 1;
 
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
-    }
+    private static final int STREAM_LENGTH = -1;
+
+    public String keyId;
+    public byte[] ivBytes;
+    public String algorithm;
+    public String version;
+    public int cipherByteLength = STREAM_LENGTH;
 }
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadata.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadata.java
new file mode 100644
index 0000000..88f0ad0
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadata.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.metadata.serialization;
+
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+
+/**
+ * Standard Record Metadata implementing Record Metadata methods
+ */
+class StandardRecordMetadata extends SerializableRecordMetadata implements RecordMetadata {
+    private static final long serialVersionUID = 1;
+
+    @Override
+    public String getKeyId() {
+        return keyId;
+    }
+
+    @Override
+    public byte[] getInitializationVector() {
+        return ivBytes;
+    }
+
+    @Override
+    public int getLength() {
+        return cipherByteLength;
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public String getVersion() {
+        return version;
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadataSerializer.java b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadataSerializer.java
new file mode 100644
index 0000000..ae65b7a
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/main/java/org/apache/nifi/repository/encryption/metadata/serialization/StandardRecordMetadataSerializer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.repository.encryption.metadata.serialization;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionProtocol;
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadataSerializer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.UncheckedIOException;
+import java.util.Objects;
+
+/**
+ * Standard implementation of Record Metadata Serializer using ObjectOutputStream
+ */
+public class StandardRecordMetadataSerializer implements RecordMetadataSerializer {
+    private final EncryptionProtocol encryptionProtocol;
+
+    public StandardRecordMetadataSerializer(final EncryptionProtocol encryptionProtocol) {
+        this.encryptionProtocol = Objects.requireNonNull(encryptionProtocol, "Encryption Protocol required");
+    }
+
+    /**
+     * Write Metadata to byte array
+     *
+     * @param keyId Key Identifier used for encryption
+     * @param initializationVector Initialization Vector
+     * @param length Length of encrypted binary
+     * @param repositoryEncryptionMethod Repository Encryption Method
+     * @return Serialized byte array
+     */
+    @Override
+    public byte[] writeMetadata(final String keyId, final byte[] initializationVector, final int length, final RepositoryEncryptionMethod repositoryEncryptionMethod) {
+        Objects.requireNonNull(keyId, "Key Identifier required");
+        Objects.requireNonNull(repositoryEncryptionMethod, "Repository Encryption Method required");
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try (final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
+            final StandardRecordMetadata recordMetadata = new StandardRecordMetadata();
+            recordMetadata.keyId = keyId;
+            recordMetadata.ivBytes = initializationVector;
+            recordMetadata.cipherByteLength = length;
+            recordMetadata.algorithm = repositoryEncryptionMethod.getAlgorithm();
+            recordMetadata.version = encryptionProtocol.getVersion();
+            objectOutputStream.writeObject(recordMetadata);
+        } catch (final IOException e) {
+            throw new UncheckedIOException(String.format("Write Metadata Key ID [%s] Failed", keyId), e);
+        }
+        return outputStream.toByteArray();
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesCtrByteArrayRepositoryEncryptorTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesCtrByteArrayRepositoryEncryptorTest.java
new file mode 100644
index 0000000..0f0427a
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesCtrByteArrayRepositoryEncryptorTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.repository.encryption;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.SecureRandom;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AesCtrByteArrayRepositoryEncryptorTest {
+    private static final String RECORD_ID = "primary-record";
+
+    private static final String KEY_ID = "primary-key";
+
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+    private static final int KEY_LENGTH = 16;
+
+    private static final String KEY_ALGORITHM = "AES";
+
+    private static final String PLAINTEXT = "RECORD";
+
+    private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+    private static final byte[] PLAINTEXT_RECORD = PLAINTEXT.getBytes(CHARSET);
+
+    private static SecretKey secretKey;
+
+    @Mock
+    private KeyProvider keyProvider;
+
+    @BeforeAll
+    public static void setKey() {
+        final byte[] key = new byte[KEY_LENGTH];
+        SECURE_RANDOM.nextBytes(key);
+        secretKey = new SecretKeySpec(key, KEY_ALGORITHM);
+    }
+
+    @Test
+    public void testEncryptDecrypt() throws KeyManagementException, IOException {
+        assertEncryptDecryptEquals();
+    }
+
+    private void assertEncryptDecryptEquals() throws KeyManagementException, IOException {
+        setKeyProvider();
+        final AesCtrStreamRepositoryEncryptor encryptor = new AesCtrStreamRepositoryEncryptor(keyProvider, EncryptionMetadataHeader.FLOWFILE);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final OutputStream encryptedOutputStream = encryptor.encrypt(outputStream, RECORD_ID, KEY_ID);
+
+        encryptedOutputStream.write(PLAINTEXT_RECORD);
+        encryptedOutputStream.close();
+
+        final byte[] encrypted = outputStream.toByteArray();
+
+        final InputStream inputStream = encryptor.decrypt(new ByteArrayInputStream(encrypted), RECORD_ID);
+        final byte[] decrypted = new byte[PLAINTEXT_RECORD.length];
+        final int decryptedBytesRead = inputStream.read(decrypted);
+
+        assertEquals(PLAINTEXT_RECORD.length, decryptedBytesRead);
+        final String decryptedRecord = new String(decrypted, CHARSET);
+        assertEquals(PLAINTEXT, decryptedRecord);
+    }
+
+    private void setKeyProvider() throws KeyManagementException {
+        when(keyProvider.keyExists(eq(KEY_ID))).thenReturn(true);
+        when(keyProvider.getKey(eq(KEY_ID))).thenReturn(secretKey);
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptorTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptorTest.java
new file mode 100644
index 0000000..e9bf462
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/AesGcmByteArrayRepositoryEncryptorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.SecureRandom;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AesGcmByteArrayRepositoryEncryptorTest {
+    private static final String RECORD_ID = "primary-record";
+
+    private static final String KEY_ID = "primary-key";
+
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+    private static final int KEY_LENGTH = 16;
+
+    private static final String KEY_ALGORITHM = "AES";
+
+    private static final String PLAINTEXT = "RECORD";
+
+    private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+    private static final byte[] PLAINTEXT_RECORD = PLAINTEXT.getBytes(CHARSET);
+
+    private static SecretKey secretKey;
+
+    @Mock
+    private KeyProvider keyProvider;
+
+    @BeforeAll
+    public static void setKey() {
+        final byte[] key = new byte[KEY_LENGTH];
+        SECURE_RANDOM.nextBytes(key);
+        secretKey = new SecretKeySpec(key, KEY_ALGORITHM);
+    }
+
+    @Test
+    public void testEncryptDecryptContentRecord() throws KeyManagementException {
+        assertEncryptDecryptEquals(EncryptionMetadataHeader.CONTENT);
+    }
+
+    @Test
+    public void testEncryptDecryptProvenanceRecord() throws KeyManagementException {
+        assertEncryptDecryptEquals(EncryptionMetadataHeader.PROVENANCE);
+    }
+
+    @Test
+    public void testDecryptEmptyByteArrayFailed() {
+        final AesGcmByteArrayRepositoryEncryptor encryptor = new AesGcmByteArrayRepositoryEncryptor(keyProvider, EncryptionMetadataHeader.CONTENT);
+        assertThrows(RepositoryEncryptionException.class, () -> encryptor.decrypt(new byte[0], RECORD_ID));
+    }
+
+    private void assertEncryptDecryptEquals(final EncryptionMetadataHeader encryptionMetadataHeader) throws KeyManagementException {
+        setKeyProvider();
+        final AesGcmByteArrayRepositoryEncryptor encryptor = new AesGcmByteArrayRepositoryEncryptor(keyProvider, encryptionMetadataHeader);
+        final byte[] encrypted = encryptor.encrypt(PLAINTEXT_RECORD, RECORD_ID, KEY_ID);
+        final byte[] decrypted = encryptor.decrypt(encrypted, RECORD_ID);
+        assertEquals(PLAINTEXT_RECORD.length, decrypted.length);
+        final String decryptedRecord = new String(decrypted, CHARSET);
+        assertEquals(PLAINTEXT, decryptedRecord);
+    }
+
+    private void setKeyProvider() throws KeyManagementException {
+        when(keyProvider.keyExists(eq(KEY_ID))).thenReturn(true);
+        when(keyProvider.getKey(eq(KEY_ID))).thenReturn(secretKey);
+    }
+}
diff --git a/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactoryTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactoryTest.java
new file mode 100644
index 0000000..95ab3ca
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/configuration/kms/StandardRepositoryKeyProviderFactoryTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.repository.encryption.configuration.kms;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.security.kms.KeyProvider;
+import org.apache.nifi.security.kms.KeyStoreKeyProvider;
+import org.apache.nifi.security.kms.StaticKeyProvider;
+import org.apache.nifi.util.NiFiProperties;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+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 StandardRepositoryKeyProviderFactoryTest {
+    private static final String KEY = "1234567812345678";
+
+    private static final String KEY_ID = "1";
+
+    private static final char[] PASSWORD = UUID.randomUUID().toString().toCharArray();
+
+    private static final String KEY_STORE_TYPE = "PKCS12";
+
+    private static final String TYPE_EXTENSION = ".p12";
+
+    private static final SecretKey SECRET_KEY = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES");
+
+    private static final String KEY_PROTECTION_ALGORITHM = "PBEWithHmacSHA256AndAES_256";
+
+    private RepositoryKeyProviderFactory factory;
+
+    @BeforeEach
+    public void setProvider() {
+        factory = new StandardRepositoryKeyProviderFactory();
+    }
+
+    @Test
+    public void testGetKeyProviderPropertyNotConfiguredException() {
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null);
+        final EncryptedRepositoryType encryptedRepositoryType = EncryptedRepositoryType.CONTENT;
+
+        final EncryptedConfigurationException exception = assertThrows(EncryptedConfigurationException.class, () ->
+                factory.getKeyProvider(encryptedRepositoryType, niFiProperties));
+        assertTrue(exception.getMessage().contains(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS));
+    }
+
+    @Test
+    public void testGetKeyProviderNotFound() {
+        final String notFoundProvider = "OTHER";
+        final Map<String, String> properties = Collections.singletonMap(NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER, notFoundProvider);
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
+        final EncryptedRepositoryType encryptedRepositoryType = EncryptedRepositoryType.CONTENT;
+
+        final EncryptedConfigurationException exception = assertThrows(EncryptedConfigurationException.class, () ->
+                factory.getKeyProvider(encryptedRepositoryType, niFiProperties));
+        assertTrue(exception.getMessage().contains(notFoundProvider));
+    }
+
+    @Test
+    public void testGetKeyProviderContentKeyStoreKeyProvider() throws IOException, GeneralSecurityException {
+        final Map<String, String> properties = new HashMap<>();
+
+        properties.put(NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER, EncryptionKeyProvider.KEYSTORE.toString());
+        properties.put(NiFiProperties.REPOSITORY_ENCRYPTION_KEY_ID, KEY_ID);
+        properties.put(NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_PASSWORD, new String(PASSWORD));
+
+        final File keyStoreFile = File.createTempFile(getClass().getSimpleName(), TYPE_EXTENSION);
+        keyStoreFile.deleteOnExit();
+        try (final FileOutputStream fileOutputStream = new FileOutputStream(keyStoreFile)) {
+            setKeyStore(fileOutputStream);
+        }
+
+        properties.put(NiFiProperties.REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_LOCATION, keyStoreFile.getAbsolutePath());
+
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
+        assertKeyProviderConfigured(KeyStoreKeyProvider.class, EncryptedRepositoryType.CONTENT, niFiProperties);
+    }
+
+    @Test
+    public void testGetKeyProviderContentStaticKeyProvider() {
+        final Map<String, String> properties = new HashMap<>();
+
+        final Class<?> providerClass = StaticKeyProvider.class;
+        properties.put(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS, providerClass.getName());
+        properties.put(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY, KEY);
+        properties.put(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_ID, KEY_ID);
+
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
+        assertKeyProviderConfigured(providerClass, EncryptedRepositoryType.CONTENT, niFiProperties);
+    }
+
+    @Test
+    public void testGetKeyProviderFlowFileStaticKeyProvider() {
+        final Map<String, String> properties = new HashMap<>();
+
+        final Class<?> providerClass = StaticKeyProvider.class;
+        properties.put(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS, providerClass.getName());
+        properties.put(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY, KEY);
+        properties.put(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID, KEY_ID);
+
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
+        assertKeyProviderConfigured(providerClass, EncryptedRepositoryType.FLOWFILE, niFiProperties);
+    }
+
+    @Test
+    public void testGetKeyProviderProvenanceStaticKeyProvider() {
+        final Map<String, String> properties = new HashMap<>();
+
+        final Class<?> providerClass = StaticKeyProvider.class;
+        properties.put(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS, providerClass.getName());
+        properties.put(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY, KEY);
+        properties.put(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_ID, KEY_ID);
+
+        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
+        assertKeyProviderConfigured(providerClass, EncryptedRepositoryType.PROVENANCE, niFiProperties);
+    }
+
+    private void assertKeyProviderConfigured(final Class<?> providerClass, final EncryptedRepositoryType encryptedRepositoryType, final NiFiProperties niFiProperties) {
+        final KeyProvider keyProvider = factory.getKeyProvider(encryptedRepositoryType, niFiProperties);
+        assertNotNull(keyProvider);
+        assertEquals(providerClass, keyProvider.getClass());
+        assertTrue(keyProvider.keyExists(KEY_ID));
+    }
+
+    private void setKeyStore(final OutputStream outputStream) throws GeneralSecurityException, IOException {
+        KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
+        keyStore.load(null, null);
+        keyStore.setEntry(KEY_ID, new KeyStore.SecretKeyEntry(SECRET_KEY), new KeyStore.PasswordProtection(PASSWORD, KEY_PROTECTION_ALGORITHM, null));
+        keyStore.store(outputStream, PASSWORD);
+    }
+}
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/AbstractEncryptionMetadata.java
similarity index 60%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/AbstractEncryptionMetadata.java
index e23d492..c31dd7f 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/AbstractEncryptionMetadata.java
@@ -14,14 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.metadata.record;
 
-import org.junit.Test;
+import java.io.Serializable;
 
-public class EncryptionExceptionTest {
-
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
-    }
+/**
+ * Abstract Encryption Metadata class representing compatible implementation of fields used for serialization
+ *
+ * Follows earlier implementation of RepositoryObjectEncryptionMetadata abstract class
+ */
+public class AbstractEncryptionMetadata implements Serializable {
+    public String keyId;
+    public byte[] ivBytes;
+    public String algorithm;
+    public String version;
+    public int cipherByteLength;
 }
diff --git a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/EncryptionMetadata.java
similarity index 71%
copy from nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
copy to nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/EncryptionMetadata.java
index e23d492..3043cf5 100644
--- a/nifi-commons/nifi-data-provenance-utils/src/test/java/org/apache/nifi/provenance/EncryptionExceptionTest.java
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/record/EncryptionMetadata.java
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.provenance;
+package org.apache.nifi.repository.encryption.metadata.record;
 
-import org.junit.Test;
+/**
+ * Encryption Metadata class representing compatible implementation of fields used for serialization
+ *
+ * Follows earlier implementation of provenance EncryptionMetadata class
+ */
+public class EncryptionMetadata extends AbstractEncryptionMetadata {
 
-public class EncryptionExceptionTest {
-
-    @Test
-    public void testShouldTriggerGroovyTestExecution() {
-        // This method does nothing but tell Maven to run the groovy tests
-    }
 }
diff --git a/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStreamTest.java b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStreamTest.java
new file mode 100644
index 0000000..0264d11
--- /dev/null
+++ b/nifi-commons/nifi-repository-encryption/src/test/java/org/apache/nifi/repository/encryption/metadata/serialization/RecordMetadataObjectInputStreamTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.repository.encryption.metadata.serialization;
+
+import org.apache.nifi.repository.encryption.configuration.EncryptionProtocol;
+import org.apache.nifi.repository.encryption.configuration.RepositoryEncryptionMethod;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadata;
+import org.apache.nifi.repository.encryption.metadata.RecordMetadataSerializer;
+import org.apache.nifi.repository.encryption.metadata.record.EncryptionMetadata;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+public class RecordMetadataObjectInputStreamTest {
+    private static final String KEY_ID = UUID.randomUUID().toString();
+
+    private static final RepositoryEncryptionMethod METHOD = RepositoryEncryptionMethod.AES_GCM;
+
+    private static final EncryptionProtocol PROTOCOL = EncryptionProtocol.VERSION_1;
+
+    private static final byte[] INITIALIZATION_VECTOR = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
+
+    private static final int LENGTH = 1024;
+
+    @Test
+    public void testGetRecordMetadata() throws IOException {
+        final RecordMetadataSerializer serializer = new StandardRecordMetadataSerializer(PROTOCOL);
+        final byte[] serialized = serializer.writeMetadata(KEY_ID, INITIALIZATION_VECTOR, LENGTH, METHOD);
+
+        try (final RecordMetadataObjectInputStream objectInputStream = new RecordMetadataObjectInputStream(new ByteArrayInputStream(serialized))) {
+            final RecordMetadata recordMetadata = objectInputStream.getRecordMetadata();
+            assertEquals(KEY_ID, recordMetadata.getKeyId());
+            assertEquals(LENGTH, recordMetadata.getLength());
+            assertArrayEquals(INITIALIZATION_VECTOR, recordMetadata.getInitializationVector());
+            assertEquals(METHOD.getAlgorithm(), recordMetadata.getAlgorithm());
+            assertEquals(PROTOCOL.getVersion(), recordMetadata.getVersion());
+        }
+    }
+
+    @Test
+    public void testGetRecordMetadataCompatibleClass() throws IOException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        final EncryptionMetadata metadata = new EncryptionMetadata();
+        metadata.algorithm = METHOD.getAlgorithm();
+        metadata.cipherByteLength = LENGTH;
+        metadata.ivBytes = INITIALIZATION_VECTOR;
+        metadata.keyId = KEY_ID;
+        metadata.version = PROTOCOL.getVersion();
+
+        try (final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
+            objectOutputStream.writeObject(metadata);
+        }
+
+        final byte[] serialized = outputStream.toByteArray();
+
+        try (final RecordMetadataObjectInputStream objectInputStream = new RecordMetadataObjectInputStream(new ByteArrayInputStream(serialized))) {
+            final RecordMetadata recordMetadata = objectInputStream.getRecordMetadata();
+            assertEquals(KEY_ID, recordMetadata.getKeyId());
+            assertEquals(LENGTH, recordMetadata.getLength());
+            assertArrayEquals(INITIALIZATION_VECTOR, recordMetadata.getInitializationVector());
+        }
+    }
+}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java
deleted file mode 100644
index 37909e9..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java
+++ /dev/null
@@ -1,261 +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.security.kms;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
-import org.apache.nifi.util.NiFiBootstrapUtils;
-import org.bouncycastle.util.encoders.DecoderException;
-import org.bouncycastle.util.encoders.Hex;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyManagementException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class CryptoUtils {
-    private static final Logger logger = LoggerFactory.getLogger(CryptoUtils.class);
-    public static final String STATIC_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.StaticKeyProvider";
-    public static final String FILE_BASED_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.FileBasedKeyProvider";
-    public static final String KEY_STORE_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.KeyStoreKeyProvider";
-
-    // TODO: Move to RepositoryEncryptionUtils in NIFI-6617
-    public static final String LEGACY_SKP_FQCN = "org.apache.nifi.provenance.StaticKeyProvider";
-    public static final String LEGACY_FBKP_FQCN = "org.apache.nifi.provenance.FileBasedKeyProvider";
-
-    // TODO: Enforce even length
-    private static final Pattern HEX_PATTERN = Pattern.compile("(?i)^[0-9a-f]+$");
-
-    private static final List<Integer> UNLIMITED_KEY_LENGTHS = Arrays.asList(32, 48, 64);
-
-    public static final String ENCRYPTED_FSR_CLASS_NAME = "org.apache.nifi.controller.repository.crypto.EncryptedFileSystemRepository";
-    public static final String EWAFFR_CLASS_NAME = "org.apache.nifi.controller.repository.crypto.EncryptedWriteAheadFlowFileRepository";
-
-    public static boolean isUnlimitedStrengthCryptoAvailable() {
-        try {
-            return Cipher.getMaxAllowedKeyLength("AES") > 128;
-        } catch (NoSuchAlgorithmException e) {
-            logger.warn("Tried to determine if unlimited strength crypto is available but the AES algorithm is not available");
-            return false;
-        }
-    }
-
-    /**
-     * Utility method which returns true if the string is null, empty, or entirely whitespace.
-     *
-     * @param src the string to evaluate
-     * @return true if empty
-     */
-    public static boolean isEmpty(String src) {
-        return src == null || src.trim().isEmpty();
-    }
-
-    /**
-     * Concatenates multiple byte[] into a single byte[].
-     *
-     * @param arrays the component byte[] in order
-     * @return a concatenated byte[]
-     */
-    public static byte[] concatByteArrays(byte[]... arrays) {
-        int totalByteLength = 0;
-        for (byte[] bytes : arrays) {
-            totalByteLength += bytes.length;
-        }
-        byte[] totalBytes = new byte[totalByteLength];
-        int currentLength = 0;
-        for (byte[] bytes : arrays) {
-            System.arraycopy(bytes, 0, totalBytes, currentLength, bytes.length);
-            currentLength += bytes.length;
-        }
-        return totalBytes;
-    }
-
-    /**
-     * Returns true if the provided configuration values are valid (shallow evaluation only; does not validate the keys
-     * contained in a {@link FileBasedKeyProvider}).
-     *
-     * @param rec the configuration to validate
-     * @return true if the config is valid
-     */
-    public static boolean isValidRepositoryEncryptionConfiguration(RepositoryEncryptionConfiguration rec) {
-        return isValidKeyProvider(rec.getKeyProviderImplementation(), rec.getKeyProviderLocation(), rec.getEncryptionKeyId(), rec.getEncryptionKeys());
-
-    }
-
-    /**
-     * Returns true if the provided configuration values successfully define the specified {@link KeyProvider}.
-     *
-     * @param keyProviderImplementation the FQ class name of the {@link KeyProvider} implementation
-     * @param keyProviderLocation       the location of the definition (for {@link FileBasedKeyProvider}, etc.)
-     * @param keyId                     the active key ID
-     * @param encryptionKeys            a map of key IDs to key material in hex format
-     * @return true if the provided configuration is valid
-     */
-    public static boolean isValidKeyProvider(String keyProviderImplementation, final String keyProviderLocation, final String keyId, final Map<String, String> encryptionKeys) {
-        try {
-            keyProviderImplementation = handleLegacyPackages(keyProviderImplementation);
-        } catch (final KeyManagementException e) {
-            logger.warn("Key Provider [{}] Validation Failed: {}", keyProviderImplementation, e.getMessage());
-            return false;
-        }
-
-        switch (keyProviderImplementation) {
-            case STATIC_KEY_PROVIDER_CLASS_NAME:
-                if (encryptionKeys == null) {
-                    return false;
-                } else {
-                    boolean everyKeyValid = encryptionKeys.values().stream().allMatch(CryptoUtils::keyIsValid);
-                    return everyKeyValid && StringUtils.isNotEmpty(keyId);
-                }
-            case FILE_BASED_KEY_PROVIDER_CLASS_NAME:
-            case KEY_STORE_KEY_PROVIDER_CLASS_NAME:
-                final Path keyProviderPath = Paths.get(keyProviderLocation);
-                return Files.isReadable(keyProviderPath) && StringUtils.isNotEmpty(keyId);
-            default:
-                logger.warn("Validation Failed: Key Provider [{}] Location [{}] Key ID [{}]", keyProviderImplementation, keyProviderLocation, keyId);
-                return false;
-        }
-    }
-
-    static String handleLegacyPackages(String implementationClassName) throws KeyManagementException {
-        if (org.apache.nifi.util.StringUtils.isBlank(implementationClassName)) {
-            throw new KeyManagementException("Invalid key provider implementation provided: " + implementationClassName);
-        }
-        if (implementationClassName.equalsIgnoreCase(LEGACY_SKP_FQCN)) {
-            return StaticKeyProvider.class.getName();
-        } else if (implementationClassName.equalsIgnoreCase(LEGACY_FBKP_FQCN)) {
-            return FileBasedKeyProvider.class.getName();
-        } else {
-            return implementationClassName;
-        }
-    }
-
-    /**
-     * Returns true if the provided key is valid hex and is the correct length for the current system's JCE policies.
-     *
-     * @param encryptionKeyHex the key in hexadecimal
-     * @return true if this key is valid
-     */
-    public static boolean keyIsValid(String encryptionKeyHex) {
-        return isHexString(encryptionKeyHex)
-                && (isUnlimitedStrengthCryptoAvailable()
-                ? UNLIMITED_KEY_LENGTHS.contains(encryptionKeyHex.length())
-                : encryptionKeyHex.length() == 32);
-    }
-
-    /**
-     * Returns true if the input is valid hexadecimal (does not enforce length and is case-insensitive).
-     *
-     * @param hexString the string to evaluate
-     * @return true if the string is valid hex
-     */
-    public static boolean isHexString(String hexString) {
-        return StringUtils.isNotEmpty(hexString) && HEX_PATTERN.matcher(hexString).matches();
-    }
-
-    /**
-     * Returns the root key from the {@code bootstrap.conf} file used to encrypt various sensitive properties and data encryption keys.
-     *
-     * @return the root key
-     * @throws KeyManagementException if the key cannot be read
-     */
-    public static SecretKey getRootKey() throws KeyManagementException {
-        try {
-            // Get the root encryption key from bootstrap.conf
-            String rootKeyHex = NiFiBootstrapUtils.extractKeyFromBootstrapFile();
-            return new SecretKeySpec(Hex.decode(rootKeyHex), "AES");
-        } catch (IOException | DecoderException e) {
-            logger.error("Encountered an error: ", e);
-            throw new KeyManagementException(e);
-        }
-    }
-
-    /**
-     * Returns true if the two parameters are equal. This method is null-safe and evaluates the
-     * equality in constant-time rather than "short-circuiting" on the first inequality. This
-     * prevents timing attacks (side channel attacks) when comparing passwords or hash values.
-     *
-     * @param a a String to compare
-     * @param b a String to compare
-     * @return true if the values are equal
-     */
-    public static boolean constantTimeEquals(String a, String b) {
-        if (a == null) {
-            return b == null;
-        } else {
-            // This returns true IFF b != null and the byte[] are equal; if b == null, a is not, and they are not equal
-            return b != null && constantTimeEquals(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
-        }
-    }
-
-    /**
-     * Returns true if the two parameters are equal. This method is null-safe and evaluates the
-     * equality in constant-time rather than "short-circuiting" on the first inequality. This
-     * prevents timing attacks (side channel attacks) when comparing passwords or hash values.
-     * Does not convert the character arrays to {@code String}s when converting to {@code byte[]}
-     * to avoid putting sensitive data in the String pool.
-     *
-     * @param a a char[] to compare
-     * @param b a char[] to compare
-     * @return true if the values are equal
-     */
-    public static boolean constantTimeEquals(char[] a, char[] b) {
-        return constantTimeEquals(convertCharsToBytes(a), convertCharsToBytes(b));
-    }
-
-
-    /**
-     * Returns true if the two parameters are equal. This method is null-safe and evaluates the
-     * equality in constant-time rather than "short-circuiting" on the first inequality. This
-     * prevents timing attacks (side channel attacks) when comparing passwords or hash values.
-     *
-     * @param a a byte[] to compare
-     * @param b a byte[] to compare
-     * @return true if the values are equal
-     */
-    public static boolean constantTimeEquals(byte[] a, byte[] b) {
-        return MessageDigest.isEqual(a, b);
-    }
-
-    /**
-     * Returns a {@code byte[]} containing the value of the provided {@code char[]} without using {@code new String(chars).getBytes()} which would put sensitive data (the password) in the String pool.
-     *
-     * @param chars the characters to convert
-     * @return the byte[]
-     */
-    private static byte[] convertCharsToBytes(char[] chars) {
-        CharBuffer charBuffer = CharBuffer.wrap(chars);
-        ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
-        return Arrays.copyOfRange(byteBuffer.array(),
-                byteBuffer.position(), byteBuffer.limit());
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/EncryptionException.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/EncryptionException.java
deleted file mode 100644
index e4635b5..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/EncryptionException.java
+++ /dev/null
@@ -1,90 +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.security.kms;
-
-import java.security.PrivilegedActionException;
-
-/**
- * Class used to denote a problem configuring encryption services or encrypting/decrypting data.
- */
-public class EncryptionException extends Exception {
-    /**
-     * Constructs a new exception with the specified detail message.  The
-     * cause is not initialized, and may subsequently be initialized by
-     * a call to {@link #initCause}.
-     *
-     * @param message the detail message. The detail message is saved for
-     *                later retrieval by the {@link #getMessage()} method.
-     */
-    public EncryptionException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs a new exception 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 exception's detail message.
-     *
-     * @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 <tt>null</tt> value is
-     *                permitted, and indicates that the cause is nonexistent or
-     *                unknown.)
-     * @since 1.4
-     */
-    public EncryptionException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Constructs a new exception with the specified cause and a detail
-     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
-     * typically contains the class and detail message of <tt>cause</tt>).
-     * This constructor is useful for exceptions that are little more than
-     * wrappers for other throwables (for example, {@link
-     * PrivilegedActionException}).
-     *
-     * @param cause the cause (which is saved for later retrieval by the
-     *              {@link #getCause()} method).  (A <tt>null</tt> value is
-     *              permitted, and indicates that the cause is nonexistent or
-     *              unknown.)
-     * @since 1.4
-     */
-    public EncryptionException(Throwable cause) {
-        super(cause);
-    }
-
-    /**
-     * Constructs a new exception with the specified detail message,
-     * cause, suppression enabled or disabled, and writable stack
-     * trace enabled or disabled.
-     *
-     * @param message            the detail message.
-     * @param cause              the cause.  (A {@code null} value is permitted,
-     *                           and indicates that the cause is nonexistent or unknown.)
-     * @param enableSuppression  whether or not suppression is enabled
-     *                           or disabled
-     * @param writableStackTrace whether or not the stack trace should
-     *                           be writable
-     * @since 1.7
-     */
-    protected EncryptionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
-        super(message, cause, enableSuppression, writableStackTrace);
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/AbstractAESEncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/AbstractAESEncryptor.java
deleted file mode 100644
index e13b225..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/AbstractAESEncryptor.java
+++ /dev/null
@@ -1,124 +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.security.repository;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.KeyManagementException;
-import java.security.Security;
-import java.util.List;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public abstract class AbstractAESEncryptor implements RepositoryObjectEncryptor {
-    private static final Logger logger = LoggerFactory.getLogger(AbstractAESEncryptor.class);
-    private static final byte[] EM_START_SENTINEL = new byte[]{0x00, 0x00};
-    private static final byte[] EM_END_SENTINEL = new byte[]{(byte) 0xFF, (byte) 0xFF};
-    private static String ALGORITHM = "AES/CTR/NoPadding";
-    protected static final int IV_LENGTH = 16;
-    protected static final byte[] EMPTY_IV = new byte[IV_LENGTH];
-    // private static final String VERSION = "v1";
-    // private static final List<String> SUPPORTED_VERSIONS = Arrays.asList(VERSION);
-
-    protected KeyProvider keyProvider;
-
-    protected AESKeyedCipherProvider aesKeyedCipherProvider = new AESKeyedCipherProvider();
-
-    /**
-     * Initializes the encryptor with a {@link KeyProvider}.
-     *
-     * @param keyProvider the key provider which will be responsible for accessing keys
-     * @throws KeyManagementException if there is an issue configuring the key provider
-     */
-    @Override
-    public void initialize(KeyProvider keyProvider) throws KeyManagementException {
-        this.keyProvider = keyProvider;
-
-        if (this.aesKeyedCipherProvider == null) {
-            this.aesKeyedCipherProvider = new AESKeyedCipherProvider();
-        }
-
-        if (Security.getProvider("BC") == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
-    }
-
-    /**
-     * Available for dependency injection to override the default {@link AESKeyedCipherProvider} if necessary.
-     *
-     * @param cipherProvider the AES cipher provider to use
-     */
-    void setCipherProvider(AESKeyedCipherProvider cipherProvider) {
-        this.aesKeyedCipherProvider = cipherProvider;
-    }
-
-    /**
-     * Utility method which extracts the {@link RepositoryObjectEncryptionMetadata} object from the {@code byte[]} or
-     * {@link InputStream} provided and verifies common validation across both streaming and block decryption. Returns
-     * the extracted metadata object.
-     *
-     * @param ciphertextSource the encrypted source -- can be {@code byte[]} or {@code InputStream}
-     * @param identifier the unique identifier for this source
-     * @param descriptor the generic name for this source type for logging/error messages
-     * @param supportedVersions the list of supported versions for the particular encryptor calling this method (see
-     * {@link org.apache.nifi.security.repository.stream.aes.RepositoryObjectAESCTREncryptor} and
-     * {@link org.apache.nifi.security.repository.block.aes.RepositoryObjectAESGCMEncryptor} for
-     * {@code SUPPORTED_VERSIONS})
-     * @return the extracted {@link RepositoryObjectEncryptionMetadata} object
-     * @throws EncryptionException if there is an exception parsing or validating the source
-     */
-    public static RepositoryObjectEncryptionMetadata prepareObjectForDecryption(Object ciphertextSource,
-                                                                                String identifier, String descriptor, List<String> supportedVersions) throws EncryptionException {
-        if (ciphertextSource == null) {
-            throw new EncryptionException("The encrypted " + descriptor + " cannot be missing");
-        }
-
-        RepositoryObjectEncryptionMetadata metadata;
-        try {
-            if (ciphertextSource instanceof InputStream) {
-                logger.debug("Detected encrypted input stream for {} with ID {}", descriptor, identifier);
-                InputStream ciphertextStream = (InputStream) ciphertextSource;
-                metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(ciphertextStream);
-            } else if (ciphertextSource instanceof byte[]) {
-                logger.debug("Detected byte[] for {} with ID {}", descriptor, identifier);
-                byte[] ciphertextBytes = (byte[]) ciphertextSource;
-                metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(ciphertextBytes);
-            } else {
-                String errorMsg = "The " + descriptor + " with ID " + identifier + " was detected as " + ciphertextSource.getClass().getSimpleName() + "; this is not a supported source of ciphertext";
-                logger.error(errorMsg);
-                throw new EncryptionException(errorMsg);
-            }
-        } catch (IOException | ClassNotFoundException e) {
-            final String msg = "Encountered an error reading the encryption metadata: ";
-            logger.error(msg, e);
-            throw new EncryptionException(msg, e);
-        }
-
-        if (!supportedVersions.contains(metadata.version)) {
-            throw new EncryptionException("The " + descriptor + " with ID " + identifier
-                    + " was encrypted with version " + metadata.version
-                    + " which is not in the list of supported versions " + StringUtils.join(supportedVersions, ","));
-        }
-
-        return metadata;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java
deleted file mode 100644
index 4f8ebbc..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java
+++ /dev/null
@@ -1,314 +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.security.repository;
-
-import static org.apache.nifi.security.kms.CryptoUtils.isValidKeyProvider;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.kms.FileBasedKeyProvider;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.kms.KeyProviderFactory;
-import org.apache.nifi.security.kms.KeyStoreKeyProvider;
-import org.apache.nifi.security.kms.StaticKeyProvider;
-import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
-import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
-import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
-import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
-import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
-import org.apache.nifi.security.util.EncryptionMethod;
-import org.apache.nifi.security.util.KeyStoreUtils;
-import org.apache.nifi.security.util.TlsException;
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
-import org.apache.nifi.stream.io.NonCloseableInputStream;
-import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.util.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class RepositoryEncryptorUtils {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryEncryptorUtils.class);
-
-    private static final int CONTENT_HEADER_SIZE = 2;
-    private static final int IV_LENGTH = 16;
-    private static final int MIN_METADATA_LENGTH = IV_LENGTH + 3 + 3; // 3 delimiters and 3 non-zero elements
-    private static final String EWAPR_CLASS_NAME = "org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository";
-
-    // TODO: Add Javadoc
-
-    public static byte[] serializeEncryptionMetadata(RepositoryObjectEncryptionMetadata metadata) throws IOException {
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ObjectOutputStream outputStream = new ObjectOutputStream(baos);
-        outputStream.writeObject(metadata);
-        outputStream.close();
-        return baos.toByteArray();
-    }
-
-    public static Cipher initCipher(AESKeyedCipherProvider aesKeyedCipherProvider, EncryptionMethod method, int mode, SecretKey key, byte[] ivBytes) throws EncryptionException {
-        try {
-            if (method == null || key == null || ivBytes == null) {
-                throw new IllegalArgumentException("Missing critical information");
-            }
-            return aesKeyedCipherProvider.getCipher(method, key, ivBytes, mode == Cipher.ENCRYPT_MODE);
-        } catch (Exception e) {
-            logger.error("Encountered an exception initializing the cipher", e);
-            throw new EncryptionException(e);
-        }
-    }
-
-    public static RepositoryObjectEncryptionMetadata extractEncryptionMetadata(byte[] encryptedRecord) throws EncryptionException, IOException, ClassNotFoundException {
-        // TODO: Inject parser for min metadata length
-        if (encryptedRecord == null || encryptedRecord.length < MIN_METADATA_LENGTH) {
-            throw new EncryptionException("The encrypted record is too short to contain the metadata");
-        }
-
-        // TODO: Inject parser for SENTINEL vs non-SENTINEL
-        // Skip the first byte (SENTINEL) and don't need to copy all the serialized record
-        ByteArrayInputStream bais = new ByteArrayInputStream(encryptedRecord);
-        // bais.read();
-        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
-            return (RepositoryObjectEncryptionMetadata) ois.readObject();
-        }
-    }
-
-    public static RepositoryObjectEncryptionMetadata extractEncryptionMetadata(InputStream encryptedRecord) throws EncryptionException, IOException, ClassNotFoundException {
-        // TODO: Inject parser for min metadata length
-        if (encryptedRecord == null) {
-            throw new EncryptionException("The encrypted record is too short to contain the metadata");
-        }
-
-        // TODO: Inject parser for SENTINEL vs non-SENTINEL
-        // Skip the first two bytes (EM_START_SENTINEL) and don't need to copy all the serialized record
-        // TODO: May need to seek for EM_START_SENTINEL segment first
-        encryptedRecord.read(new byte[CONTENT_HEADER_SIZE]);
-        try (ObjectInputStream ois = new ObjectInputStream(new NonCloseableInputStream(encryptedRecord))) {
-            return (RepositoryObjectEncryptionMetadata) ois.readObject();
-        }
-    }
-
-    public static byte[] extractCipherBytes(byte[] encryptedRecord, RepositoryObjectEncryptionMetadata metadata) {
-        // If the length is known, there is no header, start from total length - cipher length
-        // If the length is unknown (streaming/content), calculate the metadata length + header length and start from there
-        int cipherBytesStart = metadata.cipherByteLength > 0 ? encryptedRecord.length - metadata.cipherByteLength : metadata.length() + CONTENT_HEADER_SIZE;
-        return Arrays.copyOfRange(encryptedRecord, cipherBytesStart, encryptedRecord.length);
-    }
-
-    /**
-     * Returns {@code true} if the specified repository is correctly configured for an
-     * encrypted implementation. Requires the repository implementation to support encryption
-     * and at least one valid key to be configured.
-     *
-     * @param niFiProperties the {@link NiFiProperties} instance to validate
-     * @param repositoryType the specific repository configuration to check
-     * @return true if encryption is successfully configured for the specified repository
-     */
-    public static boolean isRepositoryEncryptionConfigured(NiFiProperties niFiProperties, RepositoryType repositoryType) {
-        switch (repositoryType) {
-            case CONTENT:
-                return isContentRepositoryEncryptionConfigured(niFiProperties);
-            case PROVENANCE:
-                return isProvenanceRepositoryEncryptionConfigured(niFiProperties);
-            case FLOWFILE:
-                return isFlowFileRepositoryEncryptionConfigured(niFiProperties);
-            default:
-                logger.warn("Repository encryption configuration validation attempted for {}, an invalid repository type", repositoryType);
-                return false;
-        }
-    }
-
-    /**
-     * Returns {@code true} if the provenance repository is correctly configured for an
-     * encrypted implementation. Requires the repository implementation to support encryption
-     * and at least one valid key to be configured.
-     *
-     * @param niFiProperties the {@link NiFiProperties} instance to validate
-     * @return true if encryption is successfully configured for the provenance repository
-     */
-    static boolean isProvenanceRepositoryEncryptionConfigured(NiFiProperties niFiProperties) {
-        final String implementationClassName = niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS);
-        // Referencing EWAPR.class.getName() would require a dependency on the module
-        boolean encryptedRepo = EWAPR_CLASS_NAME.equals(implementationClassName);
-        if (encryptedRepo) {
-            return isValidKeyProvider(
-                    niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                    niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                    niFiProperties.getProvenanceRepoEncryptionKeyId(),
-                    niFiProperties.getProvenanceRepoEncryptionKeys());
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Returns {@code true} if the content repository is correctly configured for an encrypted
-     * implementation. Requires the repository implementation to support encryption and at least
-     * one valid key to be configured.
-     *
-     * @param niFiProperties the {@link NiFiProperties} instance to validate
-     * @return true if encryption is successfully configured for the content repository
-     */
-    static boolean isContentRepositoryEncryptionConfigured(NiFiProperties niFiProperties) {
-        final String implementationClassName = niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION);
-        // Referencing EFSR.class.getName() would require a dependency on the module
-        boolean encryptedRepo = CryptoUtils.ENCRYPTED_FSR_CLASS_NAME.equals(implementationClassName);
-        if (encryptedRepo) {
-            return isValidKeyProvider(
-                    niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                    niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                    niFiProperties.getContentRepositoryEncryptionKeyId(),
-                    niFiProperties.getContentRepositoryEncryptionKeys());
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Returns {@code true} if the flowfile repository is correctly configured for an encrypted
-     * implementation. Requires the repository implementation to support encryption and at least
-     * one valid key to be configured.
-     *
-     * @param niFiProperties the {@link NiFiProperties} instance to validate
-     * @return true if encryption is successfully configured for the flowfile repository
-     */
-    static boolean isFlowFileRepositoryEncryptionConfigured(NiFiProperties niFiProperties) {
-        final String implementationClassName = niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_IMPLEMENTATION);
-        boolean encryptedRepo = CryptoUtils.EWAFFR_CLASS_NAME.equals(implementationClassName);
-        if (encryptedRepo) {
-            return isValidKeyProvider(
-                    niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                    niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                    niFiProperties.getFlowFileRepoEncryptionKeyId(),
-                    niFiProperties.getFlowFileRepoEncryptionKeys());
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Utility method which returns the {@link KeyProvider} implementation class name for a given repository type.
-     *
-     * @param repositoryType the {@link RepositoryType} indicator
-     * @return the FQCN of the implementation or {@code "no_such_key_provider_defined"} for unsupported repository types
-     */
-    static String determineKeyProviderImplementationClassName(RepositoryType repositoryType) {
-        // TODO: Change to build string directly using repository type packagePath property or universal in NIFI-6617
-        if (repositoryType == null) {
-            logger.warn("Could not determine key provider implementation class name for null repository");
-            return "no_such_key_provider_defined";
-        }
-        switch (repositoryType) {
-            case FLOWFILE:
-                return NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
-            case CONTENT:
-                return NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
-            case PROVENANCE:
-                return NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS;
-            default:
-                logger.warn("Could not determine key provider implementation class name for " + repositoryType.getName());
-                return "no_such_key_provider_defined";
-        }
-    }
-
-    /**
-     * Returns a configured {@link KeyProvider} instance for the specified repository type given the configuration values in {@code nifi.properties}.
-     *
-     * @param niFiProperties the {@link NiFiProperties} object
-     * @param repositoryType the {@link RepositoryType} indicator
-     * @return the configured KeyProvider
-     * @throws IOException if there is a problem reading the properties or they are not valid & complete
-     */
-    public static KeyProvider validateAndBuildRepositoryKeyProvider(NiFiProperties niFiProperties, RepositoryType repositoryType) throws IOException {
-        if (isRepositoryEncryptionConfigured(niFiProperties, repositoryType)) {
-            try {
-                final RepositoryEncryptionConfiguration configuration = RepositoryEncryptionConfiguration.fromNiFiProperties(niFiProperties, repositoryType);
-                return getKeyProvider(configuration);
-            } catch (final KeyManagementException e) {
-                final String msg = "Encountered an error building the key provider";
-                logger.error(msg, e);
-                throw new IOException(msg, e);
-            }
-        } else {
-            throw new IOException("The provided configuration does not support an encrypted " + repositoryType.getName());
-        }
-    }
-
-    /**
-     * Returns a configured {@link KeyProvider} instance for the specified repository type given the configuration values.
-     *
-     * @param repositoryEncryptionConfiguration the {@link RepositoryEncryptionConfiguration} object
-     * @return the configured KeyProvider
-     * @throws IOException if there is a problem reading the properties or they are not valid & complete
-     */
-    public static KeyProvider validateAndBuildRepositoryKeyProvider(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws IOException {
-        try {
-            return getKeyProvider(repositoryEncryptionConfiguration);
-        } catch (final KeyManagementException e) {
-            final String msg = "Encountered an error building the key provider";
-            logger.error(msg, e);
-            throw new IOException(msg, e);
-        }
-    }
-
-    private static KeyProvider getKeyProvider(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws KeyManagementException {
-        final KeyProviderConfiguration<?> keyProviderConfiguration = getKeyProviderConfiguration(repositoryEncryptionConfiguration);
-        final KeyProvider keyProvider = KeyProviderFactory.getKeyProvider(keyProviderConfiguration);
-
-        final String keyId = repositoryEncryptionConfiguration.getEncryptionKeyId();
-        if (keyProvider.keyExists(keyId)) {
-            return keyProvider;
-        } else {
-            throw new KeyManagementException(String.format("Key Identifier [%s] not found in Key Provider", keyId));
-        }
-    }
-
-    private static KeyProviderConfiguration<?> getKeyProviderConfiguration(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws KeyManagementException {
-        final String keyProviderImplementation = repositoryEncryptionConfiguration.getKeyProviderImplementation();
-        if (keyProviderImplementation.endsWith(StaticKeyProvider.class.getSimpleName())) {
-            return new StaticKeyProviderConfiguration(repositoryEncryptionConfiguration.getEncryptionKeys());
-        } else if (keyProviderImplementation.endsWith(FileBasedKeyProvider.class.getSimpleName())) {
-            final SecretKey rootKey = CryptoUtils.getRootKey();
-            return new FileBasedKeyProviderConfiguration(repositoryEncryptionConfiguration.getKeyProviderLocation(), rootKey);
-        } else if (keyProviderImplementation.endsWith(KeyStoreKeyProvider.class.getSimpleName())) {
-            final String keyProviderPassword = repositoryEncryptionConfiguration.getKeyProviderPassword();
-            if (StringUtils.isBlank(keyProviderPassword)) {
-                throw new KeyManagementException("Key Provider Password not configured");
-            }
-            final String location = repositoryEncryptionConfiguration.getKeyProviderLocation();
-            final char[] keyStorePassword = repositoryEncryptionConfiguration.getKeyProviderPassword().toCharArray();
-            final String keyStoreType = repositoryEncryptionConfiguration.getKeyStoreType();
-            try {
-                final KeyStore keyStore = KeyStoreUtils.loadSecretKeyStore(location, keyStorePassword, keyStoreType);
-                return new KeyStoreKeyProviderConfiguration(keyStore, keyStorePassword);
-            } catch (final TlsException e) {
-                throw new KeyManagementException("Key Store Provider loading failed", e);
-            }
-        } else {
-            throw new UnsupportedOperationException(String.format("Key Provider Implementation [%s] not supported", keyProviderImplementation));
-        }
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptionMetadata.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptionMetadata.java
deleted file mode 100644
index 157cf63..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptionMetadata.java
+++ /dev/null
@@ -1,64 +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.security.repository;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import org.apache.commons.codec.binary.Hex;
-
-public abstract class RepositoryObjectEncryptionMetadata implements Serializable {
-    public String keyId;
-    public String algorithm;
-    public byte[] ivBytes;
-    public String version;
-    public int cipherByteLength;
-    private transient int length = 0;
-
-    @Override
-    public String toString() {
-        String sb = "Repository Object Encryption Metadata" +
-                " Key ID: " +
-                keyId +
-                " Algorithm: " +
-                algorithm +
-                " IV: " +
-                Hex.encodeHexString(ivBytes) +
-                " Version: " +
-                version +
-                " Cipher text length: " +
-                cipherByteLength +
-                " Serialized byte length: " +
-                length();
-        return sb;
-    }
-
-    public int length() {
-        if (length == 0) {
-            try {
-                final ByteArrayOutputStream temp = new ByteArrayOutputStream(512);
-                ObjectOutputStream oos = new ObjectOutputStream(temp);
-                oos.writeObject(this);
-                length = temp.size();
-            } catch (IOException e) {
-                throw new AssertionError("This is unreachable code");
-            }
-        }
-        return length;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptor.java
deleted file mode 100644
index a4e3eb1..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryObjectEncryptor.java
+++ /dev/null
@@ -1,25 +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.security.repository;
-
-import java.security.KeyManagementException;
-import org.apache.nifi.security.kms.KeyProvider;
-
-public interface RepositoryObjectEncryptor {
-
-    void initialize(KeyProvider keyProvider) throws KeyManagementException;
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryType.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryType.java
deleted file mode 100644
index fdb9254..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryType.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.security.repository;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public enum RepositoryType {
-    CONTENT("Content repository", "content.repository", "stream"),
-    PROVENANCE("Provenance repository", "provenance.repository", "block"),
-    FLOWFILE("Flowfile repository", "flowfile.repository", "stream");
-
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryType.class);
-
-    private final String name;
-    private final String packagePath;
-    private final String encryptionProcess;
-
-    RepositoryType(String name, String packagePath, String encryptionProcess) {
-        this.name = name;
-        this.packagePath = packagePath;
-        this.encryptionProcess = encryptionProcess;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getPackagePath() {
-        return packagePath;
-    }
-
-    public String getEncryptionProcess() {
-        return encryptionProcess;
-    }
-
-    @Override
-    public String toString() {
-        final ToStringBuilder builder = new ToStringBuilder(this);
-        ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE);
-        builder.append("Repository", name);
-        builder.append("Package path", packagePath);
-        builder.append("Encryption process", encryptionProcess);
-        return builder.toString();
-    }
-
-    /**
-     * Uses loose string matching to determine the repository type from input. Matches to
-     * content, provenance, or flowfile repository, or throws an {@link IllegalArgumentException}
-     * on input which cannot be reasonably matched to any known repository type.
-     *
-     * @param input a string containing some description of the repository type
-     * @return a matching instance of the RT enum
-     */
-    public static RepositoryType determineType(String input) {
-        if (StringUtils.isBlank(input)) {
-            throw new IllegalArgumentException("The input cannot be null or empty");
-        }
-        String lowercaseInput = input.toLowerCase().trim();
-
-        // Use loose matching to handle prov[enance] and flow[ ][file]
-        if (lowercaseInput.contains("content")) {
-            return RepositoryType.CONTENT;
-        } else if (lowercaseInput.contains("prov")) {
-            return RepositoryType.PROVENANCE;
-        } else if (lowercaseInput.contains("flow")) {
-            return RepositoryType.FLOWFILE;
-        } else {
-            final String msg = "Could not determine repository type from '" + input + "'";
-            logger.error(msg);
-            throw new IllegalArgumentException(msg);
-        }
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/BlockEncryptionMetadata.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/BlockEncryptionMetadata.java
deleted file mode 100644
index b60a35f..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/BlockEncryptionMetadata.java
+++ /dev/null
@@ -1,38 +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.security.repository.block;
-
-import org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata;
-
-public class BlockEncryptionMetadata extends RepositoryObjectEncryptionMetadata {
-    public BlockEncryptionMetadata() {
-    }
-
-    public BlockEncryptionMetadata(String keyId, String algorithm, byte[] ivBytes, String version, int cipherByteLength) {
-        this.keyId = keyId;
-        this.ivBytes = ivBytes;
-        this.algorithm = algorithm;
-        this.version = version;
-        this.cipherByteLength = cipherByteLength;
-    }
-
-    @Override
-    public String toString() {
-        return "Block Encryption Metadata: " +
-                super.toString();
-    }
-}
\ No newline at end of file
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/RepositoryObjectBlockEncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/RepositoryObjectBlockEncryptor.java
deleted file mode 100644
index 6075623..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/RepositoryObjectBlockEncryptor.java
+++ /dev/null
@@ -1,68 +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.security.repository.block;
-
-import java.security.KeyManagementException;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryObjectEncryptor;
-
-/**
- * Provides an interface for encrypting and decrypting repository objects using a block cipher.
- * This is suited for small objects like flowfile and provenance events. For larger objects (like
- * content claims), see
- * {@link org.apache.nifi.security.repository.stream.RepositoryObjectStreamEncryptor}.
- */
-public interface RepositoryObjectBlockEncryptor extends RepositoryObjectEncryptor {
-
-    /**
-     * Initializes the encryptor with a {@link KeyProvider}.
-     *
-     * @param keyProvider the key provider which will be responsible for accessing keys
-     * @throws KeyManagementException if there is an issue configuring the key provider
-     */
-    void initialize(KeyProvider keyProvider) throws KeyManagementException;
-
-    /**
-     * Encrypts the serialized byte[].
-     *
-     * @param plainRecord the plain record, serialized to a byte[]
-     * @param recordId    an identifier for this record (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return the encrypted record
-     * @throws EncryptionException if there is an issue encrypting this record
-     */
-    byte[] encrypt(byte[] plainRecord, String recordId, String keyId) throws EncryptionException;
-
-    /**
-     * Decrypts the provided byte[] (an encrypted record with accompanying metadata).
-     *
-     * @param encryptedRecord the encrypted record in byte[] form
-     * @param recordId        an identifier for this record (eventId, generated, etc.)
-     * @return the decrypted record
-     * @throws EncryptionException if there is an issue decrypting this record
-     */
-    byte[] decrypt(byte[] encryptedRecord, String recordId) throws EncryptionException;
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    String getNextKeyId() throws KeyManagementException;
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptor.java
deleted file mode 100644
index 17de402..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptor.java
+++ /dev/null
@@ -1,171 +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.security.repository.block.aes;
-
-import java.io.IOException;
-import java.security.KeyManagementException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.repository.AbstractAESEncryptor;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata;
-import org.apache.nifi.security.repository.block.BlockEncryptionMetadata;
-import org.apache.nifi.security.repository.block.RepositoryObjectBlockEncryptor;
-import org.apache.nifi.security.util.EncryptionMethod;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This implementation of the {@link RepositoryObjectBlockEncryptor} handles block data by accepting
- * {@code byte[]} parameters and returning {@code byte[]} which contain the encrypted/decrypted content. This class
- * should be used when a repository needs to persist and retrieve block data with the length known a priori (i.e.
- * provenance records or flowfile attribute maps). For repositories handling streams of data with unknown or large
- * lengths (i.e. content claims), use the
- * {@link org.apache.nifi.security.repository.stream.aes.RepositoryObjectAESCTREncryptor} which does not provide
- * authenticated encryption but performs much better with large data.
- */
-public class RepositoryObjectAESGCMEncryptor extends AbstractAESEncryptor implements RepositoryObjectBlockEncryptor {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryObjectAESGCMEncryptor.class);
-    private static final String ALGORITHM = "AES/GCM/NoPadding";
-
-    // TODO: Increment the version for new implementations?
-    private static final String VERSION = "v1";
-    private static final List<String> SUPPORTED_VERSIONS = Arrays.asList(VERSION);
-    private static final int MIN_METADATA_LENGTH = IV_LENGTH + 3 + 3; // 3 delimiters and 3 non-zero elements
-    private static final int METADATA_DEFAULT_LENGTH = (20 + ALGORITHM.length() + IV_LENGTH + VERSION.length()) * 2; // Default to twice the expected length
-    private static final byte[] SENTINEL = new byte[]{0x01};
-
-    // TODO: Dependency injection for record parser (provenance SENTINEL vs. flowfile)?
-
-    /**
-     * Encrypts the serialized byte[].
-     *
-     * @param plainRecord the plain record, serialized to a byte[]
-     * @param recordId    an identifier for this record (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return the encrypted record
-     * @throws EncryptionException if there is an issue encrypting this record
-     */
-    @Override
-    public byte[] encrypt(byte[] plainRecord, String recordId, String keyId) throws EncryptionException {
-        if (plainRecord == null || CryptoUtils.isEmpty(keyId)) {
-            throw new EncryptionException("The repository object and key ID cannot be missing");
-        }
-
-        if (keyProvider == null || !keyProvider.keyExists(keyId)) {
-            throw new EncryptionException("The requested key ID is not available");
-        } else {
-            byte[] ivBytes = new byte[IV_LENGTH];
-            new SecureRandom().nextBytes(ivBytes);
-            try {
-                // TODO: Add object type to description
-                logger.debug("Encrypting repository object " + recordId + " with key ID " + keyId);
-                Cipher cipher = RepositoryEncryptorUtils.initCipher(aesKeyedCipherProvider, EncryptionMethod.AES_GCM, Cipher.ENCRYPT_MODE, keyProvider.getKey(keyId), ivBytes);
-                ivBytes = cipher.getIV();
-
-                // Perform the actual encryption
-                byte[] cipherBytes = cipher.doFinal(plainRecord);
-
-                // Serialize and concat encryption details fields (keyId, algo, IV, version, CB length) outside of encryption
-                RepositoryObjectEncryptionMetadata metadata = new BlockEncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION, cipherBytes.length);
-                byte[] serializedEncryptionMetadata = RepositoryEncryptorUtils.serializeEncryptionMetadata(metadata);
-                logger.debug("Generated encryption metadata ({} bytes) for repository object {}", serializedEncryptionMetadata.length, recordId);
-
-                // Add the sentinel byte of 0x01
-                // TODO: Remove (required for prov repo but not FF repo)
-                logger.debug("Encrypted repository object " + recordId + " with key ID " + keyId);
-                // return CryptoUtils.concatByteArrays(SENTINEL, serializedEncryptionMetadata, cipherBytes);
-                return CryptoUtils.concatByteArrays(serializedEncryptionMetadata, cipherBytes);
-            } catch (EncryptionException | BadPaddingException | IllegalBlockSizeException | IOException | KeyManagementException e) {
-                final String msg = "Encountered an exception encrypting repository object " + recordId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    /**
-     * Decrypts the provided byte[] (an encrypted record with accompanying metadata).
-     *
-     * @param encryptedRecord the encrypted record in byte[] form
-     * @param recordId        an identifier for this record (eventId, generated, etc.)
-     * @return the decrypted record
-     * @throws EncryptionException if there is an issue decrypting this record
-     */
-    @Override
-    public byte[] decrypt(byte[] encryptedRecord, String recordId) throws EncryptionException {
-        RepositoryObjectEncryptionMetadata metadata = prepareObjectForDecryption(encryptedRecord, recordId, "repository object", SUPPORTED_VERSIONS);
-
-        // TODO: Actually use the version to determine schema, etc.
-
-        if (keyProvider == null || !keyProvider.keyExists(metadata.keyId) || CryptoUtils.isEmpty(metadata.keyId)) {
-            throw new EncryptionException("The requested key ID " + metadata.keyId + " is not available");
-        } else {
-            try {
-                logger.debug("Decrypting repository object " + recordId + " with key ID " + metadata.keyId);
-                EncryptionMethod method = EncryptionMethod.forAlgorithm(metadata.algorithm);
-                Cipher cipher = RepositoryEncryptorUtils.initCipher(aesKeyedCipherProvider, method, Cipher.DECRYPT_MODE, keyProvider.getKey(metadata.keyId), metadata.ivBytes);
-
-                // Strip the metadata away to get just the cipher bytes
-                byte[] cipherBytes = extractCipherBytes(encryptedRecord, metadata);
-
-                // Perform the actual decryption
-                byte[] plainBytes = cipher.doFinal(cipherBytes);
-
-                logger.debug("Decrypted repository object " + recordId + " with key ID " + metadata.keyId);
-                return plainBytes;
-            } catch (EncryptionException | BadPaddingException | IllegalBlockSizeException | KeyManagementException e) {
-                final String msg = "Encountered an exception decrypting repository object " + recordId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    @Override
-    public String getNextKeyId() throws KeyManagementException {
-        if (keyProvider != null) {
-            List<String> availableKeyIds = keyProvider.getAvailableKeyIds();
-            if (!availableKeyIds.isEmpty()) {
-                return availableKeyIds.get(0);
-            }
-        }
-        throw new KeyManagementException("No available key IDs");
-    }
-
-
-    private byte[] extractCipherBytes(byte[] encryptedRecord, RepositoryObjectEncryptionMetadata metadata) {
-        return Arrays.copyOfRange(encryptedRecord, encryptedRecord.length - metadata.cipherByteLength, encryptedRecord.length);
-    }
-
-    @Override
-    public String toString() {
-        return "Repository Object Block Encryptor using AES G/CM with Key Provider: " + keyProvider.toString();
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java
deleted file mode 100644
index 355a170..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java
+++ /dev/null
@@ -1,66 +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.security.repository.config;
-
-import java.util.Map;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.security.util.KeyStoreUtils;
-import org.apache.nifi.util.NiFiProperties;
-
-public class ContentRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
-    /**
-     * Contructor which accepts a {@link NiFiProperties} object and extracts the relevant
-     * property values directly.
-     *
-     * @param niFiProperties the NiFi properties
-     */
-    public ContentRepositoryEncryptionConfiguration(NiFiProperties niFiProperties) {
-        this(niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                niFiProperties.getContentRepositoryEncryptionKeyId(),
-                niFiProperties.getContentRepositoryEncryptionKeys(),
-                niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION),
-                niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD)
-        );
-    }
-
-    /**
-     * Constructor which accepts explicit values for each configuration value.
-     *
-     * @param keyProviderImplementation the key provider implementation class
-     * @param keyProviderLocation the key provider location
-     * @param encryptionKeyId the active encryption key id
-     * @param encryptionKeys the map of available keys
-     * @param repositoryImplementation the repository implementation class
-     * @param keyProviderPassword Key Provider Password used based on provider implementation
-     */
-    public ContentRepositoryEncryptionConfiguration(final String keyProviderImplementation,
-                                                    final String keyProviderLocation,
-                                                    final String encryptionKeyId,
-                                                    final Map<String, String> encryptionKeys,
-                                                    final String repositoryImplementation,
-                                                    final String keyProviderPassword) {
-        this.keyProviderImplementation = keyProviderImplementation;
-        this.keyProviderLocation = keyProviderLocation;
-        this.encryptionKeyId = encryptionKeyId;
-        this.encryptionKeys = encryptionKeys;
-        this.repositoryImplementation = repositoryImplementation;
-        this.repositoryType = RepositoryType.CONTENT;
-        this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
-        this.keyProviderPassword = keyProviderPassword;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java
deleted file mode 100644
index 0c5666c..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java
+++ /dev/null
@@ -1,69 +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.security.repository.config;
-
-import java.util.Map;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.security.util.KeyStoreUtils;
-import org.apache.nifi.util.NiFiProperties;
-
-public class FlowFileRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
-    /**
-     * Constructor which accepts a {@link NiFiProperties} object and extracts the relevant
-     * property values directly.
-     *
-     * @param niFiProperties the NiFi properties
-     */
-    public FlowFileRepositoryEncryptionConfiguration(NiFiProperties niFiProperties) {
-        this(niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                niFiProperties.getFlowFileRepoEncryptionKeyId(),
-                niFiProperties.getFlowFileRepoEncryptionKeys(),
-                niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION),
-                niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD)
-        );
-    }
-
-    /**
-     * Constructor which accepts explicit values for each configuration value. This differs
-     * from {@link ContentRepositoryEncryptionConfiguration} and {@link ProvenanceRepositoryEncryptionConfiguration} because the repository implementation
-     * does not change for an encrypted flowfile repository, only the write-ahead log
-     * implementation ({@link NiFiProperties#FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION}).
-     *
-     * @param keyProviderImplementation the key provider implementation class
-     * @param keyProviderLocation the key provider location
-     * @param encryptionKeyId the active encryption key id
-     * @param encryptionKeys the map of available keys
-     * @param repositoryImplementation the write ahead log implementation
-     * @param keyProviderPassword Key Provider Password
-     */
-    public FlowFileRepositoryEncryptionConfiguration(final String keyProviderImplementation,
-                                                     final String keyProviderLocation,
-                                                     final String encryptionKeyId,
-                                                     final Map<String, String> encryptionKeys,
-                                                     final String repositoryImplementation,
-                                                     final String keyProviderPassword) {
-        this.keyProviderImplementation = keyProviderImplementation;
-        this.keyProviderLocation = keyProviderLocation;
-        this.encryptionKeyId = encryptionKeyId;
-        this.encryptionKeys = encryptionKeys;
-        this.repositoryImplementation = repositoryImplementation;
-        this.repositoryType = RepositoryType.FLOWFILE;
-        this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
-        this.keyProviderPassword = keyProviderPassword;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java
deleted file mode 100644
index 66c6283..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java
+++ /dev/null
@@ -1,66 +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.security.repository.config;
-
-import java.util.Map;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.security.util.KeyStoreUtils;
-import org.apache.nifi.util.NiFiProperties;
-
-public class ProvenanceRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
-    /**
-     * Constructor which accepts a {@link NiFiProperties} object and extracts the relevant
-     * property values directly.
-     *
-     * @param niFiProperties the NiFi properties
-     */
-    public ProvenanceRepositoryEncryptionConfiguration(NiFiProperties niFiProperties) {
-        this(niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS),
-                niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION),
-                niFiProperties.getProvenanceRepoEncryptionKeyId(),
-                niFiProperties.getProvenanceRepoEncryptionKeys(),
-                niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS),
-                niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD)
-        );
-    }
-
-    /**
-     * Constructor which accepts explicit values for each configuration value.
-     *
-     * @param keyProviderImplementation the key provider implementation class
-     * @param keyProviderLocation the key provider location
-     * @param encryptionKeyId the active encryption key id
-     * @param encryptionKeys the map of available keys
-     * @param repositoryImplementation the repository implementation class
-     * @param keyProviderPassword Key Provider Password used based on provider implementation
-     */
-    public ProvenanceRepositoryEncryptionConfiguration(final String keyProviderImplementation,
-                                                       final String keyProviderLocation,
-                                                       final String encryptionKeyId,
-                                                       final Map<String, String> encryptionKeys,
-                                                       final String repositoryImplementation,
-                                                       final String keyProviderPassword) {
-        this.keyProviderImplementation = keyProviderImplementation;
-        this.keyProviderLocation = keyProviderLocation;
-        this.encryptionKeyId = encryptionKeyId;
-        this.encryptionKeys = encryptionKeys;
-        this.repositoryImplementation = repositoryImplementation;
-        this.repositoryType = RepositoryType.CONTENT;
-        this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
-        this.keyProviderPassword = keyProviderPassword;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java
deleted file mode 100644
index 8b3cd2c..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java
+++ /dev/null
@@ -1,132 +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.security.repository.config;
-
-import java.util.Map;
-import org.apache.nifi.security.kms.FileBasedKeyProvider;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.kms.StaticKeyProvider;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.util.NiFiProperties;
-
-/**
- * Abstract class which defines the method contracts for various repository encryption configuration
- * values. The implementing classes will act as data containers for the encryption configs when
- * initializing the repositories.
- */
-public abstract class RepositoryEncryptionConfiguration {
-    String keyProviderImplementation;
-    String keyProviderLocation;
-    String encryptionKeyId;
-    Map<String, String> encryptionKeys;
-    String repositoryImplementation;
-    RepositoryType repositoryType;
-    String keyStoreType;
-    String keyProviderPassword;
-
-    /**
-     * Returns the class name of the {@link KeyProvider} implementation used.
-     *
-     * @return the class of the key provider
-     */
-    public String getKeyProviderImplementation() {
-        return keyProviderImplementation;
-    }
-
-    /**
-     * Returns the location of the key provider. For a
-     * {@link StaticKeyProvider} this will be null; for all
-     * others, it will be the location (file path/URL/etc.) to access the key definitions.
-     *
-     * @return the file, URL, etc. where the keys are defined
-     */
-    public String getKeyProviderLocation() {
-        return keyProviderLocation;
-    }
-
-    /**
-     * Returns the "active" encryption key id.
-     *
-     * @return the key id
-     */
-    public String getEncryptionKeyId() {
-        return encryptionKeyId;
-    }
-
-    /**
-     * Returns a map of all available encryption keys indexed by the key id if using
-     * {@link StaticKeyProvider}. For
-     * {@link FileBasedKeyProvider}, this method will return an
-     * empty map because the keys must be loaded using the {@code root key} to decrypt them
-     *
-     * @return a map of key ids & keys
-     * @see NiFiProperties#getContentRepositoryEncryptionKeys()
-     */
-    public Map<String, String> getEncryptionKeys() {
-        return encryptionKeys;
-    }
-
-    /**
-     * Returns the class name for the repository implementation.
-     *
-     * @return the repository class
-     */
-    public String getRepositoryImplementation() {
-        return repositoryImplementation;
-    }
-
-    /**
-     * Returns the {@link RepositoryType} enum identifying this repository. Useful for
-     * programmatically determining the kind of repository being configured.
-     *
-     * @return the repository type
-     */
-    public RepositoryType getRepositoryType() {
-        return repositoryType;
-    }
-
-    /**
-     * Get Key Store Type for Key Store implementation
-     *
-     * @return Key Store Type
-     */
-    public String getKeyStoreType() {
-        return keyStoreType;
-    }
-
-    /**
-     * Get Key Provider Password
-     *
-     * @return Key Provider Password
-     */
-    public String getKeyProviderPassword() {
-        return keyProviderPassword;
-    }
-
-    public static RepositoryEncryptionConfiguration fromNiFiProperties(NiFiProperties niFiProperties, RepositoryType repositoryType) {
-        switch (repositoryType) {
-            case CONTENT:
-                return new ContentRepositoryEncryptionConfiguration(niFiProperties);
-            case PROVENANCE:
-                return new ProvenanceRepositoryEncryptionConfiguration(niFiProperties);
-            case FLOWFILE:
-                return new FlowFileRepositoryEncryptionConfiguration(niFiProperties);
-            default:
-                throw new IllegalArgumentException("The specified repository does not support encryption");
-        }
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/RepositoryObjectStreamEncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/RepositoryObjectStreamEncryptor.java
deleted file mode 100644
index c271efb..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/RepositoryObjectStreamEncryptor.java
+++ /dev/null
@@ -1,71 +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.security.repository.stream;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.KeyManagementException;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryObjectEncryptor;
-
-
-/**
- * Provides an interface for encrypting and decrypting repository objects using a stream cipher.
- * This is suited for large objects like content claims. For small objects (like flowfile and
- * provenance events), see
- * {@link org.apache.nifi.security.repository.block.RepositoryObjectBlockEncryptor}.
- */
-public interface RepositoryObjectStreamEncryptor extends RepositoryObjectEncryptor {
-
-    /**
-     * Initializes the encryptor with a {@link KeyProvider}.
-     *
-     * @param keyProvider the key provider which will be responsible for accessing keys
-     * @throws KeyManagementException if there is an issue configuring the key provider
-     */
-    void initialize(KeyProvider keyProvider) throws KeyManagementException;
-
-    /**
-     * Encrypts the serialized byte[].
-     *
-     * @param plainRecord the plain record, serialized to a byte[]
-     * @param recordId    an identifier for this record (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return the encrypted record
-     * @throws EncryptionException if there is an issue encrypting this record
-     */
-    OutputStream encrypt(OutputStream plainRecord, String recordId, String keyId) throws EncryptionException;
-
-    /**
-     * Decrypts the provided byte[] (an encrypted record with accompanying metadata).
-     *
-     * @param encryptedRecord the encrypted record in byte[] form
-     * @param recordId        an identifier for this record (eventId, generated, etc.)
-     * @return the decrypted record
-     * @throws EncryptionException if there is an issue decrypting this record
-     */
-    InputStream decrypt(InputStream encryptedRecord, String recordId) throws EncryptionException;
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    String getNextKeyId() throws KeyManagementException;
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptor.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptor.java
deleted file mode 100644
index f51e78a..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptor.java
+++ /dev/null
@@ -1,151 +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.security.repository.stream.aes;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.KeyManagementException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.repository.AbstractAESEncryptor;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata;
-import org.apache.nifi.security.repository.StreamingEncryptionMetadata;
-import org.apache.nifi.security.repository.stream.RepositoryObjectStreamEncryptor;
-import org.apache.nifi.security.util.EncryptionMethod;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This implementation of the {@link RepositoryObjectStreamEncryptor} handles streaming data by accepting
- * {@link OutputStream} and {@link InputStream} parameters and returning custom implementations which wrap the normal
- * behavior with encryption/decryption logic transparently. This class should be used when a repository needs to persist
- * and retrieve streaming data (i.e. content claims). For repositories handling limited blocks of data with the length
- * known a priori (i.e. provenance records or flowfile attribute maps), use the
- * {@link org.apache.nifi.security.repository.block.aes.RepositoryObjectAESGCMEncryptor} which will provide
- * authenticated encryption.
- */
-public class RepositoryObjectAESCTREncryptor extends AbstractAESEncryptor implements RepositoryObjectStreamEncryptor {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryObjectAESCTREncryptor.class);
-    private static final byte[] EM_START_SENTINEL = new byte[]{0x00, 0x00};
-    private static String ALGORITHM = "AES/CTR/NoPadding";
-    private static final String VERSION = "v1";
-    private static final List<String> SUPPORTED_VERSIONS = Arrays.asList(VERSION);
-
-    /**
-     * Returns an {@link OutputStream} which encrypts the content of the provided OutputStream. This method works on
-     * streams to allow for streaming data rather than blocks of bytes of a known length. It is recommended to use this
-     * for data like flowfile content claims, rather than provenance records or flowfile attribute maps.
-     *
-     * @param plainStream the plain OutputStream which is being written to
-     * @param streamId    an identifier for this stream (eventId, generated, etc.)
-     * @param keyId       the ID of the key to use
-     * @return a stream which will encrypt the data and include the {@link RepositoryObjectEncryptionMetadata}
-     * @throws EncryptionException if there is an issue encrypting this streaming repository object
-     */
-    @Override
-    public OutputStream encrypt(OutputStream plainStream, String streamId, String keyId) throws EncryptionException {
-        if (plainStream == null || CryptoUtils.isEmpty(keyId)) {
-            throw new EncryptionException("The streaming repository object and key ID cannot be missing");
-        }
-
-        if (keyProvider == null || !keyProvider.keyExists(keyId)) {
-            throw new EncryptionException("The requested key ID is not available");
-        } else {
-            byte[] ivBytes = new byte[IV_LENGTH];
-            new SecureRandom().nextBytes(ivBytes);
-            try {
-                logger.debug("Encrypting streaming repository object " + streamId + " with key ID " + keyId);
-                Cipher cipher = RepositoryEncryptorUtils.initCipher(aesKeyedCipherProvider, EncryptionMethod.forAlgorithm(ALGORITHM), Cipher.ENCRYPT_MODE, keyProvider.getKey(keyId), ivBytes);
-                ivBytes = cipher.getIV();
-
-                // Prepare the output stream for the actual encryption
-                CipherOutputStream cipherOutputStream = new CipherOutputStream(plainStream, cipher);
-
-                // Serialize and concat encryption details fields (keyId, algo, IV, version, CB length) outside of encryption
-                RepositoryObjectEncryptionMetadata metadata = new StreamingEncryptionMetadata(keyId, ALGORITHM, ivBytes, VERSION);
-                byte[] serializedEncryptionMetadata = RepositoryEncryptorUtils.serializeEncryptionMetadata(metadata);
-
-                // Write the SENTINEL bytes and the encryption metadata to the raw output stream
-                plainStream.write(EM_START_SENTINEL);
-                plainStream.write(serializedEncryptionMetadata);
-                plainStream.flush();
-
-                logger.debug("Encrypted streaming repository object " + streamId + " with key ID " + keyId);
-                return cipherOutputStream;
-            } catch (EncryptionException | IOException | KeyManagementException e) {
-                final String msg = "Encountered an exception encrypting streaming repository object " + streamId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    /**
-     * Returns an {@link InputStream} which decrypts the content of the provided InputStream. The provided InputStream
-     * must contain a valid {@link RepositoryObjectEncryptionMetadata} object at the start of the stream. This method
-     * works on streams to allow for streaming data rather than blocks of bytes of a known length. It is recommended to
-     * use this for data like flowfile content claims, rather than provenance records or flowfile attribute maps.
-     *
-     * @param encryptedInputStream the encrypted InputStream (starting with the plaintext ROEM) which is being read from
-     * @param streamId             an identifier for this stream (eventId, generated, etc.)
-     * @return a stream which will decrypt the data based on the data in the {@link RepositoryObjectEncryptionMetadata}
-     * @throws EncryptionException if there is an issue decrypting this streaming repository object
-     */
-    @Override
-    public InputStream decrypt(InputStream encryptedInputStream, String streamId) throws EncryptionException {
-        RepositoryObjectEncryptionMetadata metadata = prepareObjectForDecryption(encryptedInputStream, streamId, "streaming repository object", SUPPORTED_VERSIONS);
-
-        if (keyProvider == null || !keyProvider.keyExists(metadata.keyId) || CryptoUtils.isEmpty(metadata.keyId)) {
-            throw new EncryptionException("The requested key ID " + metadata.keyId + " is not available");
-        } else {
-            try {
-                logger.debug("Decrypting streaming repository object with ID " + streamId + " with key ID " + metadata.keyId);
-                EncryptionMethod method = EncryptionMethod.forAlgorithm(metadata.algorithm);
-                Cipher cipher = RepositoryEncryptorUtils.initCipher(aesKeyedCipherProvider, method, Cipher.DECRYPT_MODE, keyProvider.getKey(metadata.keyId), metadata.ivBytes);
-
-                // Return a new CipherInputStream wrapping the encrypted stream at the present location
-                CipherInputStream cipherInputStream = new CipherInputStream(encryptedInputStream, cipher);
-
-                logger.debug("Decrypted streaming repository object with ID " + streamId + " with key ID " + metadata.keyId);
-                return cipherInputStream;
-            } catch (EncryptionException | KeyManagementException e) {
-                final String msg = "Encountered an exception decrypting streaming repository object with ID " + streamId;
-                logger.error(msg, e);
-                throw new EncryptionException(msg, e);
-            }
-        }
-    }
-
-    /**
-     * Returns a valid key identifier for this encryptor (valid for encryption and decryption) or throws an exception if none are available.
-     *
-     * @return the key ID
-     * @throws KeyManagementException if no available key IDs are valid for both operations
-     */
-    @Override
-    public String getNextKeyId() throws KeyManagementException {
-        return null;
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy
deleted file mode 100644
index 1472791..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy
+++ /dev/null
@@ -1,532 +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.security.kms
-
-import org.apache.commons.lang3.SystemUtils
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.attribute.PosixFilePermission
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class CryptoUtilsTest {
-    private static final Logger logger = LoggerFactory.getLogger(CryptoUtilsTest.class)
-
-    private static final String KEY_ID = "K1"
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static
-    final Set<PosixFilePermission> ALL_POSIX_ATTRS = PosixFilePermission.values() as Set<PosixFilePermission>
-
-    @ClassRule
-    public static TemporaryFolder tempFolder = new TemporaryFolder()
-
-    @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 {
-        tempFolder.create()
-    }
-
-    @After
-    void tearDown() throws Exception {
-        tempFolder?.delete()
-    }
-
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    private static boolean isRootUser() {
-        ProcessBuilder pb = new ProcessBuilder(["id", "-u"])
-        Process process = pb.start()
-        InputStream responseStream = process.getInputStream()
-        BufferedReader responseReader = new BufferedReader(new InputStreamReader(responseStream))
-        responseReader.text.trim() == "0"
-    }
-
-    @Test
-    void testShouldConcatenateByteArrays() {
-        // Arrange
-        byte[] bytes1 = "These are some bytes".getBytes(StandardCharsets.UTF_8)
-        byte[] bytes2 = "These are some other bytes".getBytes(StandardCharsets.UTF_8)
-        final byte[] EXPECTED_CONCATENATED_BYTES = ((bytes1 as List) << (bytes2 as List)).flatten() as byte[]
-        logger.info("Expected concatenated bytes: ${Hex.toHexString(EXPECTED_CONCATENATED_BYTES)}")
-
-        // Act
-        byte[] concat = CryptoUtils.concatByteArrays(bytes1, bytes2)
-        logger.info("  Actual concatenated bytes: ${Hex.toHexString(concat)}")
-
-        // Assert
-        assert concat == EXPECTED_CONCATENATED_BYTES
-    }
-
-    @Test
-    void testShouldValidateStaticKeyProvider() {
-        // Arrange
-        String staticProvider = StaticKeyProvider.class.name
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(staticProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX])
-        logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert keyProviderIsValid
-    }
-
-    @Test
-    void testShouldValidateLegacyStaticKeyProvider() {
-        // Arrange
-        String staticProvider = StaticKeyProvider.class.name.replaceFirst("security.kms", "provenance")
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(staticProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX])
-        logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert keyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateStaticKeyProviderMissingKeyId() {
-        // Arrange
-        String staticProvider = StaticKeyProvider.class.name
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(staticProvider, providerLocation, null, [(KEY_ID): KEY_HEX])
-        logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${null} / ${KEY_HEX} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !keyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateStaticKeyProviderMissingKey() {
-        // Arrange
-        String staticProvider = StaticKeyProvider.class.name
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(staticProvider, providerLocation, KEY_ID, null)
-        logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${null} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !keyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateStaticKeyProviderWithInvalidKey() {
-        // Arrange
-        String staticProvider = StaticKeyProvider.class.name
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(staticProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX[0..<-2]])
-        logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX[0..<-2]} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !keyProviderIsValid
-    }
-
-    @Test
-    void testShouldValidateFileBasedKeyProvider() {
-        // Arrange
-        String fileBasedProvider = FileBasedKeyProvider.class.name
-        File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
-        String providerLocation = fileBasedProviderFile.path
-        logger.info("Created temporary file based key provider: ${providerLocation}")
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(fileBasedProvider, providerLocation, KEY_ID, null)
-        logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${null} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert keyProviderIsValid
-    }
-
-    @Test
-    void testShouldValidateLegacyFileBasedKeyProvider() {
-        // Arrange
-        String fileBasedProvider = FileBasedKeyProvider.class.name.replaceFirst("security.kms", "provenance")
-        File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
-        String providerLocation = fileBasedProviderFile.path
-        logger.info("Created temporary file based key provider: ${providerLocation}")
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(fileBasedProvider, providerLocation, KEY_ID, null)
-        logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${null} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert keyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateMissingFileBasedKeyProvider() {
-        // Arrange
-        String fileBasedProvider = FileBasedKeyProvider.class.name
-        File fileBasedProviderFile = new File(tempFolder.root, "filebased_missing.kp")
-        String providerLocation = fileBasedProviderFile.path
-        logger.info("Created (no actual file) temporary file based key provider: ${providerLocation}")
-
-        // Act
-        String missingLocation = providerLocation
-        boolean missingKeyProviderIsValid = CryptoUtils.isValidKeyProvider(fileBasedProvider, missingLocation, KEY_ID, null)
-        logger.info("Key Provider ${fileBasedProvider} with location ${missingLocation} and keyId ${KEY_ID} / ${null} is ${missingKeyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !missingKeyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateUnreadableFileBasedKeyProvider() {
-        // Arrange
-        Assume.assumeFalse("This test does not run on Windows", SystemUtils.IS_OS_WINDOWS)
-        Assume.assumeFalse("This test does not run for root users", isRootUser())
-
-        String fileBasedProvider = FileBasedKeyProvider.class.name
-        File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
-        String providerLocation = fileBasedProviderFile.path
-        logger.info("Created temporary file based key provider: ${providerLocation}")
-
-        // Make it unreadable
-        markFileUnreadable(fileBasedProviderFile)
-
-        // Act
-        boolean unreadableKeyProviderIsValid = CryptoUtils.isValidKeyProvider(fileBasedProvider, providerLocation, KEY_ID, null)
-        logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${null} is ${unreadableKeyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !unreadableKeyProviderIsValid
-
-        // Make the file deletable so cleanup can occur
-        markFileReadable(fileBasedProviderFile)
-    }
-
-    private static void markFileReadable(File fileBasedProviderFile) {
-        if (SystemUtils.IS_OS_WINDOWS) {
-            fileBasedProviderFile.setReadable(true, false)
-        } else {
-            Files.setPosixFilePermissions(fileBasedProviderFile.toPath(), ALL_POSIX_ATTRS)
-        }
-    }
-
-    private static void markFileUnreadable(File fileBasedProviderFile) {
-        if (SystemUtils.IS_OS_WINDOWS) {
-            fileBasedProviderFile.setReadable(false, false)
-        } else {
-            Files.setPosixFilePermissions(fileBasedProviderFile.toPath(), [] as Set<PosixFilePermission>)
-        }
-    }
-
-    @Test
-    void testShouldNotValidateFileBasedKeyProviderMissingKeyId() {
-        // Arrange
-        String fileBasedProvider = FileBasedKeyProvider.class.name
-        File fileBasedProviderFile = tempFolder.newFile("missing_key_id.kp")
-        String providerLocation = fileBasedProviderFile.path
-        logger.info("Created temporary file based key provider: ${providerLocation}")
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(fileBasedProvider, providerLocation, null, null)
-        logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${null} / ${null} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !keyProviderIsValid
-    }
-
-    @Test
-    void testShouldNotValidateUnknownKeyProvider() {
-        // Arrange
-        String providerImplementation = "org.apache.nifi.provenance.ImaginaryKeyProvider"
-        String providerLocation = null
-
-        // Act
-        boolean keyProviderIsValid = CryptoUtils.isValidKeyProvider(providerImplementation, providerLocation, KEY_ID, null)
-        logger.info("Key Provider ${providerImplementation} with location ${providerLocation} and keyId ${KEY_ID} / ${null} is ${keyProviderIsValid ? "valid" : "invalid"}")
-
-        // Assert
-        assert !keyProviderIsValid
-    }
-
-    @Test
-    void testShouldValidateKey() {
-        // Arrange
-        String validKey = KEY_HEX
-        String validLowercaseKey = KEY_HEX.toLowerCase()
-
-        String tooShortKey = KEY_HEX[0..<-2]
-        String tooLongKey = KEY_HEX + KEY_HEX // Guaranteed to be 2x the max valid key length
-        String nonHexKey = KEY_HEX.replaceFirst(/A/, "X")
-
-        def validKeys = [validKey, validLowercaseKey]
-        def invalidKeys = [tooShortKey, tooLongKey, nonHexKey]
-
-        // If unlimited strength is available, also validate 128 and 196 bit keys
-        if (isUnlimitedStrengthCryptoAvailable()) {
-            validKeys << KEY_HEX_128
-            validKeys << KEY_HEX_256[0..<48]
-        } else {
-            invalidKeys << KEY_HEX_256[0..<48]
-            invalidKeys << KEY_HEX_256
-        }
-
-        // Act
-        def validResults = validKeys.collect { String key ->
-            logger.info("Validating ${key}")
-            CryptoUtils.keyIsValid(key)
-        }
-
-        def invalidResults = invalidKeys.collect { String key ->
-            logger.info("Validating ${key}")
-            CryptoUtils.keyIsValid(key)
-        }
-
-        // Assert
-        assert validResults.every()
-        assert invalidResults.every { !it }
-    }
-
-    @Test
-    void testShouldEvaluateConstantTimeEqualsForStrings() {
-        // Arrange
-        String plaintext = "This is a short string."
-        String firstCharOff = "this is a short string."
-        String lastCharOff = "This is a short string,"
-
-        final int ITERATIONS = 10_000
-        final int WARM_UP_ITERATIONS = 1_000 * ITERATIONS
-
-        def scenarios = ["identical": plaintext, "first off": firstCharOff, "last off": lastCharOff]
-        def results = [:]
-        def timings = [:]
-
-        boolean isEqual = true
-        long nanos = 0
-        long scNanos = 0
-
-        // Prepare the JVM
-        (WARM_UP_ITERATIONS).times { int i ->
-            def scIterationNanos = time("warm up sc") {
-                assert plaintext == plaintext
-            }
-            scNanos += scIterationNanos
-            def iterationNanos = time("warm up") {
-                assert CryptoUtils.constantTimeEquals(plaintext, plaintext)
-            }
-            nanos += iterationNanos
-        }
-        logger.info("${"warm up sc".padLeft(10)}: ${nanos} ns (avg: ${nanos / (WARM_UP_ITERATIONS)} ns)")
-        logger.info("${"warm up".padLeft(10)}: ${scNanos} ns (avg: ${scNanos / (WARM_UP_ITERATIONS)} ns)")
-
-        // Act
-        scenarios.each { String scenario, String value ->
-            isEqual = true
-            scNanos = 0
-            nanos = 0
-            ITERATIONS.times { int i ->
-                def scIterationNanos = time(scenario + " sc") {
-                    (plaintext == value)
-                }
-                scNanos += scIterationNanos
-                def iterationNanos = time(scenario) {
-                    isEqual = CryptoUtils.constantTimeEquals(plaintext, value)
-                }
-                nanos += iterationNanos
-            }
-            def scenarioWidth = 16
-            logger.info("${(scenario + " sc").padLeft(scenarioWidth)}: ${scNanos} ns (avg: ${scNanos / ITERATIONS} ns)")
-            logger.info("${scenario.padLeft(scenarioWidth)}: ${nanos} ns (avg: ${nanos / ITERATIONS} ns)")
-            results[scenario] = isEqual
-            timings[scenario] = nanos
-        }
-
-        // Assert
-        assert results["identical"]
-        assert !results["first off"]
-        assert !results["last off"]
-
-        // TODO: Assert timings are within std dev?
-    }
-
-    @Test
-    void testShouldEvaluateConstantTimeEqualsForBytes() {
-        // Arrange
-        String plaintext = "This is a short string."
-        String firstCharOff = "this is a short string."
-        String lastCharOff = "This is a short string,"
-
-        final int ITERATIONS = 10_000
-        final int WARM_UP_ITERATIONS = 1_000 * ITERATIONS
-
-        def scenarios = ["identical": plaintext, "first off": firstCharOff, "last off": lastCharOff]
-        def results = [:]
-        def timings = [:]
-
-        boolean isEqual = true
-        long nanos = 0
-        long scNanos = 0
-
-        // Prepare the JVM
-        byte[] plaintextBytes = plaintext.getBytes("UTF-8")
-        (WARM_UP_ITERATIONS).times { int i ->
-            def scIterationNanos = time("warm up sc") {
-                assert plaintext == plaintext
-            }
-            scNanos += scIterationNanos
-            def iterationNanos = time("warm up") {
-                assert CryptoUtils.constantTimeEquals(plaintextBytes, plaintextBytes)
-            }
-            nanos += iterationNanos
-        }
-        logger.info("${"warm up sc".padLeft(10)}: ${nanos} ns (avg: ${nanos / (WARM_UP_ITERATIONS)} ns)")
-        logger.info("${"warm up".padLeft(10)}: ${scNanos} ns (avg: ${scNanos / (WARM_UP_ITERATIONS)} ns)")
-
-        // Act
-        scenarios.each { String scenario, String value ->
-            isEqual = true
-            scNanos = 0
-            nanos = 0
-            byte[] valueBytes = value.getBytes("UTF-8")
-            ITERATIONS.times { int i ->
-                def scIterationNanos = time(scenario + " sc") {
-                    (plaintextBytes == valueBytes)
-                }
-                scNanos += scIterationNanos
-                def iterationNanos = time(scenario) {
-                    isEqual = CryptoUtils.constantTimeEquals(plaintextBytes, valueBytes)
-                }
-                nanos += iterationNanos
-            }
-            def scenarioWidth = 16
-            logger.info("${(scenario + " sc").padLeft(scenarioWidth)}: ${scNanos} ns (avg: ${scNanos / ITERATIONS} ns)")
-            logger.info("${scenario.padLeft(scenarioWidth)}: ${nanos} ns (avg: ${nanos / ITERATIONS} ns)")
-            results[scenario] = isEqual
-            timings[scenario] = nanos
-        }
-
-        // Assert
-        assert results["identical"]
-        assert !results["first off"]
-        assert !results["last off"]
-
-        // TODO: Assert timings are within std dev?
-    }
-
-    @Test
-    void testShouldEvaluateConstantTimeEqualsForChars() {
-        // Arrange
-        String plaintext = "This is a short string."
-        String firstCharOff = "this is a short string."
-        String lastCharOff = "This is a short string,"
-
-        final int ITERATIONS = 10_000
-        final int WARM_UP_ITERATIONS = 1_000 * ITERATIONS
-
-        def scenarios = ["identical": plaintext, "first off": firstCharOff, "last off": lastCharOff]
-        def results = [:]
-        def timings = [:]
-
-        boolean isEqual = true
-        long nanos = 0
-        long scNanos = 0
-
-        // Prepare the JVM
-        def plaintextChars = plaintext.chars
-        (WARM_UP_ITERATIONS).times { int i ->
-            def scIterationNanos = time("warm up sc") {
-                assert plaintext == plaintext
-            }
-            scNanos += scIterationNanos
-            def iterationNanos = time("warm up") {
-                assert CryptoUtils.constantTimeEquals(plaintextChars, plaintextChars)
-            }
-            nanos += iterationNanos
-        }
-        logger.info("${"warm up sc".padLeft(10)}: ${nanos} ns (avg: ${nanos / (WARM_UP_ITERATIONS)} ns)")
-        logger.info("${"warm up".padLeft(10)}: ${scNanos} ns (avg: ${scNanos / (WARM_UP_ITERATIONS)} ns)")
-
-        // Act
-        scenarios.each { String scenario, String value ->
-            isEqual = true
-            scNanos = 0
-            nanos = 0
-            def valueChars = value.chars
-            ITERATIONS.times { int i ->
-                def scIterationNanos = time(scenario + " sc") {
-                    (plaintextChars == valueChars)
-                }
-                scNanos += scIterationNanos
-                def iterationNanos = time(scenario) {
-                    isEqual = CryptoUtils.constantTimeEquals(plaintextChars, valueChars)
-                }
-                nanos += iterationNanos
-            }
-            def scenarioWidth = 16
-            logger.info("${(scenario + " sc").padLeft(scenarioWidth)}: ${scNanos} ns (avg: ${scNanos / ITERATIONS} ns)")
-            logger.info("${scenario.padLeft(scenarioWidth)}: ${nanos} ns (avg: ${nanos / ITERATIONS} ns)")
-            results[scenario] = isEqual
-            timings[scenario] = nanos
-        }
-
-        // Assert
-        assert results["identical"]
-        assert !results["first off"]
-        assert !results["last off"]
-
-        // TODO: Assert timings are within std dev?
-    }
-
-    private static long time(String name = "closure", Closure closure) {
-        long start = System.nanoTime()
-        closure.run()
-        long end = System.nanoTime()
-        end - start
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/AbstractAESEncryptorTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/AbstractAESEncryptorTest.groovy
deleted file mode 100644
index a8929ae..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/AbstractAESEncryptorTest.groovy
+++ /dev/null
@@ -1,160 +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.security.repository
-
-import org.apache.nifi.security.kms.EncryptionException
-import org.apache.nifi.security.util.EncryptionMethod
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-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 AbstractAESEncryptorTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(AbstractAESEncryptor.class)
-
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static final String KEY_ID = "K1"
-
-    private static final String LOG_PACKAGE = "org.slf4j.simpleLogger.log.org.apache.nifi.security.repository"
-    private static String ORIGINAL_LOG_LEVEL
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        ORIGINAL_LOG_LEVEL = System.getProperty(LOG_PACKAGE)
-        System.setProperty(LOG_PACKAGE, "DEBUG")
-
-        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() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty(LOG_PACKAGE, ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    /**
-     * This serialized input was generated by code available in commit 094fea6d1c1f798c4e54cfaf0998416e6c59e41a.
-     *
-     * @return a valid {@link InputStream} containing a {@link RepositoryObjectEncryptionMetadata} and ciphertext
-     */
-    private static InputStream formCiphertextStream() {
-        byte[] encryptedBytes = Hex.decode("0000aced00057372003f6f72672e6170616368652e6e6966692e73656375726974792e7265706" +
-                "f7369746f72792e53747265616d696e67456e6372797074696f6e4d6574616461746118466982894d442c020000787200466f726" +
-                "72e6170616368652e6e6966692e73656375726974792e7265706f7369746f72792e5265706f7369746f72794f626a656374456e6" +
-                "372797074696f6e4d657461646174619f4328584edfdf08020005490010636970686572427974654c656e6774684c0009616c676" +
-                "f726974686d7400124c6a6176612f6c616e672f537472696e673b5b0007697642797465737400025b424c00056b6579496471007" +
-                "e00024c000776657273696f6e71007e00027870ffffffff7400114145532f4354522f4e6f50616464696e67757200025b42acf31" +
-                "7f8060854e00200007870000000109a796446562404a917b9b06479be0f2f7400024b3174000276312356e626790d1b188345a3f" +
-                "4b5e52cfa4641ed18647caec833ff6a26")
-        new ByteArrayInputStream(encryptedBytes)
-    }
-
-    /**
-     * This serialized input was generated by code available in commit 094fea6d1c1f798c4e54cfaf0998416e6c59e41a.
-     *
-     * @return a valid {@code byte[]} containing a {@link RepositoryObjectEncryptionMetadata} and ciphertext
-     */
-    private static byte[] formCiphertextBytes() {
-        byte[] encryptedBytes = Hex.decode("aced0005737200416f72672e6170616368652e6e6966692e73656375726974792e7265706f736" +
-                "9746f72792e626c6f636b2e426c6f636b456e6372797074696f6e4d6574616461746136c69c49d597a81f020000787200466f726" +
-                "72e6170616368652e6e6966692e73656375726974792e7265706f7369746f72792e5265706f7369746f72794f626a656374456e6" +
-                "372797074696f6e4d657461646174619f4328584edfdf08020005490010636970686572427974654c656e6774684c0009616c676" +
-                "f726974686d7400124c6a6176612f6c616e672f537472696e673b5b0007697642797465737400025b424c00056b6579496471007" +
-                "e00024c000776657273696f6e71007e000278700000002c7400114145532f47434d2f4e6f50616464696e67757200025b42acf31" +
-                "7f8060854e002000078700000001054d7d66e359a194854af6d8211def7a47400024b31740002763145c7cfddb413ad677c5e5e4" +
-                "ba59993db96df90cfff386a62bd9db094c62f752386017d28e267a3eb903090ca")
-        encryptedBytes
-    }
-
-    @Test
-    void testShouldPrepareInputStreamForDecryption() {
-        // Arrange
-        InputStream ciphertextStream = formCiphertextStream()
-
-        // Act
-        RepositoryObjectEncryptionMetadata metadata = AbstractAESEncryptor.prepareObjectForDecryption(ciphertextStream, "S1", "streaming repository object", ["v1"])
-        logger.info("Extracted ROEM: ${metadata}")
-
-        // Assert
-        assert metadata.keyId == KEY_ID
-        assert metadata.algorithm == EncryptionMethod.AES_CTR.algorithm
-        assert metadata.version == "v1"
-    }
-
-    @Test
-    void testShouldPrepareByteArrayForDecryption() {
-        // Arrange
-        byte[] ciphertextBytes = formCiphertextBytes()
-
-        // Act
-        RepositoryObjectEncryptionMetadata metadata = AbstractAESEncryptor.prepareObjectForDecryption(ciphertextBytes, "R1", "block repository object", ["v1"])
-        logger.info("Extracted ROEM: ${metadata}")
-
-        // Assert
-        assert metadata.keyId == KEY_ID
-        assert metadata.algorithm == EncryptionMethod.AES_GCM.algorithm
-        assert metadata.version == "v1"
-    }
-
-    @Test
-    void testPrepareObjectForDecryptionShouldFailOnUnsupportedSourceType() {
-        // Arrange
-        String ciphertextString = "This is not a valid ciphertext source"
-
-        // Act
-        def msg = shouldFail(EncryptionException) {
-            RepositoryObjectEncryptionMetadata metadata = AbstractAESEncryptor.prepareObjectForDecryption(ciphertextString, "X1", "unsupported repository object", ["v1"])
-            logger.info("Extracted ROEM: ${metadata}")
-        }
-
-        // Assert
-        assert msg == "The unsupported repository object with ID X1 was detected as String; this is not a supported source of ciphertext"
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/RepositoryEncryptorUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/RepositoryEncryptorUtilsTest.groovy
deleted file mode 100644
index 7b8b67b..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/RepositoryEncryptorUtilsTest.groovy
+++ /dev/null
@@ -1,152 +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.security.repository
-
-
-import org.apache.nifi.security.kms.StaticKeyProvider
-import org.apache.nifi.security.util.EncryptionMethod
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-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 javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class RepositoryEncryptorUtilsTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryEncryptorUtilsTest.class)
-
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX_1 = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static final String KEY_HEX_2 = "00" * (isUnlimitedStrengthCryptoAvailable() ? 32 : 16)
-    private static final String KEY_HEX_3 = "AA" * (isUnlimitedStrengthCryptoAvailable() ? 32 : 16)
-
-    private static final String KEY_ID_1 = "K1"
-    private static final String KEY_ID_2 = "K2"
-    private static final String KEY_ID_3 = "K3"
-
-    private static AESKeyedCipherProvider mockCipherProvider
-
-    private static String ORIGINAL_LOG_LEVEL
-
-    private NiFiProperties nifiProperties
-    private static final String LOG_PACKAGE = "org.slf4j.simpleLogger.log.org.apache.nifi.controller.repository.crypto"
-
-    private static final boolean isLossTolerant = false
-
-    // Mapping of key IDs to keys
-    final def KEYS = [
-            (KEY_ID_1): new SecretKeySpec(Hex.decode(KEY_HEX_1), "AES"),
-            (KEY_ID_2): new SecretKeySpec(Hex.decode(KEY_HEX_2), "AES"),
-            (KEY_ID_3): new SecretKeySpec(Hex.decode(KEY_HEX_3), "AES"),
-    ]
-    private static final String DEFAULT_NIFI_PROPS_PATH = "/conf/nifi.properties"
-
-    private static final Map<String, String> DEFAULT_ENCRYPTION_PROPS = [
-            (NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION)                              : "org.apache.nifi.controller.repository.crypto.EncryptedFileSystemRepository",
-            (NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_ID)                           : KEY_ID_1,
-            (NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY)                              : KEY_HEX_1,
-            (NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS): StaticKeyProvider.class.name,
-            (NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION)            : ""
-    ]
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        ORIGINAL_LOG_LEVEL = System.getProperty(LOG_PACKAGE)
-        System.setProperty(LOG_PACKAGE, "DEBUG")
-
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-
-        mockCipherProvider = [
-                getCipher: { EncryptionMethod em, SecretKey key, byte[] ivBytes, boolean encryptMode ->
-                    logger.mock("Getting cipher for ${em} with IV ${Hex.toHexString(ivBytes)} encrypt ${encryptMode}")
-                    Cipher cipher = Cipher.getInstance(em.algorithm)
-                    cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, key, new IvParameterSpec(ivBytes))
-                    cipher
-                }
-        ] as AESKeyedCipherProvider
-    }
-
-    @Before
-    void setUp() throws Exception {
-    }
-
-    @After
-    void tearDown() throws Exception {
-    }
-
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty(LOG_PACKAGE, ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    @Test
-    void testShouldDetermineKeyProviderImplementationClassName() {
-        // Arrange
-        final Map EXPECTED_CLASS_NAMES = [
-                (RepositoryType.CONTENT)   : NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
-                (RepositoryType.FLOWFILE)  : NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
-                (RepositoryType.PROVENANCE): NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS,
-        ]
-
-        // Act
-        Map<RepositoryType, String> actualClassNames = RepositoryType.values().collectEntries { RepositoryType rt ->
-            [rt, RepositoryEncryptorUtils.determineKeyProviderImplementationClassName(rt)]
-        }
-
-        // Assert
-        actualClassNames.each { RepositoryType rt, String actualClassName ->
-            assert actualClassName == EXPECTED_CLASS_NAMES[rt]
-        }
-    }
-
-    @Test
-    void testDetermineKeyProviderImplementationClassNameShouldHandleUnsupportedRepositoryTypes() {
-        // Arrange
-
-        // Act
-        def actualClassName = RepositoryEncryptorUtils.determineKeyProviderImplementationClassName(null)
-
-        // Assert
-        assert actualClassName == "no_such_key_provider_defined"
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptorTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptorTest.groovy
deleted file mode 100644
index 7619a90..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/block/aes/RepositoryObjectAESGCMEncryptorTest.groovy
+++ /dev/null
@@ -1,267 +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.security.repository.block.aes
-
-import org.apache.nifi.security.kms.KeyProvider
-import org.apache.nifi.security.util.EncryptionMethod
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-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 javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class RepositoryObjectAESGCMEncryptorTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryObjectAESGCMEncryptorTest.class)
-
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static final String LOG_PACKAGE = "org.slf4j.simpleLogger.log.org.apache.nifi.security.repository.block.aes"
-
-    private static KeyProvider mockKeyProvider
-    private static AESKeyedCipherProvider mockCipherProvider
-
-    private static String ORIGINAL_LOG_LEVEL
-
-    private RepositoryObjectAESGCMEncryptor encryptor
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        ORIGINAL_LOG_LEVEL = System.getProperty(LOG_PACKAGE)
-        System.setProperty(LOG_PACKAGE, "DEBUG")
-
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-
-        mockKeyProvider = [
-                getKey   : { String keyId ->
-                    logger.mock("Requesting key ID: ${keyId}")
-                    new SecretKeySpec(Hex.decode(KEY_HEX), "AES")
-                },
-                keyExists: { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    true
-                }] as KeyProvider
-
-        mockCipherProvider = [
-                getCipher: { EncryptionMethod em, SecretKey key, byte[] ivBytes, boolean encryptMode ->
-                    logger.mock("Getting cipher for ${em} with IV ${Hex.toHexString(ivBytes)} encrypt ${encryptMode}")
-                    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding")
-                    cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, key, new IvParameterSpec(ivBytes))
-                    cipher
-                }] as AESKeyedCipherProvider
-    }
-
-    @Before
-    void setUp() throws Exception {
-
-    }
-
-    @After
-    void tearDown() throws Exception {
-
-    }
-
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty(LOG_PACKAGE, ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    /**
-     * Given arbitrary bytes, create an OutputStream, encrypt them, and persist with the (plaintext) encryption metadata, then recover
-     */
-    @Test
-    void testShouldEncryptAndDecryptArbitraryBytes() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        encryptor = new RepositoryObjectAESGCMEncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-
-        // Act
-        byte[] encryptedBytes = encryptor.encrypt(SERIALIZED_BYTES, recordId, keyId)
-        logger.info("Encrypted bytes: ${Hex.toHexString(encryptedBytes)}".toString())
-
-        byte[] decryptedBytes = encryptor.decrypt(encryptedBytes, recordId)
-        logger.info("Decrypted data to: \n\t${Hex.toHexString(decryptedBytes)}")
-
-        // Assert
-        assert decryptedBytes == SERIALIZED_BYTES
-        logger.info("Decoded: ${new String(decryptedBytes, StandardCharsets.UTF_8)}")
-    }
-
-    /**
-     * Test which demonstrates that multiple encryption and decryption calls each receive their own independent {@link RepositoryObjectEncryptionMetadata} instance.
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultiplePiecesOfContent() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES_1 = "This is plaintext content 1.".getBytes(StandardCharsets.UTF_8)
-        final byte[] SERIALIZED_BYTES_2 = "This is plaintext content 2.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes 1 (${SERIALIZED_BYTES_1.size()}): ${Hex.toHexString(SERIALIZED_BYTES_1)}")
-        logger.info("Serialized bytes 2 (${SERIALIZED_BYTES_2.size()}): ${Hex.toHexString(SERIALIZED_BYTES_2)}")
-
-        encryptor = new RepositoryObjectAESGCMEncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId1 = "R1"
-        String recordId2 = "R2"
-
-        // Act
-        logger.info("Using record ID ${recordId1} and key ID ${keyId}")
-        byte[] encryptedBytes1 = encryptor.encrypt(SERIALIZED_BYTES_1, recordId1, keyId)
-        logger.info("Encrypted bytes 1: ${Hex.toHexString(encryptedBytes1)}".toString())
-
-        logger.info("Using record ID ${recordId2} and key ID ${keyId}")
-        byte[] encryptedBytes2 = encryptor.encrypt(SERIALIZED_BYTES_2, recordId2, keyId)
-        logger.info("Encrypted bytes 2: ${Hex.toHexString(encryptedBytes2)}".toString())
-
-        byte[] decryptedBytes1 = encryptor.decrypt(encryptedBytes1, recordId1)
-        logger.info("Decrypted data 1 to: \n\t${Hex.toHexString(decryptedBytes1)}")
-
-        byte[] decryptedBytes2 = encryptor.decrypt(encryptedBytes2, recordId2)
-        logger.info("Decrypted data 2 to: \n\t${Hex.toHexString(decryptedBytes2)}")
-
-        // Assert
-        assert decryptedBytes1 == SERIALIZED_BYTES_1
-        logger.info("Decoded 1: ${new String(decryptedBytes1, StandardCharsets.UTF_8)}")
-
-        assert decryptedBytes2 == SERIALIZED_BYTES_2
-        logger.info("Decoded 2: ${new String(decryptedBytes2, StandardCharsets.UTF_8)}")
-    }
-
-    /**
-     * Test which demonstrates that encrypting and decrypting large blocks of content (~6 KB) works via block mechanism
-     */
-    @Test
-    void testShouldEncryptAndDecryptLargeContent() {
-        // Arrange
-        final byte[] IMAGE_BYTES = new File("src/test/resources/nifi.png").readBytes()
-        logger.info("Image bytes (${IMAGE_BYTES.size()}): src/test/resources/nifi.png")
-
-        encryptor = new RepositoryObjectAESGCMEncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-
-        // Act
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-        byte[] encryptedBytes = encryptor.encrypt(IMAGE_BYTES, recordId, keyId)
-        logger.info("Encrypted bytes (${encryptedBytes.size()}): ${Hex.toHexString(encryptedBytes)[0..<32]}...".toString())
-
-        byte[] decryptedBytes = encryptor.decrypt(encryptedBytes, recordId)
-        logger.info("Decrypted data to (${decryptedBytes.size()}): \n\t${Hex.toHexString(decryptedBytes)[0..<32]}...")
-
-        // Assert
-        assert decryptedBytes == IMAGE_BYTES
-        logger.info("Decoded (binary PNG header): ${new String(decryptedBytes[0..<16] as byte[], StandardCharsets.UTF_8)}...")
-    }
-
-    /**
-     * Test which demonstrates that multiple encryption and decryption calls each receive their own independent {@code RepositoryObjectEncryptionMetadata} instance and use independent keys.
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultiplePiecesOfContentWithDifferentKeys() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES_1 = "This is plaintext content 1.".getBytes(StandardCharsets.UTF_8)
-        final byte[] SERIALIZED_BYTES_2 = "This is plaintext content 2.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes 1 (${SERIALIZED_BYTES_1.size()}): ${Hex.toHexString(SERIALIZED_BYTES_1)}")
-        logger.info("Serialized bytes 2 (${SERIALIZED_BYTES_2.size()}): ${Hex.toHexString(SERIALIZED_BYTES_2)}")
-
-        // Set up a mock that can provide multiple keys
-        KeyProvider mockMultipleKeyProvider = [
-                getKey   : { String keyId ->
-                    logger.mock("Requesting key ID: ${keyId}")
-                    def keyHex = keyId == "K1" ? KEY_HEX : "AB" * 16
-                    new SecretKeySpec(Hex.decode(keyHex), "AES")
-                },
-                keyExists: { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    true
-                }] as KeyProvider
-
-        encryptor = new RepositoryObjectAESGCMEncryptor()
-        encryptor.initialize(mockMultipleKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId1 = "K1"
-        String keyId2 = "K2"
-        String recordId1 = "R1"
-        String recordId2 = "R2"
-
-        // Act
-        logger.info("Using record ID ${recordId1} and key ID ${keyId1}")
-        byte[] encryptedBytes1 = encryptor.encrypt(SERIALIZED_BYTES_1, recordId1, keyId1)
-        logger.info("Encrypted bytes 1: ${Hex.toHexString(encryptedBytes1)}".toString())
-
-        logger.info("Using record ID ${recordId2} and key ID ${keyId2}")
-        byte[] encryptedBytes2 = encryptor.encrypt(SERIALIZED_BYTES_2, recordId2, keyId2)
-        logger.info("Encrypted bytes 2: ${Hex.toHexString(encryptedBytes2)}".toString())
-
-        byte[] decryptedBytes1 = encryptor.decrypt(encryptedBytes1, recordId1)
-        logger.info("Decrypted data 1 to: \n\t${Hex.toHexString(decryptedBytes1)}")
-
-        byte[] decryptedBytes2 = encryptor.decrypt(encryptedBytes2, recordId2)
-        logger.info("Decrypted data 2 to: \n\t${Hex.toHexString(decryptedBytes2)}")
-
-        // Assert
-        assert decryptedBytes1 == SERIALIZED_BYTES_1
-        logger.info("Decoded 1: ${new String(decryptedBytes1, StandardCharsets.UTF_8)}")
-
-        assert decryptedBytes2 == SERIALIZED_BYTES_2
-        logger.info("Decoded 2: ${new String(decryptedBytes2, StandardCharsets.UTF_8)}")
-    }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptorTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptorTest.groovy
deleted file mode 100644
index d26d8a4..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/repository/stream/aes/RepositoryObjectAESCTREncryptorTest.groovy
+++ /dev/null
@@ -1,389 +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.security.repository.stream.aes
-
-import org.apache.nifi.security.kms.KeyProvider
-import org.apache.nifi.security.util.EncryptionMethod
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-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 javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class RepositoryObjectAESCTREncryptorTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(RepositoryObjectAESCTREncryptorTest.class)
-
-    private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static final String LOG_PACKAGE = "org.slf4j.simpleLogger.log.org.apache.nifi.security.repository.stream.aes"
-    private static String ORIGINAL_LOG_LEVEL
-
-    private static KeyProvider mockKeyProvider
-    private static AESKeyedCipherProvider mockCipherProvider
-
-    private RepositoryObjectAESCTREncryptor encryptor
-
-    @BeforeClass
-    static void setUpOnce() throws Exception {
-        ORIGINAL_LOG_LEVEL = System.getProperty(LOG_PACKAGE)
-        System.setProperty(LOG_PACKAGE, "DEBUG")
-
-        Security.addProvider(new BouncyCastleProvider())
-
-        logger.metaClass.methodMissing = { String name, args ->
-            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
-        }
-
-        mockKeyProvider = [
-                getKey   : { String keyId ->
-                    logger.mock("Requesting key ID: ${keyId}")
-                    new SecretKeySpec(Hex.decode(KEY_HEX), "AES")
-                },
-                keyExists: { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    true
-                }] as KeyProvider
-
-        mockCipherProvider = [
-                getCipher: { EncryptionMethod em, SecretKey key, byte[] ivBytes, boolean encryptMode ->
-                    logger.mock("Getting cipher for ${em} with IV ${Hex.toHexString(ivBytes)} encrypt ${encryptMode}")
-                    Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding")
-                    cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, key, new IvParameterSpec(ivBytes))
-                    cipher
-                }] as AESKeyedCipherProvider
-    }
-
-    @Before
-    void setUp() throws Exception {
-
-    }
-
-    @After
-    void tearDown() throws Exception {
-
-    }
-
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty(LOG_PACKAGE, ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
-    /**
-     * Given arbitrary bytes, create an OutputStream, encrypt them, and persist with the (plaintext) encryption metadata, then recover
-     */
-    @Test
-    void testShouldEncryptAndDecryptArbitraryBytes() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES = "This is a plaintext message.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes (${SERIALIZED_BYTES.size()}): ${Hex.toHexString(SERIALIZED_BYTES)}")
-
-        encryptor = new RepositoryObjectAESCTREncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-
-        OutputStream encryptDestination = new ByteArrayOutputStream(256)
-
-        // Act
-        OutputStream encryptedOutputStream = encryptor.encrypt(encryptDestination, recordId, keyId)
-        encryptedOutputStream.write(SERIALIZED_BYTES)
-        encryptedOutputStream.flush()
-        encryptedOutputStream.close()
-
-        byte[] encryptedBytes = encryptDestination.toByteArray()
-        logger.info("Encrypted bytes: ${Hex.toHexString(encryptedBytes)}".toString())
-
-        InputStream encryptedInputStream = new ByteArrayInputStream(encryptedBytes)
-
-        InputStream decryptedInputStream = encryptor.decrypt(encryptedInputStream, recordId)
-        byte[] recoveredBytes = new byte[SERIALIZED_BYTES.length]
-        decryptedInputStream.read(recoveredBytes)
-        logger.info("Decrypted data to: \n\t${Hex.toHexString(recoveredBytes)}")
-
-        // Assert
-        assert recoveredBytes == SERIALIZED_BYTES
-        logger.info("Decoded: ${new String(recoveredBytes, StandardCharsets.UTF_8)}")
-    }
-
-    /**
-     * Test which demonstrates that normal mechanism of {@code OutputStream os = repository.write(contentClaim); os.write(content1); os.write(content2);} works because only one encryption metadata record is written (before {@code content1}). {@code content2} is written with the same recordId and keyId because the output stream is written to by the same {@code session.write()}
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultiplePiecesOfContent() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES_1 = "This is plaintext content 1.".getBytes(StandardCharsets.UTF_8)
-        final byte[] SERIALIZED_BYTES_2 = "This is plaintext content 2.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes 1 (${SERIALIZED_BYTES_1.size()}): ${Hex.toHexString(SERIALIZED_BYTES_1)}")
-        logger.info("Serialized bytes 2 (${SERIALIZED_BYTES_2.size()}): ${Hex.toHexString(SERIALIZED_BYTES_2)}")
-
-        encryptor = new RepositoryObjectAESCTREncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-
-        OutputStream encryptDestination = new ByteArrayOutputStream(512)
-
-        // Act
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-        OutputStream encryptedOutputStream = encryptor.encrypt(encryptDestination, recordId, keyId)
-        encryptedOutputStream.write(SERIALIZED_BYTES_1)
-        encryptedOutputStream.write(SERIALIZED_BYTES_2)
-
-        encryptedOutputStream.flush()
-        encryptedOutputStream.close()
-
-        byte[] encryptedBytes = encryptDestination.toByteArray()
-        logger.info("Encrypted bytes: ${Hex.toHexString(encryptedBytes)}".toString())
-
-        InputStream encryptedInputStream = new ByteArrayInputStream(encryptedBytes)
-
-        InputStream decryptedInputStream = encryptor.decrypt(encryptedInputStream, recordId)
-        byte[] recoveredBytes1 = new byte[SERIALIZED_BYTES_1.length]
-        decryptedInputStream.read(recoveredBytes1)
-        logger.info("Decrypted data 1 to: \n\t${Hex.toHexString(recoveredBytes1)}")
-
-        byte[] recoveredBytes2 = new byte[SERIALIZED_BYTES_2.length]
-        decryptedInputStream.read(recoveredBytes2)
-        logger.info("Decrypted data 2 to: \n\t${Hex.toHexString(recoveredBytes2)}")
-
-        // Assert
-        assert recoveredBytes1 == SERIALIZED_BYTES_1
-        logger.info("Decoded 1: ${new String(recoveredBytes1, StandardCharsets.UTF_8)}")
-
-        assert recoveredBytes2 == SERIALIZED_BYTES_2
-        logger.info("Decoded 2: ${new String(recoveredBytes2, StandardCharsets.UTF_8)}")
-    }
-
-    /**
-     * Test which demonstrates that encrypting and decrypting large blocks of content (~6 KB) works via streaming mechanism
-     */
-    @Test
-    void testShouldEncryptAndDecryptLargeContent() {
-        // Arrange
-        final byte[] IMAGE_BYTES = new File("src/test/resources/nifi.png").readBytes()
-        logger.info("Image bytes (${IMAGE_BYTES.size()}): src/test/resources/nifi.png")
-
-        // Arbitrary buffer size to force multiple writes
-        final int BUFFER_SIZE = 256
-
-        encryptor = new RepositoryObjectAESCTREncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId = "R1"
-
-        // Create a stream with enough room for the content and some header & encryption overhead
-        OutputStream encryptDestination = new ByteArrayOutputStream(6 * 1024 + 512)
-
-        // Act
-        logger.info("Using record ID ${recordId} and key ID ${keyId}")
-        OutputStream encryptedOutputStream = encryptor.encrypt(encryptDestination, recordId, keyId)
-
-        // Buffer the byte[] writing to the stream in chunks of BUFFER_SIZE
-        for (int i = 0; i < IMAGE_BYTES.length; i+= BUFFER_SIZE) {
-            int buf = Math.min(i+BUFFER_SIZE, IMAGE_BYTES.length)
-            encryptedOutputStream.write((byte[]) (IMAGE_BYTES[i..<buf]))
-            encryptedOutputStream.flush()
-        }
-        encryptedOutputStream.close()
-
-        byte[] encryptedBytes = encryptDestination.toByteArray()
-        logger.info("Encrypted bytes (${encryptedBytes.size()}): ${Hex.toHexString(encryptedBytes)}".toString())
-
-        InputStream encryptedInputStream = new ByteArrayInputStream(encryptedBytes)
-
-        InputStream decryptedInputStream = encryptor.decrypt(encryptedInputStream, recordId)
-        byte[] recoveredBytes = decryptedInputStream.getBytes()
-        logger.info("Decrypted data to (${recoveredBytes.size()}): \n\t${Hex.toHexString(recoveredBytes)}")
-
-        // Assert
-        assert recoveredBytes == IMAGE_BYTES
-        logger.info("Decoded (binary PNG header): ${new String(recoveredBytes[0..<16] as byte[], StandardCharsets.UTF_8)}...")
-    }
-
-    /**
-     * Test which demonstrates that if two {@code #encrypt()} calls are made, each piece of content is encrypted and decrypted independently, and each has its own encryption metadata persisted
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultiplePiecesOfContentIndependently() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES_1 = "This is plaintext content 1.".getBytes(StandardCharsets.UTF_8)
-        final byte[] SERIALIZED_BYTES_2 = "This is plaintext content 2.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes 1 (${SERIALIZED_BYTES_1.size()}): ${Hex.toHexString(SERIALIZED_BYTES_1)}")
-        logger.info("Serialized bytes 2 (${SERIALIZED_BYTES_2.size()}): ${Hex.toHexString(SERIALIZED_BYTES_2)}")
-
-        encryptor = new RepositoryObjectAESCTREncryptor()
-        encryptor.initialize(mockKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId = "K1"
-        String recordId1 = "R1"
-        String recordId2 = "R2"
-
-        OutputStream encryptDestination1 = new ByteArrayOutputStream(512)
-        OutputStream encryptDestination2 = new ByteArrayOutputStream(512)
-
-        // Act
-        logger.info("Using record ID ${recordId1} and key ID ${keyId}")
-        OutputStream encryptedOutputStream1 = encryptor.encrypt(encryptDestination1, recordId1, keyId)
-        encryptedOutputStream1.write(SERIALIZED_BYTES_1)
-        encryptedOutputStream1.flush()
-        encryptedOutputStream1.close()
-
-        logger.info("Using record ID ${recordId2} and key ID ${keyId}")
-        OutputStream encryptedOutputStream2 = encryptor.encrypt(encryptDestination2, recordId2, keyId)
-        encryptedOutputStream2.write(SERIALIZED_BYTES_2)
-        encryptedOutputStream2.flush()
-        encryptedOutputStream2.close()
-        
-        byte[] encryptedBytes1 = encryptDestination1.toByteArray()
-        logger.info("Encrypted bytes 1: ${Hex.toHexString(encryptedBytes1)}".toString())
-
-        byte[] encryptedBytes2 = encryptDestination2.toByteArray()
-        logger.info("Encrypted bytes 2: ${Hex.toHexString(encryptedBytes2)}".toString())
-
-        InputStream encryptedInputStream1 = new ByteArrayInputStream(encryptedBytes1)
-        InputStream encryptedInputStream2 = new ByteArrayInputStream(encryptedBytes2)
-
-        InputStream decryptedInputStream1 = encryptor.decrypt(encryptedInputStream1, recordId1)
-        byte[] recoveredBytes1 = new byte[SERIALIZED_BYTES_1.length]
-        decryptedInputStream1.read(recoveredBytes1)
-        logger.info("Decrypted data 1 to: \n\t${Hex.toHexString(recoveredBytes1)}")
-
-        InputStream decryptedInputStream2 = encryptor.decrypt(encryptedInputStream2, recordId2)
-        byte[] recoveredBytes2 = new byte[SERIALIZED_BYTES_2.length]
-        decryptedInputStream2.read(recoveredBytes2)
-        logger.info("Decrypted data 2 to: \n\t${Hex.toHexString(recoveredBytes2)}")
-
-        // Assert
-        assert recoveredBytes1 == SERIALIZED_BYTES_1
-        logger.info("Decoded 1: ${new String(recoveredBytes1, StandardCharsets.UTF_8)}")
-
-        assert recoveredBytes2 == SERIALIZED_BYTES_2
-        logger.info("Decoded 2: ${new String(recoveredBytes2, StandardCharsets.UTF_8)}")
-    }
-
-    /**
-     * Test which demonstrates that if two {@code #encrypt()} calls are made *with different keys*, each piece of content is encrypted and decrypted independently, and each has its own encryption metadata persisted (including the key ID)
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultiplePiecesOfContentWithDifferentKeys() {
-        // Arrange
-        final byte[] SERIALIZED_BYTES_1 = "This is plaintext content 1.".getBytes(StandardCharsets.UTF_8)
-        final byte[] SERIALIZED_BYTES_2 = "This is plaintext content 2.".getBytes(StandardCharsets.UTF_8)
-        logger.info("Serialized bytes 1 (${SERIALIZED_BYTES_1.size()}): ${Hex.toHexString(SERIALIZED_BYTES_1)}")
-        logger.info("Serialized bytes 2 (${SERIALIZED_BYTES_2.size()}): ${Hex.toHexString(SERIALIZED_BYTES_2)}")
-
-        // Set up a mock that can provide multiple keys
-        KeyProvider mockMultipleKeyProvider = [
-                getKey   : { String keyId ->
-                    logger.mock("Requesting key ID: ${keyId}")
-                    def keyHex = keyId == "K1" ? KEY_HEX : "AB" * 16
-                    new SecretKeySpec(Hex.decode(keyHex), "AES")
-                },
-                keyExists: { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    true
-                }] as KeyProvider
-
-
-        encryptor = new RepositoryObjectAESCTREncryptor()
-        encryptor.initialize(mockMultipleKeyProvider)
-        encryptor.setCipherProvider(mockCipherProvider)
-        logger.info("Created ${encryptor}")
-
-        String keyId1 = "K1"
-        String keyId2 = "K1"
-        String recordId1 = "R1"
-        String recordId2 = "R2"
-
-        OutputStream encryptDestination1 = new ByteArrayOutputStream(512)
-        OutputStream encryptDestination2 = new ByteArrayOutputStream(512)
-
-        // Act
-        logger.info("Using record ID ${recordId1} and key ID ${keyId1}")
-        OutputStream encryptedOutputStream1 = encryptor.encrypt(encryptDestination1, recordId1, keyId1)
-        encryptedOutputStream1.write(SERIALIZED_BYTES_1)
-        encryptedOutputStream1.flush()
-        encryptedOutputStream1.close()
-
-        logger.info("Using record ID ${recordId2} and key ID ${keyId2}")
-        OutputStream encryptedOutputStream2 = encryptor.encrypt(encryptDestination2, recordId2, keyId2)
-        encryptedOutputStream2.write(SERIALIZED_BYTES_2)
-        encryptedOutputStream2.flush()
-        encryptedOutputStream2.close()
-
-        byte[] encryptedBytes1 = encryptDestination1.toByteArray()
-        logger.info("Encrypted bytes 1: ${Hex.toHexString(encryptedBytes1)}".toString())
-
-        byte[] encryptedBytes2 = encryptDestination2.toByteArray()
-        logger.info("Encrypted bytes 2: ${Hex.toHexString(encryptedBytes2)}".toString())
-
-        InputStream encryptedInputStream1 = new ByteArrayInputStream(encryptedBytes1)
-        InputStream encryptedInputStream2 = new ByteArrayInputStream(encryptedBytes2)
-
-        InputStream decryptedInputStream1 = encryptor.decrypt(encryptedInputStream1, recordId1)
-        byte[] recoveredBytes1 = new byte[SERIALIZED_BYTES_1.length]
-        decryptedInputStream1.read(recoveredBytes1)
-        logger.info("Decrypted data 1 to: \n\t${Hex.toHexString(recoveredBytes1)}")
-
-        InputStream decryptedInputStream2 = encryptor.decrypt(encryptedInputStream2, recordId2)
-        byte[] recoveredBytes2 = new byte[SERIALIZED_BYTES_2.length]
-        decryptedInputStream2.read(recoveredBytes2)
-        logger.info("Decrypted data 2 to: \n\t${Hex.toHexString(recoveredBytes2)}")
-
-        // Assert
-        assert recoveredBytes1 == SERIALIZED_BYTES_1
-        logger.info("Decoded 1: ${new String(recoveredBytes1, StandardCharsets.UTF_8)}")
-
-        assert recoveredBytes2 == SERIALIZED_BYTES_2
-        logger.info("Decoded 2: ${new String(recoveredBytes2, StandardCharsets.UTF_8)}")
-    }
-}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index d4e1a09..2cecacc 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -40,6 +40,7 @@
         <module>nifi-sensitive-property-provider</module>
         <module>nifi-record</module>
         <module>nifi-record-path</module>
+        <module>nifi-repository-encryption</module>
         <module>nifi-rocksdb-utils</module>
         <module>nifi-schema-utils</module>
         <module>nifi-security-kerberos-api</module>
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 8d2c58e..18b7247 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -3056,6 +3056,39 @@
 |`nifi.h2.url.append`|This property specifies additional arguments to add to the connection string for the H2 database. The default value should be used and should not be changed. It is: `;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE`.
 |====
 
+[[repository-encryption-properties]]
+=== Repository Encryption
+
+Repository encryption provides a layer of security for information persisted to the filesystem during processing.
+Enabling encryption and configuring a Key Provider using these properties applies to all repositories.
+
+|====
+|*Property*|*Description*
+|`nifi.repository.encryption.protocol.version`|The encryption protocol version applied to all repository implementations. Absence of this property value disables repository encryption. Configuring a supported protocol enables encryption for all repositories. Supported protocol versions include: `1`.
+|`nifi.repository.encryption.key.id`|The key identifier that repository implementations will use for new encryption operations. The key identifier must match the `alias` value for a Key Entry when using the `KEYSTORE` provider.
+|`nifi.repository.encryption.key.provider`|The Key Provider implementation that repository implementations will use for retrieving keys necessary for encryption and decryption. Supported providers include: `KEYSTORE`.
+|`nifi.repository.encryption.key.provider.keystore.location`|Path to the KeyStore resource required for the `KEYSTORE` provider to read available keys. The KeyStore must contain one or more Secret Key entries. File paths must end with a known extension. Supported KeyStore types include: `PKCS12` and `BCFKS`. Supported extensions include: `.p12` and `.bcfks`
+|`nifi.repository.encryption.key.provider.keystore.password`|Password for the configured KeyStore resource required for the `KEYSTORE` provider to decrypt available keys. The configured KeyStore must use the same password for both the KeyStore and individual Key Entries.
+|====
+
+Configuring repository encryption properties overrides the following repository implementation class properties, as well
+as associated Key Provider properties:
+
+* `nifi.content.repository.implementation`
+* `nifi.flowfile.repository.wal.implementation`
+* `nifi.provenance.repository.implementation`
+* `nifi.swap.manager.implementation`
+
+The following provides an example set of configuration properties using a PKCS12 KeyStore as the Key Provider:
+
+....
+nifi.repository.encryption.protocol.version=1
+nifi.repository.encryption.key.id=primary-key
+nifi.repository.encryption.key.provider=KEYSTORE
+nifi.repository.encryption.key.provider.keystore.location=conf/repository.p12
+nifi.repository.encryption.key.provider.keystore.password=2fRKmwDyMYmT7P5L
+....
+
 === FlowFile Repository
 
 The FlowFile repository keeps track of the attributes and current state of each FlowFile in the system. By default,
@@ -3092,6 +3125,8 @@
 [[encrypted-write-ahead-flowfile-repository-properties]]
 === Encrypted Write Ahead FlowFile Repository Properties
 
+WARNING: The following properties are deprecated in favor of <<repository-encryption-properties>> properties.
+
 All of the properties defined above (see <<write-ahead-flowfile-repository,Write Ahead FlowFile Repository>>) still apply. Only encryption-specific properties are listed here. See <<user-guide.adoc#encrypted-flowfile,Encrypted FlowFile Repository in the User Guide>> for more information.
 
 NOTE: Unlike the encrypted content and provenance repositories, the repository implementation does not change here, only the _underlying write-ahead log implementation_. This allows for cleaner separation and more flexibility in implementation selection. The property that should be changed to enable encryption is `nifi.flowfile.repository.wal.implementation`.
@@ -3106,17 +3141,6 @@
 |`nifi.flowfile.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.flowfile.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
 |====
 
-The simplest configuration is below:
-
-....
-nifi.flowfile.repository.implementation=org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
-nifi.flowfile.repository.wal.implementation=org.apache.nifi.wali.EncryptedSequentialAccessWriteAheadLog
-nifi.flowfile.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.flowfile.repository.encryption.key.provider.location=
-nifi.flowfile.repository.encryption.key.id=Key1
-nifi.flowfile.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-....
-
 === Volatile FlowFile Repository
 
 This implementation stores FlowFiles in memory instead of on disk.  It *will* result in data loss in the event of power/machine failure or a restart of NiFi.  To use this implementation, set  `nifi.flowfile.repository.implementation` to `org.apache.nifi.controller.repository.VolatileFlowFileRepository`.
@@ -3277,6 +3301,8 @@
 [[encrypted-file-system-content-repository-properties]]
 === Encrypted File System Content Repository Properties
 
+WARNING: The following properties are deprecated in favor of <<repository-encryption-properties>> properties.
+
 All of the properties defined above (see <<file-system-content-repository-properties,File System Content Repository Properties>>) still apply. Only encryption-specific properties are listed here. See <<user-guide.adoc#encrypted-content,Encrypted Content Repository in the User Guide>> for more information.
 
 |====
@@ -3289,16 +3315,6 @@
 |`nifi.content.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.content.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
 |====
 
-The simplest configuration is below:
-
-....
-nifi.content.repository.implementation=org.apache.nifi.controller.repository.crypto.EncryptedFileSystemRepository
-nifi.content.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.content.repository.encryption.key.provider.location=
-nifi.content.repository.encryption.key.id=Key1
-nifi.content.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-....
-
 === Volatile Content Repository Properties
 
 |====
@@ -3404,6 +3420,8 @@
 [[encrypted-write-ahead-provenance-repository-properties]]
 === Encrypted Write Ahead Provenance Repository Properties
 
+WARNING: The following properties are deprecated in favor of <<repository-encryption-properties>> properties.
+
 All of the properties defined above (see <<write-ahead-provenance-repository-properties,Write Ahead Repository Properties>>) still apply. Only encryption-specific properties are listed here. See <<user-guide.adoc#encrypted-provenance,Encrypted Provenance Repository in the User Guide>> for more information.
 
 |====
@@ -3416,17 +3434,6 @@
 |`nifi.provenance.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.provenance.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
 |====
 
-The simplest configuration is below:
-
-....
-nifi.provenance.repository.implementation=org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository
-nifi.provenance.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=Key1
-nifi.provenance.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-....
-
-
 === Persistent Provenance Repository Properties
 
 |====
diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc b/nifi-docs/src/main/asciidoc/user-guide.adoc
index 6c9b2bb..83880c7 100644
--- a/nifi-docs/src/main/asciidoc/user-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/user-guide.adoc
@@ -2879,288 +2879,124 @@
 
 **Note:** Detailed descriptions for each of these properties can be found in <<administration-guide.adoc#system_properties,System Properties>>.
 
-==== Encrypted Provenance Considerations
-The above migration recommendations for `WriteAheadProvenanceRepository` also apply to the encrypted version of the configuration, `EncryptedWriteAheadProvenanceRepository`.
+== Repository Encryption
 
-The next section has more information about implementing an Encrypted Provenance Repository.
+NiFi supports encryption of local repositories using a configurable Key Provider to enable protection of information
+on the filesystem. Repository encryption configuration uses a version number to indicate the cipher algorithms, metadata
+format, and repository implementation classes. This approach provides a generalized method for configuration without the
+need to customize each repository implementation class.
 
-[[encrypted-provenance]]
-=== Encrypted Provenance Repository
-While OS-level access control can offer some security over the provenance data written to the disk in a repository, there are scenarios where the data may be sensitive, compliance and regulatory requirements exist, or NiFi is running on hardware not under the direct control of the organization (cloud, etc.). In this case, the provenance repository allows for all data to be encrypted before being persisted to the disk.
+Repository encryption incurs a performance cost due to the overhead of cipher operations. Filesystem encryption at the
+operating system level provides an alternative solution, with different performance characteristics. For deployments
+where filesystem encryption is not configured, repository encryption provides an enhanced level of data protection.
+Due to increased performance requirements, more computing resources may be necessary to achieve sufficient throughput
+when enabling repository encryption.
 
-[WARNING]
-.Experimental
-============
-This implementation is marked <<experimental_warning, *experimental*>> as of Apache NiFi 1.10.0 (October 2019). The API, configuration, and internal behavior may change without warning, and such changes may occur during a minor release. Use at your own risk.
-============
+The security of repository encryption depends on a combination of the cipher algorithms and the protection of encryption
+keys. Key protection and key rotation are important parts of securing an encrypted repository configuration.
+Key protection involves limiting access to the Key Provider and key rotation requires manual updates to generate and
+specify a new encryption key.
 
-[WARNING]
-.Performance
-============
-The current implementation of the encrypted provenance repository intercepts the record writer and reader of `WriteAheadProvenanceRepository`, which offers significant performance improvements over the legacy `PersistentProvenanceRepository` and uses the `AES/GCM` algorithm, which is fairly performant on commodity hardware. In most scenarios, the added cost will not be significant (unnoticable on a flow with hundreds of provenance events per second, moderately noticable on a flow with thousands - tens of thousands of events per second). However, administrators should perform their own risk assessment and performance analysis and decide how to move forward. Switching back and forth between encrypted/unencrypted implementations is not recommended at this time.
-============
+=== Repository Encryption Protocol Version 1
 
-==== What is it?
+The first version of support for repository encryption includes the following cipher algorithms:
 
-The `EncryptedWriteAheadProvenanceRepository` is a new implementation of the provenance repository which encrypts all event record information before it is written to the repository. This allows for storage on systems where OS-level access controls are not sufficient to protect the data while still allowing querying and access to the data through the NiFi UI/API.
+[options="header"]
+|=======================
+|Repository Type       |Cipher Algorithm
+|Content Repository    |AES/CTR/NoPadding
+|FlowFile Repository   |AES/GCM/NoPadding
+|FlowFile Swap Manager |AES/GCM/NoPadding
+|Provenance Repository |AES/GCM/NoPadding
+|=======================
 
-==== How does it work?
+The following classes provide the direct repository encryption implementation, extending standard classes:
 
-The `WriteAheadProvenanceRepository` was introduced in NiFi 1.2.0 and provided a refactored and much faster provenance repository implementation than the previous `PersistentProvenanceRepository`. The encrypted version wraps that implementation with a record writer and reader which encrypt and decrypt the serialized bytes respectively.
+[options="header"]
+|=======================
+|Repository Type       |Class
+|Content Repository    |org.apache.nifi.content.EncryptedFileSystemRepository
+|FlowFile Repository   |org.apache.nifi.wali.EncryptedSequentialAccessWriteAheadLog
+|FlowFile Swap Manager |org.apache.nifi.controller.EncryptedFileSystemSwapManager
+|Provenance Repository |org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository
+|=======================
 
-The fully qualified class `org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository` is specified as the provenance repository implementation in _nifi.properties_ as the value of `nifi.provenance.repository.implementation`. In addition, <<administration-guide.adoc#encrypted-write-ahead-provenance-repository-properties,new properties>> must be populated to allow successful initialization.
+==== Encryption Metadata Serialization
 
-===== StaticKeyProvider
-The `StaticKeyProvider` implementation defines keys directly in _nifi.properties_. Individual keys are provided in hexadecimal encoding. The keys can also be encrypted like any other sensitive property in _nifi.properties_ using the <<administration-guide.adoc#encrypt-config_tool,`./encrypt-config.sh`>> tool in the NiFi Toolkit.
+Each repository implementation class leverages standard cipher operations to perform encryption and decryption. In order
+to support AES, the encryption process writes metadata associated with each encryption operation. Encryption protocol
+version 1 uses Java Object serialization to write objects containing the encryption Key Identifier, the cipher
+Initialization Vector, and other required properties. Serialized objects include the following required properties:
 
-The following configuration section would result in a key provider with two available keys, "Key1" (active) and "AnotherKey".
-....
-nifi.provenance.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.provenance.repository.encryption.key.id=Key1
-nifi.provenance.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-nifi.provenance.repository.encryption.key.id.AnotherKey=0101010101010101010101010101010101010101010101010101010101010101
-....
+[options="header"]
+|=====================
+|Property Name       |Property Type |Description
+|keyId               |String        |Encryption key identifier
+|ivBytes             |byte[]        |Cipher initialization vector
+|algorithm           |String        |Cipher algorithm
+|version             |String        |Encryption protocol version
+|cipherByteLength    |int           |Length of enciphered record
+|=====================
 
-===== FileBasedKeyProvider
-The `FileBasedKeyProvider` implementation reads from an encrypted definition file of the format:
+Metadata serialization uses the standard `java.io.ObjectOutputStream.writeObject()` method to write objects to a stream
+that can be converted to a byte array. The deserialization process uses a custom extension of the
+`java.io.ObjectInputStream` to read objects regardless of the original class name associated with the record. This
+approach requires the presence of the standard metadata properties, but provides a compatibility layer that avoids
+linking the implementation to a specific Java class.
+
+The initial implementation of encrypted repositories used different byte array markers when writing metadata. Each
+repository implementation uses the following byte array markers before writing a serialized metadata record:
+
+[options="header"]
+|=======================
+|Repository Type       |Byte Array
+|Content Repository    |byte[]{0x00, 0x00}
+|FlowFile Repository   |byte[]{}
+|Provenance Repository |byte[]{0x01}
+|=======================
+
+=== Repository Encryption Configuration
+
+Configuring repository encryption requires specifying the encryption protocol version and the associated Key Provider
+properties. Repository encryption can be configured on new or existing installations using standard properties. Records
+in existing repositories should be readable using standard capabilities, and the encrypted repository will write new
+records using the specified configuration.
+
+Disabling repository encryption on existing installations requires removing existing repository contents, and
+restarting the system after making configuration changes. For this reason, flow administrators should confirm that the
+system has processed all available FlowFiles to avoid losing information when disabling repository encryption.
+
+==== Protocol Version Configuration
+
+Setting the following protocol version property enables encryption for all repositories:
 
 ....
-key1=NGCpDpxBZNN0DBodz0p1SDbTjC2FG5kp1pCmdUKJlxxtcMSo6GC4fMlTyy1mPeKOxzLut3DRX+51j6PCO5SznA==
-key2=GYxPbMMDbnraXs09eGJudAM5jTvVYp05XtImkAg4JY4rIbmHOiVUUI6OeOf7ZW+hH42jtPgNW9pSkkQ9HWY/vQ==
-key3=SFe11xuz7J89Y/IQ7YbJPOL0/YKZRFL/VUxJgEHxxlXpd/8ELA7wwN59K1KTr3BURCcFP5YGmwrSKfr4OE4Vlg==
-key4=kZprfcTSTH69UuOU3jMkZfrtiVR/eqWmmbdku3bQcUJ/+UToecNB5lzOVEMBChyEXppyXXC35Wa6GEXFK6PMKw==
-key5=c6FzfnKm7UR7xqI2NFpZ+fEKBfSU7+1NvRw+XWQ9U39MONWqk5gvoyOCdFR1kUgeg46jrN5dGXk13sRqE0GETQ==
+nifi.repository.encryption.protocol.version=1
 ....
 
-Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
+==== Key Provider Configuration
 
-===== KeyStoreKeyProvider
+All encrypted repositories require a Key Provider to perform encryption and decryption operations. NiFi supports
+configuring the Key Provider implementation as well as the Key Identifier that will be used for new encryption
+operations. Key Provider implementations can hold multiple keys to support using a new key while maintaining access to
+information encrypted using the previous key.
 
-See <<secret-key-generation-and-storage-using-keytool>> for details on generating and storing an AES Secret Key for use with the `KeyStoreKeyProvider`.
+Repository encryption supports access to secret keys using standard `java.security.KeyStore` files.
+See <<secret-key-generation-and-storage-using-keytool>> for details on supported KeyStore types, as well as examples of
+generating secret keys.
 
-The following configuration properties support using a PKCS12 keystore with a Secret Key:
-
-  nifi.provenance.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
-  nifi.provenance.repository.encryption.key.provider.location=./conf/repository.p12
-  nifi.provenance.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
-  nifi.provenance.repository.encryption.key.id=primary-key
-
-The same configuration can be used with a BCFKS keystore using a different location property:
-
-  nifi.provenance.repository.encryption.key.provider.location=./conf/repository.bcfks
-
-[[provenance-repository-key-rotation]]
-===== Key Rotation
-Simply update _nifi.properties_ to reference a new key ID in `nifi.provenance.repository.encryption.key.id`. Previously-encrypted events can still be decrypted as long as that key is still available in the key definition file or `nifi.provenance.repository.encryption.key.id.<OldKeyID>` as the key ID is serialized alongside the encrypted record.
-
-==== Writing and Reading Event Records
-Once the repository is initialized, all provenance event record write operations are serialized according to the configured schema writer (`EventIdFirstSchemaRecordWriter` by default for `WriteAheadProvenanceRepository`) to a `byte[]`. Those bytes are then encrypted using an implementation of `ProvenanceEventEncryptor` (the only current implementation is `AES/GCM/NoPadding`) and the encryption metadata (`keyId`, `algorithm`, `version`, `IV`) is serialized and prepended. The complete `byte[]` is then written to the repository on disk as normal.
-
-image:encrypted-wapr-hex.png["Encrypted provenance repository file on disk"]
-
-On record read, the process is reversed. The encryption metadata is parsed and used to decrypt the serialized bytes, which are then deserialized into a `ProvenanceEventRecord` object. The delegation to the normal schema record writer/reader allows for "random-access" (i.e. immediate seek without decryption of unnecessary records).
-
-Within the NiFi UI/API, there is no detectable difference between an encrypted and unencrypted provenance repository. The Provenance Query operations work as expected with no change to the process.
-
-==== Potential Issues
-
-[WARNING]
-.Switching Implementations
-============
-When switching between implementation "families" (i.e. `VolatileProvenanceRepository` or `PersistentProvenanceRepository` to `EncryptedWriteAheadProvenanceRepository`), the existing repository must be cleared from the file system before starting NiFi. A terminal command like `localhost:$NIFI_HOME $ rm -rf provenance_repository/` is sufficient.
-============
-
-* Switching between unencrypted and encrypted repositories
-** If a user has an existing repository (`WriteAheadProvenanceRepository` only -- **not** `PersistentProvenanceRepository`) that is not encrypted and switches their configuration to use an encrypted repository, the application writes an error to the log but starts up. However, previous events are not accessible through the provenance query interface and new events will overwrite the existing events. The same behavior occurs if a user switches from an encrypted repository to an unencrypted repository. Automatic roll-over is a future effort (link:https://issues.apache.org/jira/browse/NIFI-3722[NIFI-3722^]) but NiFi is not intended for long-term storage of provenance events so the impact should be minimal. There are two scenarios for roll-over:
-*** Encrypted -> unencrypted -- if the previous repository implementation was encrypted, these events should be handled seamlessly as long as the key provider available still has the keys used to encrypt the events (see <<provenance-repository-key-rotation,Key Rotation>>)
-*** Unencrypted -> encrypted -- if the previous repository implementation was unencrypted, these events should be handled seamlessly as the previously recorded events simply need to be read with a plaintext schema record reader and then written back with the encrypted record writer
-** There is also a future effort to provide a standalone tool in NiFi Toolkit to encrypt/decrypt an existing provenance repository to make the transition easier. The translation process could take a long time depending on the size of the existing repository, and being able to perform this task outside of application startup would be valuable (link:https://issues.apache.org/jira/browse/NIFI-3723[NIFI-3723^]).
-* Multiple repositories -- No additional effort or testing has been applied to multiple repositories at this time. It is possible/likely issues will occur with repositories on different physical devices. There is no option to provide a heterogenous environment (i.e. one encrypted, one plaintext repository).
-* Corruption -- when a disk is filled or corrupted, there have been reported issues with the repository becoming corrupted and recovery steps are necessary. This is likely to continue to be an issue with the encrypted repository, although still limited in scope to individual records (i.e. an entire repository file won't be irrecoverable due to the encryption).
-
-[[encrypted-content]]
-== Encrypted Content Repository
-While OS-level access control can offer some security over the flowfile content data written to the disk in a repository, there are scenarios where the data may be sensitive, compliance and regulatory requirements exist, or NiFi is running on hardware not under the direct control of the organization (cloud, etc.). In this case, the content repository allows for all data to be encrypted before being persisted to the disk. For more information on the internal workings of the content repository, see <<nifi-in-depth.adoc#content-repository,NiFi In-Depth - Content Repository>>.
-
-[WARNING]
-.Experimental
-============
-This implementation is marked <<experimental_warning, *experimental*>> as of Apache NiFi 1.10.0 (October 2019). The API, configuration, and internal behavior may change without warning, and such changes may occur during a minor release. Use at your own risk.
-============
-
-[WARNING]
-.Performance
-============
-The current implementation of the encrypted content repository intercepts the serialization of content data via the `EncryptedContentRepositoryOutputStream` and uses the `AES/CTR` algorithm, which is fairly performant on commodity hardware. This use of a stream cipher (because the content is operated on in a streaming manner for performance) differs from the use of an authenticated encryption algorithm (AEAD) like `AES/GCM` in the <<encrypted-provenance,Encrypted Provenance Repository>>. In most scenarios, the added cost will not be significant (unnoticable on a flow with hundreds of content read/write events per second, moderately noticable on a flow with thousands - tens of thousands of events per second). However, administrators should perform their own risk assessment and performance analysis and decide how to move forward. Switching back and forth between encrypted/unencrypted implementations is not recommended at this time.
-============
-
-=== What is it?
-
-The `EncryptedFileSystemRepository` is a new implementation of the content repository which encrypts all content data before it is written to the repository. This allows for storage on systems where OS-level access controls are not sufficient to protect the data while still allowing querying and access to the data through the NiFi UI/API.
-
-=== How does it work?
-
-The `FileSystemRepository` was introduced in NiFi 0.2.1 and provided the only persistent content repository implementation. The encrypted version wraps that implementation with functionality to return to the `Session` (usually `StandardProcessSession`) a special `OutputStream`/`InputStream` which encrypt and decrypt the serialized bytes respectively. This allows all components to continue interacting with the content repository interface in the same way as before and continue operating on content data in a streaming manner, without requiring any changes to handle the data protection.
-
-The fully qualified class `org.apache.nifi.content.EncryptedFileSystemRepository` is specified as the content repository implementation in _nifi.properties_ as the value of `nifi.content.repository.implementation`. In addition, <<administration-guide.adoc#encrypted-file-system-content-repository-properties,new properties>> must be populated to allow successful initialization.
-
-==== StaticKeyProvider
-The `StaticKeyProvider` implementation defines keys directly in _nifi.properties_. Individual keys are provided in hexadecimal encoding. The keys can also be encrypted like any other sensitive property in _nifi.properties_ using the <<administration-guide.adoc#encrypt-config_tool,`./encrypt-config.sh`>> tool in the NiFi Toolkit.
-
-The following configuration section would result in a key provider with two available keys, "Key1" (active) and "AnotherKey".
-....
-nifi.content.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.content.repository.encryption.key.id=Key1
-nifi.content.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-nifi.content.repository.encryption.key.id.AnotherKey=0101010101010101010101010101010101010101010101010101010101010101
-....
-
-==== FileBasedKeyProvider
-The `FileBasedKeyProvider` implementation reads from an encrypted definition file of the format:
+The following configuration properties provide an example using a PKCS12 KeyStore file named `repository.p12` containing
+a secret key labeled with an alias of `primary-key`:
 
 ....
-key1=NGCpDpxBZNN0DBodz0p1SDbTjC2FG5kp1pCmdUKJlxxtcMSo6GC4fMlTyy1mPeKOxzLut3DRX+51j6PCO5SznA==
-key2=GYxPbMMDbnraXs09eGJudAM5jTvVYp05XtImkAg4JY4rIbmHOiVUUI6OeOf7ZW+hH42jtPgNW9pSkkQ9HWY/vQ==
-key3=SFe11xuz7J89Y/IQ7YbJPOL0/YKZRFL/VUxJgEHxxlXpd/8ELA7wwN59K1KTr3BURCcFP5YGmwrSKfr4OE4Vlg==
-key4=kZprfcTSTH69UuOU3jMkZfrtiVR/eqWmmbdku3bQcUJ/+UToecNB5lzOVEMBChyEXppyXXC35Wa6GEXFK6PMKw==
-key5=c6FzfnKm7UR7xqI2NFpZ+fEKBfSU7+1NvRw+XWQ9U39MONWqk5gvoyOCdFR1kUgeg46jrN5dGXk13sRqE0GETQ==
+nifi.repository.encryption.key.id=primary-key
+nifi.repository.encryption.key.provider=KEYSTORE
+nifi.repository.encryption.key.provider.keystore.location=conf/repository.p12
+nifi.repository.encryption.key.provider.keystore.password=2fRKmwDyMYmT7P5L
 ....
 
-Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
-
-==== KeyStoreKeyProvider
-
-See <<secret-key-generation-and-storage-using-keytool>> for details on generating and storing an AES Secret Key for use with the `KeyStoreKeyProvider`.
-
-The following configuration properties support using a PKCS12 keystore with a Secret Key:
-
-  nifi.content.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
-  nifi.content.repository.encryption.key.provider.location=./conf/repository.p12
-  nifi.content.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
-  nifi.content.repository.encryption.key.id=primary-key
-
-The same configuration can be used with a BCFKS keystore using a different location property:
-
-  nifi.content.repository.encryption.key.provider.location=./conf/repository.bcfks
-
-.Data Protection vs. Key Protection
-****
-Even though the flowfile content is encrypted using `AES/CTR` to handle streaming data, if using the _Config Encrypt
-Tool_ or `FileBasedKeyProvider`, those _keys_ will be protected using `AES/GCM` to provide authenticated encryption
-over the key material.
-****
-
-[[content-repository-key-rotation]]
-==== Key Rotation
-Simply update _nifi.properties_ to reference a new key ID in `nifi.content.repository.encryption.key.id`. Previously-encrypted content claims can still be decrypted as long as that key is still available in the key definition file or `nifi.content.repository.encryption.key.id.<OldKeyID>` as the key ID is serialized alongside the encrypted content.
-
-=== Writing and Reading Content Claims
-Once the repository is initialized, all content claim write operations are serialized using `RepositoryObjectStreamEncryptor` (the only currently existing implementation is `RepositoryObjectAESCTREncryptor`) to an `OutputStream`. The actual implementation is `EncryptedContentRepositoryOutputStream`, which encrypts the data written by the component via `StandardProcessSession` inline and the encryption metadata (`keyId`, `algorithm`, `version`, `IV`) is serialized and prepended. The complete `OutputStream` is then written to the repository on disk as normal.
-
-image:encrypted-content-hex.png["Encrypted content repository file on disk"]
-
-On content claim read, the process is reversed. The encryption metadata (`RepositoryObjectEncryptionMetadata`) is parsed and used to decrypt the serialized bytes, which are then deserialized into a `CipherInputStream` object. The delegation to the normal repository file system interaction allows for "random-access" (i.e. immediate seek without decryption of unnecessary content claims).
-
-Within the NiFi UI/API, there is no detectable difference between an encrypted and unencrypted content repository. The Provenance Query operations to view content work as expected with no change to the process.
-
-=== Potential Issues
-
-[WARNING]
-.Switching Implementations
-============
-When switching between implementation "families" (i.e. `VolatileContentRepository` or `FileSystemRepository` to `EncryptedFileSystemRepository`), the existing repository must be cleared from the file system before starting NiFi. A terminal command like `localhost:$NIFI_HOME $ rm -rf content_repository/` is sufficient.
-============
-
-* Switching between unencrypted and encrypted repositories
-** If a user has an existing repository (`FileSystemRepository`) that is not encrypted and switches their configuration to use an encrypted repository, the application writes an error to the log but starts up. However, previous content claims are not accessible through the provenance query interface and new content claims will overwrite the existing claims. The same behavior occurs if a user switches from an encrypted repository to an unencrypted repository. Automatic roll-over is a future effort (link:https://issues.apache.org/jira/browse/NIFI-6783[NIFI-6783^]) but NiFi is not intended for long-term storage of content claims so the impact should be minimal. There are two scenarios for roll-over:
-*** Encrypted -> unencrypted -- if the previous repository implementation was encrypted, these claims should be handled seamlessly as long as the key provider available still has the keys used to encrypt the claims (see <<content-repository-key-rotation,Key Rotation>>)
-*** Unencrypted -> encrypted -- if the previous repository implementation was unencrypted, these claims should be handled seamlessly as the previously written claims simply need to be read with a plaintext `InputStream` and then be written back with the `EncryptedContentRepositoryOutputStream`
-** There is also a future effort to provide a standalone tool in NiFi Toolkit to encrypt/decrypt an existing content repository to make the transition easier. The translation process could take a long time depending on the size of the existing repository, and being able to perform this task outside of application startup would be valuable (link:https://issues.apache.org/jira/browse/NIFI-6783[NIFI-6783^]).
-* Multiple repositories -- No additional effort or testing has been applied to multiple repositories at this time. It is possible/likely issues will occur with repositories on different physical devices. There is no option to provide a heterogenous environment (i.e. one encrypted, one plaintext repository).
-* Corruption -- when a disk is filled or corrupted, there have been reported issues with the repository becoming corrupted and recovery steps are necessary. This is likely to continue to be an issue with the encrypted repository, although still limited in scope to individual claims (i.e. an entire repository file won't be irrecoverable due to the encryption). Some testing has been performed on scenarios where disk space is exhausted. While the flow can no longer write additional content claims to the repository in that case, the NiFi application continues to function properly, and successfully written content claims are still available via the Provenance Query operations. Stopping NiFi and removing the content repository (or moving it to a larger disk) resolves the issue.
-
-[[encrypted-flowfile]]
-== Encrypted FlowFile Repository
-While OS-level access control can offer some security over the flowfile attribute and content claim data written to the disk in a repository, there are scenarios where the data may be sensitive, compliance and regulatory requirements exist, or NiFi is running on hardware not under the direct control of the organization (cloud, etc.). In this case, the flowfile repository allows for all data to be encrypted before being persisted to the disk. For more information on the internal workings of the flowfile repository, see <<nifi-in-depth.adoc#flowfile-repository,NiFi In-Depth - FlowFile Repository>>.
-
-[WARNING]
-.Experimental
-============
-This implementation is marked <<experimental_warning, *experimental*>> as of Apache NiFi 1.11.0 (January 2020). The API, configuration, and internal behavior may change without warning, and such changes may occur during a minor release. Use at your own risk.
-============
-
-[WARNING]
-.Performance
-============
-The current implementation of the encrypted flowfile repository intercepts the serialization of flowfile record data via the `EncryptedSchemaRepositoryRecordSerde` and uses the `AES/GCM` algorithm, which is fairly performant on commodity hardware. This use of an authenticated encryption algorithm (AEAD) block cipher (because the content length is limited and known a priori) is the same as the <<encrypted-provenance,Encrypted Provenance Repository>>, but differs from the unauthenticated stream cipher used in the <<encrypted-content,Encrypted Content Repository>>. In low volume flowfile scenarios, the added cost will be minimal. However, administrators should perform their own risk assessment and performance analysis and decide how to move forward. Switching back and forth between encrypted/unencrypted implementations is not recommended at this time.
-============
-
-=== What is it?
-
-The `EncryptedSequentialAccessWriteAheadLog` is a new implementation of the flowfile write-ahead log which encrypts all flowfile attribute data before it is written to the repository. This allows for storage on systems where OS-level access controls are not sufficient to protect the data while still allowing querying and access to the data through the NiFi UI/API.
-
-=== How does it work?
-
-The `SequentialAccessWriteAheadLog` was introduced in NiFi 1.6.0 and provided a faster flowfile repository implementation. The encrypted version wraps that implementation with functionality to transparently encrypt and decrypt the serialized `RepositoryRecord` objects during file system interaction. During all writes to disk (swapping, snapshotting, journaling, and checkpointing), the flowfile containers are serialized to bytes based on a schema, and this serialized form is encrypted before writing. This allows the snapshot handler to continue interacting with the flowfile repository interface in the same way as before and continue operating on flowfile data in a random access manner, without requiring any changes to handle the data protection.
-
-The fully qualified class `org.apache.nifi.wali.EncryptedSequentialAccessWriteAheadLog` is specified as the flowfile repository write-ahead log implementation in _nifi.properties_ as the value of `nifi.flowfile.repository.wal.implementation`. In addition, <<administration-guide.adoc#encrypted-write-ahead-flowfile-repository-properties,new properties>> must be populated to allow successful initialization.
-
-==== StaticKeyProvider
-The `StaticKeyProvider` implementation defines keys directly in _nifi.properties_. Individual keys are provided in hexadecimal encoding. The keys can also be encrypted like any other sensitive property in _nifi.properties_ using the <<administration-guide.adoc#encrypt-config_tool,`./encrypt-config.sh`>> tool in the NiFi Toolkit.
-
-The following configuration section would result in a key provider with two available keys, "Key1" (active) and "AnotherKey".
-....
-nifi.flowfile.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.StaticKeyProvider
-nifi.flowfile.repository.encryption.key.id=Key1
-nifi.flowfile.repository.encryption.key=0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210
-nifi.flowfile.repository.encryption.key.id.AnotherKey=0101010101010101010101010101010101010101010101010101010101010101
-....
-
-==== FileBasedKeyProvider
-The `FileBasedKeyProvider` implementation reads from an encrypted definition file of the format:
-
-....
-key1=NGCpDpxBZNN0DBodz0p1SDbTjC2FG5kp1pCmdUKJlxxtcMSo6GC4fMlTyy1mPeKOxzLut3DRX+51j6PCO5SznA==
-key2=GYxPbMMDbnraXs09eGJudAM5jTvVYp05XtImkAg4JY4rIbmHOiVUUI6OeOf7ZW+hH42jtPgNW9pSkkQ9HWY/vQ==
-key3=SFe11xuz7J89Y/IQ7YbJPOL0/YKZRFL/VUxJgEHxxlXpd/8ELA7wwN59K1KTr3BURCcFP5YGmwrSKfr4OE4Vlg==
-key4=kZprfcTSTH69UuOU3jMkZfrtiVR/eqWmmbdku3bQcUJ/+UToecNB5lzOVEMBChyEXppyXXC35Wa6GEXFK6PMKw==
-key5=c6FzfnKm7UR7xqI2NFpZ+fEKBfSU7+1NvRw+XWQ9U39MONWqk5gvoyOCdFR1kUgeg46jrN5dGXk13sRqE0GETQ==
-....
-
-Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
-
-==== KeyStoreKeyProvider
-
-See <<secret-key-generation-and-storage-using-keytool>> for details on generating and storing an AES Secret Key for use with the `KeyStoreKeyProvider`.
-
-The following configuration properties support using a PKCS12 keystore with a Secret Key:
-
-  nifi.flowfile.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
-  nifi.flowfile.repository.encryption.key.provider.location=./conf/repository.p12
-  nifi.flowfile.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
-  nifi.flowfile.repository.encryption.key.id=primary-key
-
-The same configuration can be used with a BCFKS keystore using a different location property:
-
-  nifi.flowfile.repository.encryption.key.provider.location=./conf/repository.bcfks
-
-[[flowfile-repository-key-rotation]]
-==== Key Rotation
-Simply update _nifi.properties_ to reference a new key ID in `nifi.flowfile.repository.encryption.key.id`. Previously-encrypted flowfile records can still be decrypted as long as that key is still available in the key definition file or `nifi.flowfile.repository.encryption.key.id.<OldKeyID>` as the key ID is serialized alongside the encrypted record.
-
-=== Writing and Reading FlowFiles
-Once the repository is initialized, all flowfile record write operations are serialized using `RepositoryObjectBlockEncryptor` (the only currently existing implementation is `RepositoryObjectAESGCMEncryptor`) to the provided `DataOutputStream`. The original stream is swapped with a temporary wrapped stream, which encrypts the data written by the wrapped serializer/deserializer via `EncryptedSchemaRepositoryRecordSerde` inline and the encryption metadata (`keyId`, `algorithm`, `version`, `IV`, `cipherByteLength`) is serialized and prepended. The complete length and encrypted bytes are then written to the original `DataOutputStream` on disk as normal.
-
-image:encrypted-flowfile-hex.png["Encrypted flowfile repository journal file on disk"]
-
-On flowfile record read, the process is reversed. The encryption metadata (`RepositoryObjectEncryptionMetadata`) is parsed and used to decrypt the serialized bytes, which are then deserialized into a `DataInputStream` object.
-
-During swaps and recoveries, the flowfile records are deserialized and reserialized, so if the active key has been changed, the flowfile records will be re-encrypted with the new active key.
-
-Within the NiFi UI/API, there is no detectable difference between an encrypted and unencrypted flowfile repository. All framework interactions with flowfiles work as expected with no change to the process.
-
 [[secret-key-generation-and-storage-using-keytool]]
-=== Secret Key Generation and Storage using Keytool
+==== Secret Key Generation and Storage using Keytool
 
 The `KeyStoreKeyProvider` supports reading from a `java.security.KeyStore` using a configured password to load AES Secret Key entries.
 The `KeyStoreKeyProvider` can be configured with any of the encrypted repository implementations.
@@ -3187,23 +3023,6 @@
 Enter a keystore password when prompted. The same value must be used for both the keystore password and key password.
 The keystore password will be used in the provider configuration properties.
 
-=== Potential Issues
-
-[WARNING]
-.Switching Implementations
-============
-It is not recommended to switch between any implementation other than `SequentialAccessWriteAheadLog` and the `EncryptedSequentialAccessWriteAheadLog`. To migrate from a different provider, first migrate to the plaintext sequential log, allow NiFi to automatically recover the flowfiles, then stop NiFi and change the configuration to enable encryption. NiFi will automatically recover the plaintext flowfiles from the repository, and begin encrypting them on subsequent writes.
-============
-
-* Switching between unencrypted and encrypted repositories
-** If a user has an existing write-ahead repository (`WriteAheadFlowFileRepository`) that is not encrypted (uses the `SequentialAccessWriteAheadLog`) and switches their configuration to use an encrypted repository, the application handles this and all flowfile records will be recovered on startup. Future writes (including re-serialization of these same flowfiles) will be encrypted. If a user switches from an encrypted repository to an unencrypted repository, the flowfiles cannot be recovered, and it is recommended to delete the existing flowfile repository before switching in this direction. Automatic roll-over is a future effort (link:https://issues.apache.org/jira/browse/NIFI-6994[NIFI-6994^]) but NiFi is not intended for long-term storage of flowfile records so the impact should be minimal. There are two scenarios for roll-over:
-*** Encrypted -> unencrypted -- if the previous repository implementation was encrypted, these records should be handled seamlessly as long as the key provider available still has the keys used to encrypt the claims (see <<flowfile-repository-key-rotation,Key Rotation>>)
-*** Unencrypted -> encrypted -- currently handled seamlesssly for `SequentialAccessWriteAheadLog` but there are other initial implementations which could be handled
-** There is also a future effort to provide a standalone tool in NiFi Toolkit to encrypt/decrypt an existing flowfile repository to make the transition easier. The translation process could take a long time depending on the size of the existing repository, and being able to perform this task outside of application startup would be valuable (link:https://issues.apache.org/jira/browse/NIFI-6994[NIFI-6994^]).
-* Multiple repositories -- No additional effort or testing has been applied to multiple repositories at this time. Current implementations of the flowfile repository allow only for one repository, though it can reside across multiple volumes and partitions. It is possible/likely issues will occur with repositories on different physical devices. There is no option to provide a heterogenous environment (i.e. one encrypted, one plaintext partition/directory).
-* Corruption -- when a disk is filled or corrupted, there have been reported issues with the repository becoming corrupted and recovery steps are necessary. This is likely to continue to be an issue with the encrypted repository, although still limited in scope to individual records (i.e. an entire repository file won't be irrecoverable due to the encryption). It is important for the continued operation of NiFi to ensure that the disk storing the flowfile repository does not run out of available space.
-
-
 [[experimental_warning]]
 == Experimental Warning
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/pom.xml
index 4e6c667..6367a70 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/pom.xml
@@ -47,6 +47,16 @@
             <artifactId>nifi-security-utils</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-kms</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-repository-encryption</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
             <version>2.8.1</version>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactory.java
index 714fc7f..f05ef2c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactory.java
@@ -17,35 +17,17 @@
 
 package org.apache.nifi.controller.repository;
 
-import java.io.IOException;
 import org.apache.nifi.controller.repository.claim.ResourceClaimManager;
 import org.apache.nifi.repository.schema.FieldCache;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.EncryptionException;
-import org.apache.nifi.security.repository.config.FlowFileRepositoryEncryptionConfiguration;
 import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.wali.SerDe;
 
 public class EncryptedRepositoryRecordSerdeFactory extends StandardRepositoryRecordSerdeFactory {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedRepositoryRecordSerdeFactory.class);
+    private final NiFiProperties niFiProperties;
 
-    private FlowFileRepositoryEncryptionConfiguration ffrec;
-
-    public EncryptedRepositoryRecordSerdeFactory(final ResourceClaimManager claimManager, final NiFiProperties niFiProperties, final FieldCache fieldCache) throws EncryptionException {
+    public EncryptedRepositoryRecordSerdeFactory(final ResourceClaimManager claimManager, final NiFiProperties niFiProperties, final FieldCache fieldCache) {
         super(claimManager, fieldCache);
-
-        // Retrieve encryption configuration
-        FlowFileRepositoryEncryptionConfiguration ffrec = new FlowFileRepositoryEncryptionConfiguration(niFiProperties);
-
-        // The configuration should be validated immediately rather than waiting until attempting to deserialize records (initial recovery at startup)
-        if (!CryptoUtils.isValidRepositoryEncryptionConfiguration(ffrec)) {
-            logger.error("The flowfile repository encryption configuration is not valid (see above). Shutting down...");
-            throw new EncryptionException("The flowfile repository encryption configuration is not valid");
-        }
-
-        this.ffrec = ffrec;
+        this.niFiProperties = niFiProperties;
     }
 
     @Override
@@ -54,13 +36,7 @@
         if (encodingName == null || EncryptedSchemaRepositoryRecordSerde.class.getName().equals(encodingName)) {
             // Delegate the creation of the wrapped serde to the standard factory
             final SerDe<SerializedRepositoryRecord> serde = super.createSerDe(null);
-
-            // Retrieve encryption configuration
-            try {
-                return new EncryptedSchemaRepositoryRecordSerde(serde, ffrec);
-            } catch (IOException e) {
-                throw new IllegalArgumentException("Could not create Deserializer for Repository Records because the encoding " + encodingName + " requires NiFi properties which could not be loaded");
-            }
+            return new EncryptedSchemaRepositoryRecordSerde(serde, niFiProperties);
         }
 
         // If not encrypted, delegate to the standard factory
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerde.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerde.java
index e71af0c..5983a1c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerde.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/main/java/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerde.java
@@ -23,19 +23,19 @@
 import java.io.DataOutputStream;
 import java.io.EOFException;
 import java.io.IOException;
-import java.security.KeyManagementException;
 import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.security.kms.EncryptionException;
+import java.util.Objects;
+
+import org.apache.nifi.repository.encryption.AesGcmByteArrayRepositoryEncryptor;
+import org.apache.nifi.repository.encryption.RepositoryEncryptor;
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.kms.RepositoryKeyProviderFactory;
+import org.apache.nifi.repository.encryption.configuration.kms.StandardRepositoryKeyProviderFactory;
 import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.block.RepositoryObjectBlockEncryptor;
-import org.apache.nifi.security.repository.block.aes.RepositoryObjectAESGCMEncryptor;
-import org.apache.nifi.security.repository.config.FlowFileRepositoryEncryptionConfiguration;
 import org.apache.nifi.stream.io.StreamUtils;
 import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
 import org.wali.SerDe;
 import org.wali.UpdateType;
 
@@ -49,73 +49,27 @@
  * Repository Properties</a>.
  */
 public class EncryptedSchemaRepositoryRecordSerde implements SerDe<SerializedRepositoryRecord> {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedSchemaRepositoryRecordSerde.class);
     private final SerDe<SerializedRepositoryRecord> wrappedSerDe;
-    private final KeyProvider keyProvider;
-    private String activeKeyId;
-
-    /**
-     * Creates an instance of the serializer/deserializer which wraps another SerDe instance but transparently encrypts/decrypts the data before/after writing/reading from the streams.
-     *
-     * @param wrappedSerDe                              the wrapped SerDe instance which performs the object <-> bytes (de)serialization
-     * @param flowFileRepositoryEncryptionConfiguration the configuration values necessary to encrypt/decrypt the data
-     * @throws IOException if there is a problem retrieving the configuration values
-     */
-    public EncryptedSchemaRepositoryRecordSerde(final SerDe<SerializedRepositoryRecord> wrappedSerDe, final FlowFileRepositoryEncryptionConfiguration
-            flowFileRepositoryEncryptionConfiguration) throws IOException {
-        if (wrappedSerDe == null) {
-            throw new IllegalArgumentException("This implementation must be provided another serde instance to function");
-        }
-        this.wrappedSerDe = wrappedSerDe;
-
-        // Initialize the encryption-specific fields
-        this.keyProvider = RepositoryEncryptorUtils.validateAndBuildRepositoryKeyProvider(flowFileRepositoryEncryptionConfiguration);
-
-        // Set active key ID
-        setActiveKeyId(flowFileRepositoryEncryptionConfiguration.getEncryptionKeyId());
-    }
+    private final RepositoryEncryptor<byte[], byte[]> encryptor;
+    private final String keyId;
 
     /**
      * Creates an instance of the serializer/deserializer which wraps another SerDe instance but transparently encrypts/decrypts the data before/after writing/reading from the streams.
      *
      * @param wrappedSerDe   the wrapped SerDe instance which performs the object <-> bytes (de)serialization
      * @param niFiProperties the configuration values necessary to encrypt/decrypt the data
-     * @throws IOException if there is a problem retrieving the configuration values
      */
-    public EncryptedSchemaRepositoryRecordSerde(final SerDe<SerializedRepositoryRecord> wrappedSerDe, final NiFiProperties niFiProperties) throws IOException {
-        this(wrappedSerDe, new FlowFileRepositoryEncryptionConfiguration(niFiProperties));
-    }
-
-    /**
-     * Returns the active key ID used for encryption.
-     *
-     * @return the active key ID
-     */
-    String getActiveKeyId() {
-        return activeKeyId;
-    }
-
-    /**
-     * Sets the active key ID used for encryption.
-     *
-     * @param activeKeyId the key ID to use
-     */
-    public void setActiveKeyId(String activeKeyId) {
-        // Key must not be blank and key provider must make key available
-        if (StringUtils.isNotBlank(activeKeyId) && keyProvider.keyExists(activeKeyId)) {
-            this.activeKeyId = activeKeyId;
-            logger.debug("Set active key ID to '" + activeKeyId + "'");
-        } else {
-            logger.warn("Attempted to set active key ID to '" + activeKeyId + "' but that is not a valid or available key ID. Keeping active key ID as '" + this.activeKeyId + "'");
-        }
+    public EncryptedSchemaRepositoryRecordSerde(final SerDe<SerializedRepositoryRecord> wrappedSerDe, final NiFiProperties niFiProperties) {
+        this.wrappedSerDe = Objects.requireNonNull(wrappedSerDe, "Wrapped SerDe required");
+        final RepositoryKeyProviderFactory repositoryKeyProviderFactory = new StandardRepositoryKeyProviderFactory();
+        final KeyProvider keyProvider = repositoryKeyProviderFactory.getKeyProvider(EncryptedRepositoryType.FLOWFILE, niFiProperties);
+        this.encryptor = new AesGcmByteArrayRepositoryEncryptor(keyProvider, EncryptionMetadataHeader.FLOWFILE);
+        this.keyId = niFiProperties.getFlowFileRepoEncryptionKeyId();
     }
 
     @Override
     public void writeHeader(final DataOutputStream out) throws IOException {
         wrappedSerDe.writeHeader(out);
-        if (logger.isDebugEnabled()) {
-            logger.debug("Wrote schema header ({} bytes) to output stream", out.size());
-        }
     }
 
     @Override
@@ -138,7 +92,9 @@
      */
     @Deprecated
     @Override
-    public void serializeEdit(SerializedRepositoryRecord previousRecordState, SerializedRepositoryRecord newRecordState, DataOutputStream out) throws IOException {
+    public void serializeEdit(final SerializedRepositoryRecord previousRecordState,
+                              final SerializedRepositoryRecord newRecordState,
+                              final DataOutputStream out) throws IOException {
         serializeRecord(newRecordState, out);
     }
 
@@ -152,54 +108,32 @@
     @Override
     public void serializeRecord(final SerializedRepositoryRecord record, final DataOutputStream out) throws IOException {
         // Create BAOS wrapped in DOS to intercept the output
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        DataOutputStream tempDataStream = new DataOutputStream(byteArrayOutputStream);
+        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        final DataOutputStream tempDataStream = new DataOutputStream(byteArrayOutputStream);
 
         // Use the delegate to serialize the actual record and extract the output
-        String recordId = getRecordIdentifier(record).toString();
+        final String recordId = getRecordIdentifier(record).toString();
         wrappedSerDe.serializeRecord(record, tempDataStream);
         tempDataStream.flush();
-        byte[] plainSerializedBytes = byteArrayOutputStream.toByteArray();
-        logger.debug("Serialized flowfile record {} to temp stream with length {}", recordId, plainSerializedBytes.length);
+        final byte[] plainSerializedBytes = byteArrayOutputStream.toByteArray();
 
         // Encrypt the serialized byte[] to the real stream
         encryptToStream(plainSerializedBytes, recordId, out);
-        logger.debug("Encrypted serialized flowfile record {} to actual output stream", recordId);
     }
 
     /**
      * Encrypts the plain serialized bytes and writes them to the output stream. Precedes
-     * the cipher bytes with the plaintext
-     * {@link org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata} data
-     * to allow for on-demand deserialization and decryption.
+     * the cipher bytes with the plaintext to allow for on-demand deserialization and decryption.
      *
      * @param plainSerializedBytes the plain serialized bytes
      * @param recordId             the unique identifier for this record to be stored in the encryption metadata
      * @param out                  the output stream
      * @throws IOException if there is a problem writing to the stream
      */
-    private void encryptToStream(byte[] plainSerializedBytes, String recordId, DataOutputStream out) throws IOException {
-        try {
-            RepositoryObjectBlockEncryptor encryptor = new RepositoryObjectAESGCMEncryptor();
-            encryptor.initialize(keyProvider);
-            logger.debug("Initialized {} for flowfile record {}", encryptor.toString(), recordId);
-
-            byte[] cipherBytes = encryptor.encrypt(plainSerializedBytes, recordId, getActiveKeyId());
-            logger.debug("Encrypted {} bytes for flowfile record {}", cipherBytes.length, recordId);
-
-            // Maybe remove cipher bytes length because it's included in encryption metadata; deserialization might need to change?
-            out.writeInt(cipherBytes.length);
-            out.write(cipherBytes);
-            logger.debug("Wrote {} bytes (encrypted, including length) for flowfile record {} to output stream", cipherBytes.length + 4, recordId);
-        } catch (KeyManagementException | EncryptionException e) {
-            logger.error("Encountered an error encrypting & serializing flowfile record {} due to {}", recordId, e.getLocalizedMessage());
-            if (logger.isDebugEnabled()) {
-                logger.debug(e.getLocalizedMessage(), e);
-            }
-
-            // The calling method contract only throws IOException
-            throw new IOException("Encountered an error encrypting & serializing flowfile record " + recordId, e);
-        }
+    private void encryptToStream(final byte[] plainSerializedBytes, final String recordId, final DataOutputStream out) throws IOException {
+        final byte[] cipherBytes = encryptor.encrypt(plainSerializedBytes, recordId, keyId);
+        out.writeInt(cipherBytes.length);
+        out.write(cipherBytes);
     }
 
     /**
@@ -222,7 +156,9 @@
      */
     @Deprecated
     @Override
-    public SerializedRepositoryRecord deserializeEdit(DataInputStream in, Map<Object, SerializedRepositoryRecord> currentRecordStates, int version) throws IOException {
+    public SerializedRepositoryRecord deserializeEdit(final DataInputStream in,
+                                                      final Map<Object, SerializedRepositoryRecord> currentRecordStates,
+                                                      final int version) throws IOException {
         return deserializeRecord(in, version);
 
         // deserializeRecord may return a null if there is no more data. However, when we are deserializing
@@ -252,15 +188,12 @@
         // Read the encrypted record bytes
         byte[] cipherBytes = new byte[encryptedRecordLength];
         StreamUtils.fillBuffer(in, cipherBytes);
-        logger.debug("Read {} bytes (encrypted, including length) from actual input stream", encryptedRecordLength + 4);
 
         // Decrypt the byte[]
-        DataInputStream wrappedInputStream = decryptToStream(cipherBytes);
+        final DataInputStream wrappedInputStream = decryptToStream(cipherBytes);
 
         // Deserialize the plain bytes using the delegate serde
-        final SerializedRepositoryRecord deserializedRecord = wrappedSerDe.deserializeRecord(wrappedInputStream, version);
-        logger.debug("Deserialized flowfile record {} from temp stream", getRecordIdentifier(deserializedRecord));
-        return deserializedRecord;
+        return wrappedSerDe.deserializeRecord(wrappedInputStream, version);
     }
 
     /**
@@ -269,26 +202,10 @@
      * @param cipherBytes the serialized, encrypted bytes
      * @return a stream wrapping the plain bytes
      */
-    private DataInputStream decryptToStream(byte[] cipherBytes) throws IOException {
-        try {
-            RepositoryObjectBlockEncryptor encryptor = new RepositoryObjectAESGCMEncryptor();
-            encryptor.initialize(keyProvider);
-            logger.debug("Initialized {} for decrypting flowfile record", encryptor.toString());
-
-            byte[] plainSerializedBytes = encryptor.decrypt(cipherBytes, "[pending record ID]");
-            logger.debug("Decrypted {} bytes for flowfile record", cipherBytes.length);
-
-            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(plainSerializedBytes);
-            return new DataInputStream(byteArrayInputStream);
-        } catch (KeyManagementException | EncryptionException e) {
-            logger.error("Encountered an error decrypting & deserializing flowfile record due to {}", e.getLocalizedMessage());
-            if (logger.isDebugEnabled()) {
-                logger.debug(e.getLocalizedMessage(), e);
-            }
-
-            // The calling method contract only throws IOException
-            throw new IOException("Encountered an error decrypting & deserializing flowfile record", e);
-        }
+    private DataInputStream decryptToStream(final byte[] cipherBytes) {
+        final byte[] plainSerializedBytes = encryptor.decrypt(cipherBytes, "[pending record ID]");
+        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(plainSerializedBytes);
+        return new DataInputStream(byteArrayInputStream);
     }
 
     /**
@@ -298,7 +215,7 @@
      * @return identifier of record
      */
     @Override
-    public Object getRecordIdentifier(SerializedRepositoryRecord record) {
+    public Object getRecordIdentifier(final SerializedRepositoryRecord record) {
         return wrappedSerDe.getRecordIdentifier(record);
     }
 
@@ -309,7 +226,7 @@
      * @return update type
      */
     @Override
-    public UpdateType getUpdateType(SerializedRepositoryRecord record) {
+    public UpdateType getUpdateType(final SerializedRepositoryRecord record) {
         return wrappedSerDe.getUpdateType(record);
     }
 
@@ -326,7 +243,7 @@
      * @return location
      */
     @Override
-    public String getLocation(SerializedRepositoryRecord record) {
+    public String getLocation(final SerializedRepositoryRecord record) {
         return wrappedSerDe.getLocation(record);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy
deleted file mode 100644
index fe690df..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy
+++ /dev/null
@@ -1,157 +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.controller.repository
-
-import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.controller.repository.claim.ResourceClaimManager
-import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager
-import org.apache.nifi.security.kms.EncryptionException
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.wali.SerDe
-
-import java.security.Security
-
-import static org.apache.nifi.security.kms.CryptoUtils.STATIC_KEY_PROVIDER_CLASS_NAME
-
-@RunWith(JUnit4.class)
-class EncryptedRepositoryRecordSerdeFactoryTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedRepositoryRecordSerdeFactoryTest.class)
-
-    private ResourceClaimManager claimManager
-
-    private static final String KEY_ID = "K1"
-    private static final String KEY_1_HEX = "0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210"
-
-    private NiFiProperties mockNiFiProperties
-
-    @Rule
-    public TestName testName = new TestName()
-
-    @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 {
-        claimManager = new StandardResourceClaimManager()
-
-        Map flowfileEncryptionProps = [
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS): STATIC_KEY_PROVIDER_CLASS_NAME,
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY)                              : KEY_1_HEX,
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID)                           : KEY_ID
-        ]
-        mockNiFiProperties = new NiFiProperties(new Properties(flowfileEncryptionProps))
-    }
-
-    @After
-    void tearDown() throws Exception {
-        claimManager.purge()
-    }
-
-    @Test
-    void testShouldCreateEncryptedSerde() {
-        // Arrange
-        EncryptedRepositoryRecordSerdeFactory factory = new EncryptedRepositoryRecordSerdeFactory(claimManager, mockNiFiProperties)
-
-        // Act
-        SerDe<RepositoryRecord> serde = factory.createSerDe(EncryptedSchemaRepositoryRecordSerde.class.name)
-        logger.info("Created serde: ${serde} ")
-
-        // Assert
-        assert serde instanceof EncryptedSchemaRepositoryRecordSerde
-    }
-
-    @Test
-    void testShouldCreateEncryptedSerdeForNullEncoding() {
-        // Arrange
-        EncryptedRepositoryRecordSerdeFactory factory = new EncryptedRepositoryRecordSerdeFactory(claimManager, mockNiFiProperties)
-
-        // Act
-        SerDe<RepositoryRecord> serde = factory.createSerDe(null)
-        logger.info("Created serde: ${serde} ")
-
-        // Assert
-        assert serde instanceof EncryptedSchemaRepositoryRecordSerde
-    }
-
-    @Test
-    void testShouldCreateStandardSerdeForStandardEncoding() {
-        // Arrange
-        EncryptedRepositoryRecordSerdeFactory factory = new EncryptedRepositoryRecordSerdeFactory(claimManager, mockNiFiProperties)
-
-        // Act
-        SerDe<RepositoryRecord> serde = factory.createSerDe(SchemaRepositoryRecordSerde.class.name)
-        logger.info("Created serde: ${serde} ")
-
-        // Assert
-        assert serde instanceof SchemaRepositoryRecordSerde
-    }
-
-    @Test
-    void testCreateSerDeShouldFailWithUnpopulatedNiFiProperties() {
-        // Arrange
-        NiFiProperties emptyNiFiProperties = new NiFiProperties(new Properties([:]))
-
-        // Act
-        def msg = shouldFail(EncryptionException) {
-            EncryptedRepositoryRecordSerdeFactory factory = new EncryptedRepositoryRecordSerdeFactory(claimManager, emptyNiFiProperties)
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg =~ "The flowfile repository encryption configuration is not valid"
-    }
-
-    @Test
-    void testConstructorShouldFailWithInvalidNiFiProperties() {
-        // Arrange
-        Map invalidFlowfileEncryptionProps = [
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS): STATIC_KEY_PROVIDER_CLASS_NAME.reverse(),
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY)                              : KEY_1_HEX,
-                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID)                           : KEY_ID
-        ]
-        NiFiProperties invalidNiFiProperties = new NiFiProperties(new Properties(invalidFlowfileEncryptionProps))
-
-        // Act
-        def msg = shouldFail(EncryptionException) {
-            EncryptedRepositoryRecordSerdeFactory factory = new EncryptedRepositoryRecordSerdeFactory(claimManager, invalidNiFiProperties)
-        }
-        logger.expected(msg)
-
-        // Assert
-        assert msg =~ "The flowfile repository encryption configuration is not valid"
-    }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy
deleted file mode 100644
index d6a5418..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy
+++ /dev/null
@@ -1,320 +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.controller.repository
-
-import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.controller.queue.FlowFileQueue
-import org.apache.nifi.controller.repository.claim.ResourceClaimManager
-import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager
-import org.apache.nifi.security.kms.CryptoUtils
-import org.apache.nifi.security.repository.config.FlowFileRepositoryEncryptionConfiguration
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.wali.SerDe
-
-import java.security.Security
-
-import static org.apache.nifi.security.kms.CryptoUtils.STATIC_KEY_PROVIDER_CLASS_NAME
-
-@RunWith(JUnit4.class)
-class EncryptedSchemaRepositoryRecordSerdeTest extends GroovyTestCase {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedSchemaRepositoryRecordSerdeTest.class)
-
-    public static final String TEST_QUEUE_IDENTIFIER = "testQueueIdentifier"
-
-    private ResourceClaimManager claimManager
-    private Map<String, FlowFileQueue> queueMap
-    private FlowFileQueue flowFileQueue
-    private ByteArrayOutputStream byteArrayOutputStream
-    private DataOutputStream dataOutputStream
-
-    // TODO: Mock the wrapped serde
-    // TODO: Make integration test with real wrapped serde
-    private SerDe<RepositoryRecord> wrappedSerDe
-
-    private static final String KPI = STATIC_KEY_PROVIDER_CLASS_NAME
-    private static final String KPL = ""
-    private static final String KEY_ID = "K1"
-    private static final Map<String, String> KEYS = [K1: "0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210"]
-    // TODO: Change to WAL impl name
-    private static final String REPO_IMPL = CryptoUtils.EWAFFR_CLASS_NAME
-
-    private FlowFileRepositoryEncryptionConfiguration flowFileREC
-
-    private EncryptedSchemaRepositoryRecordSerde esrrs
-
-    @Rule
-    public TestName testName = new TestName()
-
-    @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 {
-        claimManager = new StandardResourceClaimManager()
-        queueMap = [:]
-        flowFileQueue = createAndRegisterMockQueue(TEST_QUEUE_IDENTIFIER)
-        byteArrayOutputStream = new ByteArrayOutputStream()
-        dataOutputStream = new DataOutputStream(byteArrayOutputStream)
-        wrappedSerDe = new SchemaRepositoryRecordSerde(claimManager)
-        wrappedSerDe.setQueueMap(queueMap)
-
-        flowFileREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, KEYS, REPO_IMPL)
-
-        esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, flowFileREC)
-    }
-
-    @After
-    void tearDown() throws Exception {
-        claimManager.purge()
-        queueMap.clear()
-    }
-
-    private FlowFileQueue createMockQueue(String identifier = testName.methodName + new Date().toString()) {
-        [getIdentifier: { ->
-            logger.mock("Retrieving flowfile queue identifier: ${identifier}")
-            identifier
-        }] as FlowFileQueue
-    }
-
-    private FlowFileQueue createAndRegisterMockQueue(String identifier = testName.methodName + new Date().toString()) {
-        FlowFileQueue queue = createMockQueue(identifier)
-        queueMap.put(identifier, queue)
-        queue
-    }
-
-    private RepositoryRecord buildCreateRecord(FlowFileQueue queue, Map<String, String> attributes = [:]) {
-        StandardRepositoryRecord record = new StandardRepositoryRecord(queue)
-        StandardFlowFileRecord.Builder ffrb = new StandardFlowFileRecord.Builder().id(System.nanoTime())
-        ffrb.addAttributes([uuid: getMockUUID()] + attributes as Map<String, String>)
-        record.setWorking(ffrb.build())
-        record
-    }
-
-    private String getMockUUID() {
-        "${testName.methodName ?: "no_test"}@${new Date().format("mmssSSS")}" as String
-    }
-
-    /** This test ensures that the creation of a flowfile record is applied to the specified output stream correctly */
-    @Test
-    void testShouldSerializeAndDeserializeRecord() {
-        // Arrange
-        RepositoryRecord newRecord = buildCreateRecord(flowFileQueue, [id: "1", firstName: "Andy", lastName: "LoPresto"])
-        DataOutputStream dos = dataOutputStream
-
-        esrrs.writeHeader(dataOutputStream)
-
-        // Act
-        esrrs.serializeRecord(newRecord, dos)
-        logger.info("Output stream: ${Hex.toHexString(byteArrayOutputStream.toByteArray())} ")
-
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
-        esrrs.readHeader(dis)
-        RepositoryRecord deserializedRecord = esrrs.deserializeRecord(dis, 2)
-
-        /* The records will not be identical, because the process of serializing/deserializing changes the application
-         * of the delta data. The CREATE with a "current" record containing attributes becomes an UPDATE with an
-         * "original" record containing attributes */
-
-        // Assert
-        logger.info("    Original record: ${newRecord.dump()}")
-        logger.info("Deserialized record: ${deserializedRecord.dump()}")
-        assert newRecord.type == RepositoryRecordType.CREATE
-        assert deserializedRecord.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord.originalAttributes == newRecord.current.attributes
-    }
-
-    /** This test ensures that the creation of a flowfile record is applied to the specified output stream correctly */
-    @Test
-    void testShouldSerializeAndDeserializeEdit() {
-        // Arrange
-        RepositoryRecord newRecord = buildCreateRecord(flowFileQueue, [id: "1", firstName: "Andy", lastName: "LoPresto"])
-        DataOutputStream dos = dataOutputStream
-
-        esrrs.writeHeader(dataOutputStream)
-
-        // Act
-        esrrs.serializeEdit(null, newRecord, dos)
-        logger.info("Output stream: ${Hex.toHexString(byteArrayOutputStream.toByteArray())} ")
-
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
-        esrrs.readHeader(dis)
-        RepositoryRecord deserializedRecord = esrrs.deserializeEdit(dis, [:], 2)
-
-        /* The records will not be identical, because the process of serializing/deserializing changes the application
-         * of the delta data. The CREATE with a "current" record containing attributes becomes an UPDATE with an
-         * "original" record containing attributes */
-
-        // Assert
-        logger.info("    Original record: ${newRecord.dump()}")
-        logger.info("Deserialized record: ${deserializedRecord.dump()}")
-        assert newRecord.type == RepositoryRecordType.CREATE
-        assert deserializedRecord.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord.originalAttributes == newRecord.current.attributes
-    }
-
-    /** This test ensures that the creation of a flowfile record is applied to the specified output stream correctly with encryption */
-    @Test
-    void testShouldEncryptOutput() {
-        // Arrange
-        RepositoryRecord newRecord = buildCreateRecord(flowFileQueue, [id: "1", firstName: "Andy", lastName: "LoPresto"])
-        DataOutputStream dos = dataOutputStream
-
-        esrrs.writeHeader(dataOutputStream)
-
-        // Act
-        esrrs.serializeRecord(newRecord, dos)
-        byte[] serializedBytes = byteArrayOutputStream.toByteArray()
-        def hexOutput = Hex.toHexString(serializedBytes)
-        logger.info("Output stream (${serializedBytes.length} bytes): ${hexOutput} ")
-
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(serializedBytes))
-        esrrs.readHeader(dis)
-        RepositoryRecord deserializedRecord = esrrs.deserializeRecord(dis, 2)
-
-        /* The records will not be identical, because the process of serializing/deserializing changes the application
-         * of the delta data. The CREATE with a "current" record containing attributes becomes an UPDATE with an
-         * "original" record containing attributes */
-
-        // Assert
-        logger.info("    Original record: ${newRecord.dump()}")
-        logger.info("Deserialized record: ${deserializedRecord.dump()}")
-        assert newRecord.type == RepositoryRecordType.CREATE
-        assert deserializedRecord.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord.originalAttributes == newRecord.current.attributes
-
-        // Ensure the value is not present in plaintext
-        assert !hexOutput.contains(Hex.toHexString("Andy".bytes))
-
-        // Ensure the value is not present in "simple" encryption (reversing bytes)
-        assert !hexOutput.contains(Hex.toHexString("ydnA".bytes))
-
-        // Ensure the encryption metadata is present in the output
-        assert hexOutput.contains(Hex.toHexString("org.apache.nifi.security.repository.block.BlockEncryptionMetadata".bytes))
-    }
-
-    /** This test ensures that multiple records can be serialized and deserialized */
-    @Test
-    void testShouldSerializeAndDeserializeMultipleRecords() {
-        // Arrange
-        RepositoryRecord record1 = buildCreateRecord(flowFileQueue, [id: "1", firstName: "Andy", lastName: "LoPresto"])
-        RepositoryRecord record2 = buildCreateRecord(flowFileQueue, [id: "2", firstName: "Mark", lastName: "Payne"])
-        DataOutputStream dos = dataOutputStream
-
-        // Act
-        esrrs.writeHeader(dos)
-        esrrs.serializeRecord(record1, dos)
-        esrrs.serializeRecord(record2, dos)
-        dos.flush()
-        logger.info("Output stream: ${Hex.toHexString(byteArrayOutputStream.toByteArray())} ")
-
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
-        esrrs.readHeader(dis)
-        RepositoryRecord deserializedRecord1 = esrrs.deserializeRecord(dis, esrrs.getVersion())
-        RepositoryRecord deserializedRecord2 = esrrs.deserializeRecord(dis, esrrs.getVersion())
-
-        /* The records will not be identical, because the process of serializing/deserializing changes the application
-         * of the delta data. The CREATE with a "current" record containing attributes becomes an UPDATE with an
-         * "original" record containing attributes */
-
-        logger.info("Original record 1: ${record1.dump()}")
-        logger.info("Original record 2: ${record2.dump()}")
-        logger.info("Deserialized record 1: ${deserializedRecord1.dump()}")
-        logger.info("Deserialized record 2: ${deserializedRecord2.dump()}")
-
-        // Assert
-        assert record1.type == RepositoryRecordType.CREATE
-        assert record2.type == RepositoryRecordType.CREATE
-
-        assert deserializedRecord1.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord1.originalAttributes == record1.current.attributes
-
-        assert deserializedRecord2.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord2.originalAttributes == record2.current.attributes
-    }
-
-    /** This test ensures that multiple records can be serialized and deserialized using different keys */
-    @Test
-    void testShouldSerializeAndDeserializeMultipleRecordsWithMultipleKeys() {
-        // Arrange
-        RepositoryRecord record1 = buildCreateRecord(flowFileQueue, [id: "1", firstName: "Andy", lastName: "LoPresto"])
-        RepositoryRecord record2 = buildCreateRecord(flowFileQueue, [id: "2", firstName: "Mark", lastName: "Payne"])
-        DataOutputStream dos = dataOutputStream
-
-        // Configure the serde with multiple keys available
-        def multipleKeys = KEYS + [K2: "0F" * 32]
-        FlowFileRepositoryEncryptionConfiguration multipleKeyFFREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, multipleKeys, REPO_IMPL, null)
-
-        esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, multipleKeyFFREC)
-        assert esrrs.getActiveKeyId() == "K1"
-
-        // Act
-        esrrs.writeHeader(dos)
-        esrrs.serializeRecord(record1, dos)
-
-        // Change the active key
-        esrrs.setActiveKeyId("K2")
-        esrrs.serializeRecord(record2, dos)
-        dos.flush()
-        logger.info("Output stream: ${Hex.toHexString(byteArrayOutputStream.toByteArray())} ")
-
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
-        esrrs.readHeader(dis)
-        RepositoryRecord deserializedRecord1 = esrrs.deserializeRecord(dis, esrrs.getVersion())
-        RepositoryRecord deserializedRecord2 = esrrs.deserializeRecord(dis, esrrs.getVersion())
-
-        /* The records will not be identical, because the process of serializing/deserializing changes the application
-         * of the delta data. The CREATE with a "current" record containing attributes becomes an UPDATE with an
-         * "original" record containing attributes */
-
-        logger.info("Original record 1: ${record1.dump()}")
-        logger.info("Original record 2: ${record2.dump()}")
-        logger.info("Deserialized record 1: ${deserializedRecord1.dump()}")
-        logger.info("Deserialized record 2: ${deserializedRecord2.dump()}")
-
-        // Assert
-        assert record1.type == RepositoryRecordType.CREATE
-        assert record2.type == RepositoryRecordType.CREATE
-
-        assert deserializedRecord1.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord1.originalAttributes == record1.current.attributes
-
-        assert deserializedRecord2.type == RepositoryRecordType.UPDATE
-        assert deserializedRecord2.originalAttributes == record2.current.attributes
-    }
-
-
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/resources/logback-test.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/resources/logback-test.xml
deleted file mode 100644
index 617dcb6..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-      http://www.apache.org/licenses/LICENSE-2.0
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-
-<configuration>
-<!--<configuration debug="true">-->
-    <!--<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />-->
-    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>%-4r [%t] %-5p %c{3} - %m%n</pattern>
-        </encoder>
-    </appender>
-
-    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
-        <file>./target/log</file>
-        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <pattern>%date %level [%thread] %logger{40} %msg%n</pattern>
-        </encoder>
-    </appender>
-
-
-    <logger name="org.apache.nifi" level="INFO"/>
-    <logger name="org.apache.nifi.controller.tasks" level="DEBUG"/>"
-    <logger name="org.apache.nifi.controller.service" level="DEBUG"/>
-    <logger name="org.apache.nifi.encrypt" level="DEBUG"/>
-    <logger name="org.apache.nifi.controller.repository" level="DEBUG"/>
-    <logger name="org.apache.nifi.security.repository" level="DEBUG"/>
-    <logger name="org.apache.nifi.controller.service.mock" level="ERROR"/>
-
-    <logger name="StandardProcessSession.claims" level="INFO" />
-
-    <root level="INFO">
-        <appender-ref ref="CONSOLE"/>
-    </root>
-
-</configuration>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index f50dbb9..d082365 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -70,6 +70,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-security-kms</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-repository-encryption</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-properties</artifactId>
         </dependency>
         <dependency>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/EncryptedFileSystemSwapManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/EncryptedFileSystemSwapManager.java
index 6e064e6..1978a64 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/EncryptedFileSystemSwapManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/EncryptedFileSystemSwapManager.java
@@ -17,14 +17,11 @@
 package org.apache.nifi.controller;
 
 import org.apache.nifi.controller.repository.FlowFileSwapManager;
-import org.apache.nifi.security.kms.CryptoUtils;
-import org.apache.nifi.security.kms.EncryptionException;
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.repository.encryption.configuration.kms.RepositoryKeyProviderFactory;
+import org.apache.nifi.repository.encryption.configuration.kms.StandardRepositoryKeyProviderFactory;
 import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.config.FlowFileRepositoryEncryptionConfiguration;
 import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
@@ -53,22 +50,17 @@
     private static final int SIZE_IV_AES_BYTES = 16;
     private static final int SIZE_TAG_GCM_BITS = 128;
 
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedFileSystemSwapManager.class);
     private static final SecureRandom secureRandom = new SecureRandom();
 
     private final SecretKey secretKey;
 
     public EncryptedFileSystemSwapManager(final NiFiProperties nifiProperties)
-            throws IOException, EncryptionException, GeneralSecurityException {
+            throws GeneralSecurityException {
         super(nifiProperties);
-        // acquire reference to FlowFileRepository key
-        final FlowFileRepositoryEncryptionConfiguration configuration = new FlowFileRepositoryEncryptionConfiguration(nifiProperties);
-        if (!CryptoUtils.isValidRepositoryEncryptionConfiguration(configuration)) {
-            logger.error("The flowfile repository encryption configuration is not valid (see above). Shutting down...");
-            throw new EncryptionException("The flowfile repository encryption configuration is not valid");
-        }
-        final KeyProvider keyProvider = RepositoryEncryptorUtils.validateAndBuildRepositoryKeyProvider(configuration);
-        this.secretKey = keyProvider.getKey(configuration.getEncryptionKeyId());
+        final RepositoryKeyProviderFactory repositoryKeyProviderFactory = new StandardRepositoryKeyProviderFactory();
+        final KeyProvider keyProvider = repositoryKeyProviderFactory.getKeyProvider(EncryptedRepositoryType.FLOWFILE, nifiProperties);
+        final String keyId = nifiProperties.getFlowFileRepoEncryptionKeyId();
+        this.secretKey = keyProvider.getKey(keyId);
     }
 
     protected InputStream getInputStream(final File file) throws IOException {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index fab4847..751b6aa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -178,6 +178,7 @@
 import org.apache.nifi.reporting.Severity;
 import org.apache.nifi.reporting.StandardEventAccess;
 import org.apache.nifi.reporting.UserAwareEventAccess;
+import org.apache.nifi.repository.encryption.configuration.EncryptionProtocol;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.security.util.SslContextFactory;
 import org.apache.nifi.security.util.StandardTlsConfiguration;
@@ -241,6 +242,10 @@
     public static final String DEFAULT_SWAP_MANAGER_IMPLEMENTATION = "org.apache.nifi.controller.FileSystemSwapManager";
     public static final String DEFAULT_COMPONENT_STATUS_REPO_IMPLEMENTATION = "org.apache.nifi.controller.status.history.VolatileComponentStatusRepository";
 
+    private static final String ENCRYPTED_PROVENANCE_REPO_IMPLEMENTATION = "org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository";
+    private static final String ENCRYPTED_CONTENT_REPO_IMPLEMENTATION = "org.apache.nifi.controller.repository.crypto.EncryptedFileSystemRepository";
+    private static final String ENCRYPTED_SWAP_MANAGER_IMPLEMENTATION = "org.apache.nifi.controller.EncryptedFileSystemSwapManager";
+
     public static final String GRACEFUL_SHUTDOWN_PERIOD = "nifi.flowcontroller.graceful.shutdown.seconds";
     public static final long DEFAULT_GRACEFUL_SHUTDOWN_SECONDS = 10;
 
@@ -818,7 +823,9 @@
     }
 
     public FlowFileSwapManager createSwapManager() {
-        final String implementationClassName = nifiProperties.getProperty(NiFiProperties.FLOWFILE_SWAP_MANAGER_IMPLEMENTATION, DEFAULT_SWAP_MANAGER_IMPLEMENTATION);
+        final String implementationClassName = isEncryptionProtocolVersionConfigured(nifiProperties)
+                ? ENCRYPTED_SWAP_MANAGER_IMPLEMENTATION
+                : nifiProperties.getProperty(NiFiProperties.FLOWFILE_SWAP_MANAGER_IMPLEMENTATION, DEFAULT_SWAP_MANAGER_IMPLEMENTATION);
         if (implementationClassName == null) {
             return null;
         }
@@ -1140,13 +1147,16 @@
         return startConnectablesAfterInitialization.contains(component) || startRemoteGroupPortsAfterInitialization.contains(component);
     }
 
-    private ContentRepository createContentRepository(final NiFiProperties properties) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-        final String implementationClassName = properties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION, DEFAULT_CONTENT_REPO_IMPLEMENTATION);
+    private ContentRepository createContentRepository(final NiFiProperties properties) {
+        final String implementationClassName = isEncryptionProtocolVersionConfigured(properties)
+                ? ENCRYPTED_CONTENT_REPO_IMPLEMENTATION
+                : properties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION, DEFAULT_CONTENT_REPO_IMPLEMENTATION);
         if (implementationClassName == null) {
             throw new RuntimeException("Cannot create Content Repository because the NiFi Properties is missing the following property: "
                     + NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION);
         }
 
+        LOG.info("Creating Content Repository [{}]", implementationClassName);
         try {
             final ContentRepository contentRepo = NarThreadContextClassLoader.createInstance(extensionManager, implementationClassName, ContentRepository.class, properties);
             synchronized (contentRepo) {
@@ -1158,13 +1168,16 @@
         }
     }
 
-    private ProvenanceRepository createProvenanceRepository(final NiFiProperties properties) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-        final String implementationClassName = properties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, DEFAULT_PROVENANCE_REPO_IMPLEMENTATION);
-        if (implementationClassName == null) {
+    private ProvenanceRepository createProvenanceRepository(final NiFiProperties properties) {
+        final String implementationClassName = isEncryptionProtocolVersionConfigured(properties)
+                ? ENCRYPTED_PROVENANCE_REPO_IMPLEMENTATION
+                : properties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, DEFAULT_PROVENANCE_REPO_IMPLEMENTATION);
+        if (StringUtils.isBlank(implementationClassName)) {
             throw new RuntimeException("Cannot create Provenance Repository because the NiFi Properties is missing the following property: "
                     + NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS);
         }
 
+        LOG.info("Creating Provenance Repository [{}]", implementationClassName);
         try {
             return NarThreadContextClassLoader.createInstance(extensionManager, implementationClassName, ProvenanceRepository.class, properties);
         } catch (final Exception e) {
@@ -3096,6 +3109,11 @@
         return flowFileEventRepository;
     }
 
+    private boolean isEncryptionProtocolVersionConfigured(final NiFiProperties properties) {
+        final String version = properties.getProperty(NiFiProperties.REPOSITORY_ENCRYPTION_PROTOCOL_VERSION);
+        return Integer.toString(EncryptionProtocol.VERSION_1.getVersionNumber()).equals(version);
+    }
+
     private static class HeartbeatBean {
 
         private final ProcessGroup rootGroup;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/WriteAheadFlowFileRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/WriteAheadFlowFileRepository.java
index f069de6..2af45c8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/WriteAheadFlowFileRepository.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/WriteAheadFlowFileRepository.java
@@ -22,8 +22,8 @@
 import org.apache.nifi.controller.repository.claim.ResourceClaim;
 import org.apache.nifi.controller.repository.claim.ResourceClaimManager;
 import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.repository.encryption.configuration.EncryptionProtocol;
 import org.apache.nifi.repository.schema.FieldCache;
-import org.apache.nifi.security.kms.EncryptionException;
 import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.wali.EncryptedSequentialAccessWriteAheadLog;
@@ -216,12 +216,13 @@
     }
 
     protected RepositoryRecordSerdeFactory createSerdeFactory(final ResourceClaimManager claimManager, final FieldCache fieldCache) {
-        if (EncryptedSequentialAccessWriteAheadLog.class.getName().equals(nifiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION))) {
-            try {
-                return new EncryptedRepositoryRecordSerdeFactory(claimManager, nifiProperties, fieldCache);
-            } catch (final EncryptionException e) {
-                throw new RuntimeException(e);
-            }
+        final String version = nifiProperties.getProperty(NiFiProperties.REPOSITORY_ENCRYPTION_PROTOCOL_VERSION);
+        final boolean encryptionConfigured = Integer.toString(EncryptionProtocol.VERSION_1.getVersionNumber()).equals(version);
+        final String implementation = nifiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION);
+
+        if (EncryptedSequentialAccessWriteAheadLog.class.getName().equals(implementation) || encryptionConfigured) {
+            logger.info("Creating Encrypted FlowFile Repository [{}]", EncryptedSequentialAccessWriteAheadLog.class.getName());
+            return new EncryptedRepositoryRecordSerdeFactory(claimManager, nifiProperties, fieldCache);
         } else {
             return new StandardRepositoryRecordSerdeFactory(claimManager, fieldCache);
         }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepository.java
index f9041d9..aa0b175 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepository.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepository.java
@@ -21,12 +21,13 @@
 import org.apache.nifi.controller.repository.claim.ContentClaim;
 import org.apache.nifi.controller.repository.claim.ResourceClaim;
 import org.apache.nifi.controller.repository.claim.StandardContentClaim;
-import org.apache.nifi.security.kms.EncryptionException;
+import org.apache.nifi.repository.encryption.AesCtrStreamRepositoryEncryptor;
+import org.apache.nifi.repository.encryption.RepositoryEncryptor;
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.kms.RepositoryKeyProviderFactory;
+import org.apache.nifi.repository.encryption.configuration.kms.StandardRepositoryKeyProviderFactory;
 import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.security.repository.stream.RepositoryObjectStreamEncryptor;
-import org.apache.nifi.security.repository.stream.aes.RepositoryObjectAESCTREncryptor;
 import org.apache.nifi.stream.io.ByteCountingOutputStream;
 import org.apache.nifi.stream.io.NonCloseableOutputStream;
 import org.apache.nifi.stream.io.StreamUtils;
@@ -40,7 +41,7 @@
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.file.Path;
-import java.security.KeyManagementException;
+import java.util.Objects;
 
 /**
  * This class is an implementation of the {@link FileSystemRepository} content repository which provides transparent
@@ -54,25 +55,23 @@
 public class EncryptedFileSystemRepository extends FileSystemRepository {
     private static final Logger logger = LoggerFactory.getLogger(EncryptedFileSystemRepository.class);
 
-    private String activeKeyId;
-    private KeyProvider keyProvider;
+    private RepositoryEncryptor<OutputStream, InputStream> repositoryEncryptor;
+
+    private String keyId;
 
     /**
      * Default no args constructor for service loading only
      */
     public EncryptedFileSystemRepository() {
-        super();
-        keyProvider = null;
+
     }
 
     public EncryptedFileSystemRepository(final NiFiProperties niFiProperties) throws IOException {
         super(niFiProperties);
-
-        // Initialize the encryption-specific fields
-        this.keyProvider = RepositoryEncryptorUtils.validateAndBuildRepositoryKeyProvider(niFiProperties, RepositoryType.CONTENT);
-
-        // Set active key ID
-        setActiveKeyId(niFiProperties.getContentRepositoryEncryptionKeyId());
+        final RepositoryKeyProviderFactory repositoryKeyProviderFactory = new StandardRepositoryKeyProviderFactory();
+        final KeyProvider keyProvider = repositoryKeyProviderFactory.getKeyProvider(EncryptedRepositoryType.CONTENT, niFiProperties);
+        repositoryEncryptor = new AesCtrStreamRepositoryEncryptor(keyProvider, EncryptionMetadataHeader.CONTENT);
+        keyId = Objects.requireNonNull(niFiProperties.getContentRepositoryEncryptionKeyId(), "Key Identifier required");
     }
 
     /**
@@ -178,35 +177,13 @@
      */
     @Override
     public InputStream read(final ContentClaim claim) throws IOException {
-        InputStream inputStream = super.read(claim);
-
+        final InputStream inputStream = super.read(claim);
         if (claim == null) {
             return inputStream;
         }
 
-        try {
-            String recordId = getRecordId(claim);
-            logger.debug("Creating decrypted input stream to read flowfile content with record ID: " + recordId);
-
-            final InputStream decryptingInputStream = getDecryptingInputStream(inputStream, recordId);
-            logger.debug("Reading from record ID {}", recordId);
-            if (logger.isTraceEnabled()) {
-                logger.trace("Stack trace: ", new RuntimeException("Stack Trace for reading from record ID " + recordId));
-            }
-
-            return decryptingInputStream;
-        } catch (EncryptionException | KeyManagementException e) {
-            logger.error("Encountered an error instantiating the encrypted content repository input stream: " + e.getMessage());
-            throw new IOException("Error creating encrypted content repository input stream", e);
-        }
-    }
-
-    private InputStream getDecryptingInputStream(InputStream inputStream, String recordId) throws KeyManagementException, EncryptionException {
-        RepositoryObjectStreamEncryptor encryptor = new RepositoryObjectAESCTREncryptor();
-        encryptor.initialize(keyProvider);
-
-        // ECROS wrapping COS wrapping BCOS wrapping FOS
-        return encryptor.decrypt(inputStream, recordId);
+        final String recordId = getRecordId(claim);
+        return repositoryEncryptor.decrypt(inputStream, recordId);
     }
 
     /**
@@ -220,42 +197,11 @@
      */
     @Override
     public OutputStream write(final ContentClaim claim) throws IOException {
-        StandardContentClaim scc = validateContentClaimForWriting(claim);
-
-        // BCOS wrapping FOS
-        ByteCountingOutputStream claimStream = getWritableClaimStreamByResourceClaim(scc.getResourceClaim());
+        final StandardContentClaim scc = validateContentClaimForWriting(claim);
+        final ByteCountingOutputStream claimStream = getWritableClaimStreamByResourceClaim(scc.getResourceClaim());
         final long startingOffset = claimStream.getBytesWritten();
-
-        try {
-            String keyId = getActiveKeyId();
-            String recordId = getRecordId(claim);
-            logger.debug("Creating encrypted output stream (keyId: " + keyId + ") to write flowfile content with record ID: " + recordId);
-            final OutputStream out = getEncryptedOutputStream(scc, claimStream, startingOffset, keyId, recordId);
-            logger.debug("Writing to {}", out);
-            if (logger.isTraceEnabled()) {
-                logger.trace("Stack trace: ", new RuntimeException("Stack Trace for writing to " + out));
-            }
-
-            return out;
-        } catch (EncryptionException | KeyManagementException e) {
-            logger.error("Encountered an error instantiating the encrypted content repository output stream: " + e.getMessage());
-            throw new IOException("Error creating encrypted content repository output stream", e);
-        }
-    }
-
-    String getActiveKeyId() {
-        return activeKeyId;
-    }
-
-    public void setActiveKeyId(String activeKeyId) {
-        // Key must not be blank and key provider must make key available
-        if (StringUtils.isNotBlank(activeKeyId) && keyProvider.keyExists(activeKeyId)) {
-            this.activeKeyId = activeKeyId;
-            logger.debug("Set active key ID to '" + activeKeyId + "'");
-        } else {
-            logger.warn("Attempted to set active key ID to '" + activeKeyId + "' but that is not a valid or available key ID. Keeping active key ID as '" + this.activeKeyId + "'");
-
-        }
+        final String recordId = getRecordId(claim);
+        return new EncryptedContentRepositoryOutputStream(scc, claimStream, recordId, startingOffset);
     }
 
     /**
@@ -267,31 +213,18 @@
      * @param claim the content claim
      * @return the string identifier
      */
-    public static String getRecordId(ContentClaim claim) {
+    public static String getRecordId(final ContentClaim claim) {
         // For version 1, use the content claim's resource claim ID as the record ID rather than introducing a new field in the metadata
         if (claim != null && claim.getResourceClaim() != null
                 && !StringUtils.isBlank(claim.getResourceClaim().getId())) {
             return "nifi-ecr-rc-" + claim.getResourceClaim().getId() + "+" + claim.getOffset();
         } else {
-            String tempId = "nifi-ecr-ts-" + System.nanoTime();
-            logger.error("Cannot determine record ID from null content claim or claim with missing/empty resource claim ID; using timestamp-generated ID: " + tempId + "+0");
+            final String tempId = "nifi-ecr-ts-" + System.nanoTime();
+            logger.error("Cannot determine record ID from null content claim or claim with missing/empty resource claim ID; using timestamp-generated ID [{}]", tempId + "+0");
             return tempId;
         }
     }
 
-    private OutputStream getEncryptedOutputStream(StandardContentClaim scc,
-                                                  ByteCountingOutputStream claimStream,
-                                                  long startingOffset,
-                                                  String keyId,
-                                                  String recordId) throws KeyManagementException,
-            EncryptionException {
-        RepositoryObjectStreamEncryptor encryptor = new RepositoryObjectAESCTREncryptor();
-        encryptor.initialize(keyProvider);
-
-        // ECROS wrapping COS wrapping BCOS wrapping FOS
-        return new EncryptedContentRepositoryOutputStream(scc, claimStream, encryptor, recordId, keyId, startingOffset);
-    }
-
     /**
      * Private class which wraps the {@link org.apache.nifi.controller.repository.FileSystemRepository.ContentRepositoryOutputStream}'s
      * internal {@link ByteCountingOutputStream} with a {@link CipherOutputStream}
@@ -301,14 +234,15 @@
         private final CipherOutputStream cipherOutputStream;
         private final long startingOffset;
 
-        EncryptedContentRepositoryOutputStream(StandardContentClaim scc,
-                                               ByteCountingOutputStream byteCountingOutputStream,
-                                               RepositoryObjectStreamEncryptor encryptor, String recordId, String keyId, long startingOffset) throws EncryptionException {
+        EncryptedContentRepositoryOutputStream(final StandardContentClaim scc,
+                                               final ByteCountingOutputStream byteCountingOutputStream,
+                                               final String recordId,
+                                               final long startingOffset) {
             super(scc, byteCountingOutputStream, 0);
             this.startingOffset = startingOffset;
 
             // Set up cipher stream
-            this.cipherOutputStream = (CipherOutputStream) encryptor.encrypt(new NonCloseableOutputStream(byteCountingOutputStream), recordId, keyId);
+            this.cipherOutputStream = (CipherOutputStream) repositoryEncryptor.encrypt(new NonCloseableOutputStream(byteCountingOutputStream), recordId, keyId);
         }
 
         @Override
@@ -341,7 +275,7 @@
          * @param len the length in bytes to write
          * @throws IOException if there is a problem writing the output
          */
-        private void writeBytes(byte[] b, int off, int len) throws IOException {
+        private void writeBytes(final byte[] b, final int off, final int len) throws IOException {
             if (closed) {
                 throw new IOException("Stream is closed");
             }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepositoryTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepositoryTest.groovy
index fd0185d..ed13980 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepositoryTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/repository/crypto/EncryptedFileSystemRepositoryTest.groovy
@@ -20,17 +20,11 @@
 import org.apache.nifi.controller.repository.claim.ContentClaim
 import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager
 import org.apache.nifi.controller.repository.util.DiskUtils
-import org.apache.nifi.security.kms.KeyProvider
 import org.apache.nifi.security.kms.StaticKeyProvider
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils
-import org.apache.nifi.security.repository.RepositoryObjectEncryptionMetadata
-import org.apache.nifi.security.util.EncryptionMethod
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider
 import org.apache.nifi.util.NiFiProperties
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.bouncycastle.util.encoders.Hex
 import org.junit.After
-import org.junit.AfterClass
 import org.junit.Assume
 import org.junit.Before
 import org.junit.BeforeClass
@@ -40,49 +34,24 @@
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
-import javax.crypto.Cipher
-import javax.crypto.CipherInputStream
-import javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
 import java.nio.charset.StandardCharsets
 import java.nio.file.Path
 import java.security.Security
 
-import static groovy.test.GroovyAssert.shouldFail
-
 @RunWith(JUnit4.class)
 class EncryptedFileSystemRepositoryTest {
     private static final Logger logger = LoggerFactory.getLogger(EncryptedFileSystemRepositoryTest.class)
 
     private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
-    private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-    private static final String KEY_HEX_1 = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
-    private static final String KEY_HEX_2 = "00" * (isUnlimitedStrengthCryptoAvailable() ? 32 : 16)
-    private static final String KEY_HEX_3 = "AA" * (isUnlimitedStrengthCryptoAvailable() ? 32 : 16)
-
+    private static final String KEY_HEX_1 = KEY_HEX_128 * 2
     private static final String KEY_ID_1 = "K1"
-    private static final String KEY_ID_2 = "K2"
-    private static final String KEY_ID_3 = "K3"
-
-    private static AESKeyedCipherProvider mockCipherProvider
-
-    private static String ORIGINAL_LOG_LEVEL
 
     private EncryptedFileSystemRepository repository = null
     private final File rootFile = new File("target/content_repository")
     private NiFiProperties nifiProperties
-    private static final String LOG_PACKAGE = "org.slf4j.simpleLogger.log.org.apache.nifi.controller.repository.crypto"
 
     private static final boolean isLossTolerant = false
 
-    // Mapping of key IDs to keys
-    final def KEYS = [
-            (KEY_ID_1): new SecretKeySpec(Hex.decode(KEY_HEX_1), "AES"),
-            (KEY_ID_2): new SecretKeySpec(Hex.decode(KEY_HEX_2), "AES"),
-            (KEY_ID_3): new SecretKeySpec(Hex.decode(KEY_HEX_3), "AES"),
-    ]
     private static final String DEFAULT_NIFI_PROPS_PATH = "/conf/nifi.properties"
 
     private static final Map<String, String> DEFAULT_ENCRYPTION_PROPS = [
@@ -96,22 +65,12 @@
     @BeforeClass
     static void setUpOnce() throws Exception {
         Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS)
-        ORIGINAL_LOG_LEVEL = System.getProperty(LOG_PACKAGE)
 
         Security.addProvider(new BouncyCastleProvider())
 
         logger.metaClass.methodMissing = { String name, args ->
             logger.debug("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
         }
-
-        mockCipherProvider = [
-                getCipher: { EncryptionMethod em, SecretKey key, byte[] ivBytes, boolean encryptMode ->
-                    logger.mock("Getting cipher for ${em} with IV ${Hex.toHexString(ivBytes)} encrypt ${encryptMode}")
-                    Cipher cipher = Cipher.getInstance(em.algorithm)
-                    cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, key, new IvParameterSpec(ivBytes))
-                    cipher
-                }
-        ] as AESKeyedCipherProvider
     }
 
     @Before
@@ -147,17 +106,6 @@
         repository.shutdown()
     }
 
-    @AfterClass
-    static void tearDownOnce() throws Exception {
-        if (ORIGINAL_LOG_LEVEL) {
-            System.setProperty(LOG_PACKAGE, ORIGINAL_LOG_LEVEL)
-        }
-    }
-
-    private static boolean isUnlimitedStrengthCryptoAvailable() {
-        Cipher.getMaxAllowedKeyLength("AES") > 128
-    }
-
     @Test
     void testReadNullContentClaimShouldReturnEmptyInputStream() {
         final InputStream inputStream = repository.read((ContentClaim) null)
@@ -173,9 +121,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         String plainContent = "hello"
         byte[] plainBytes = plainContent.bytes
         logger.info("Writing \"${plainContent}\" (${plainContent.length()}): ${Hex.toHexString(plainBytes)}")
@@ -183,9 +128,6 @@
         // Act
         writeContentToClaim(claim, plainBytes)
 
-        // Independently access the persisted file and verify that the content is encrypted
-        independentlyVerifyTextClaimEncryption(claim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first(), plainContent)
-
         // Assert
 
         // Use the EFSR to decrypt the same content
@@ -201,9 +143,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File image = new File("src/test/resources/encrypted_content_repo.png")
         byte[] plainBytes = image.bytes
         logger.info("Writing \"${image.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -211,9 +150,6 @@
         // Act
         writeContentToClaim(claim, plainBytes)
 
-        // Independently access the persisted file and verify that the content is encrypted
-        independentlyVerifyByteClaimEncryption(claim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first())
-
         // Assert
 
         // Use the EFSR to decrypt the same content
@@ -225,11 +161,6 @@
      */
     @Test
     void testShouldEncryptAndDecryptMultipleRecords() {
-        // Arrange
-
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         def content = [
                 "This is a plaintext message. ",
                 "Some,csv,data\ncol1,col2,col3",
@@ -252,129 +183,6 @@
     }
 
     /**
-     * Simple test to write multiple pieces of encrypted content, each using a different encryption key, to the repository and then retrieve & decrypt via the repository.
-     */
-    @Test
-    void testShouldEncryptAndDecryptMultipleRecordsWithDifferentKeys() {
-        // Arrange
-
-        def content = [
-                "K1": "This is a plaintext message. ",
-                "K2": "Some,csv,data\ncol1,col2,col3",
-                "K3": "Easy to read 0123456789abcdef"
-        ]
-
-        // Set up mock key provider and inject into repository (manually set key IDs later)
-        KeyProvider mockKeyProvider = createMockKeyProvider()
-        repository.keyProvider = mockKeyProvider
-
-        int i = 0
-
-        // Act
-        def claims = content.collectEntries { String keyId, String pieceOfContent ->
-            // Increment the key ID used (set "active key ID")
-            repository.setActiveKeyId(keyId)
-            logger.info("Set key ID for content ${i++} to ${keyId}")
-
-            // Create a claim for each piece of content
-            final ContentClaim claim = repository.create(isLossTolerant)
-
-            // Write the content out
-            writeContentToClaim(claim, pieceOfContent.bytes)
-
-            [keyId, claim]
-        } as Map<String, ContentClaim>
-
-        // TODO: Revisit for refactoring
-
-        // Manually verify different key IDs used for each claim
-        claims.each { String keyId, ContentClaim claim ->
-            // Independently access the persisted file and verify that the content is encrypted
-            logger.info("Manual verification of record ID ${EncryptedFileSystemRepository.getRecordId(claim)}")
-            String persistedFilePath = getPersistedFilePath(claim)
-            logger.verify("Persisted file: ${persistedFilePath}")
-            byte[] persistedBytes = new File(persistedFilePath).bytes
-            logger.verify("Read bytes (${persistedBytes.length}): ${pba(persistedBytes)}")
-
-            // Skip to the section for this content claim
-            long start = claim.offset
-            long end = claim.offset + claim.length
-            byte[] contentSection = persistedBytes[start..<end]
-            logger.verify("Extracted ${contentSection.length} bytes from ${start} to <${end}")
-
-            // Verify that the persisted keyId is what was expected
-            RepositoryObjectEncryptionMetadata metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(new ByteArrayInputStream(contentSection))
-            logger.verify("Parsed encryption metadata: ${metadata}")
-            assert metadata.keyId == keyId
-        }
-
-        // Assert
-        claims.each { String keyId, ContentClaim claim ->
-            String pieceOfContent = content[keyId]
-
-            // Use the EFSR to decrypt the same content
-            def retrievedBytes = verifyClaimDecryption(claim, pieceOfContent.bytes)
-            assert new String(retrievedBytes, StandardCharsets.UTF_8) == pieceOfContent
-        }
-    }
-
-    /**
-     * Simple test to write encrypted content to the repository, independently read the persisted file to ensure the content is encrypted, and then retrieve & decrypt via the repository.
-     */
-    @Test
-    void testShouldValidateActiveKeyId() {
-        // Arrange
-
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = createMockKeyProvider()
-        repository.keyProvider = mockKeyProvider
-
-        def validKeyIds = mockKeyProvider.getAvailableKeyIds()
-        def invalidKeyIds = [null, "", "   ", "K4"]
-
-
-        // Act
-        validKeyIds.each { String keyId ->
-            repository.setActiveKeyId(keyId)
-
-            // Assert
-            assert repository.getActiveKeyId() == keyId
-        }
-
-        // Reset to empty
-        repository.@activeKeyId = null
-        invalidKeyIds.collect { String invalidKeyId ->
-            repository.setActiveKeyId(invalidKeyId)
-
-            // Assert
-            assert repository.getActiveKeyId() == null
-        }
-    }
-
-    /**
-     * Simple test to show blocking on uninitialized key ID and key provider.
-     */
-    @Test
-    void testWriteShouldRequireActiveKeyId() {
-        // Arrange
-        repository.@activeKeyId = null
-
-        final ContentClaim claim = repository.create(isLossTolerant)
-
-        String plainContent = "hello"
-        byte[] plainBytes = plainContent.bytes
-
-        // Act
-        def msg = shouldFail(Exception) {
-            writeContentToClaim(claim, plainBytes)
-        }
-
-        // Assert
-        assert msg.localizedMessage == "Error creating encrypted content repository output stream"
-        assert msg.cause.localizedMessage =~ "The .* and key ID cannot be missing"
-    }
-
-    /**
      * Simple test to show no blocking on uninitialized key ID to retrieve content.
      */
     @Test
@@ -382,18 +190,12 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         String plainContent = "hello"
         byte[] plainBytes = plainContent.bytes
 
         // Write the encrypted content to the repository
         writeContentToClaim(claim, plainBytes)
 
-        // Reset the active key ID to null
-        repository.@activeKeyId = null
-
         // Act
         final InputStream inputStream = repository.read(claim)
         byte[] retrievedContent = inputStream.bytes
@@ -440,9 +242,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File image = new File("src/test/resources/bgBannerFoot.png")
         byte[] plainBytes = image.bytes
         logger.info("Writing \"${image.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -451,11 +250,6 @@
         final long bytesRead = repository.importFrom(image.newInputStream(), claim)
         logger.info("Read ${bytesRead} bytes from ${image.name} into ${claim.resourceClaim.id}")
 
-        // Independently access the persisted file and verify that the content is encrypted
-        independentlyVerifyByteClaimEncryption(claim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first())
-
-        // Assert
-
         // Use the EFSR to decrypt the same content
         verifyClaimDecryption(claim, plainBytes)
     }
@@ -468,9 +262,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File image = new File("src/test/resources/bgBannerFoot.png")
         byte[] plainBytes = image.bytes
         logger.info("Writing \"${image.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -479,11 +270,6 @@
         final long bytesRead = repository.importFrom(image.toPath(), claim)
         logger.info("Read ${bytesRead} bytes from ${image.name} into ${claim.resourceClaim.id}")
 
-        // Independently access the persisted file and verify that the content is encrypted
-        independentlyVerifyByteClaimEncryption(claim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first())
-
-        // Assert
-
         // Use the EFSR to decrypt the same content
         verifyClaimDecryption(claim, plainBytes)
     }
@@ -496,9 +282,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File image = new File("src/test/resources/bgBannerFoot.png")
         byte[] plainBytes = image.bytes
         logger.info("Writing \"${image.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -527,9 +310,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File longText = new File("src/test/resources/longtext.txt")
         byte[] plainBytes = longText.bytes
         logger.info("Writing \"${longText.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -565,9 +345,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File image = new File("src/test/resources/bgBannerFoot.png")
         byte[] plainBytes = image.bytes
         logger.info("Writing \"${image.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -602,9 +379,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File longText = new File("src/test/resources/longtext.txt")
         byte[] plainBytes = longText.bytes
         logger.info("Writing \"${longText.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -646,9 +420,6 @@
         // Arrange
         final ContentClaim claim = repository.create(isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File textFile = new File("src/test/resources/longtext.txt")
         byte[] plainBytes = textFile.bytes
         logger.info("Writing \"${textFile.name}\" (${plainBytes.length}): ${pba(plainBytes)}")
@@ -656,21 +427,11 @@
         // Write to the content repository (encrypted)
         writeContentToClaim(claim, plainBytes)
 
-        // Independently access the persisted file and verify that the content is encrypted
-        independentlyVerifyByteClaimEncryption(claim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first())
-
-        // Act
-
         // Clone the content claim
         logger.info("Preparing to clone claim ${claim}")
         ContentClaim clonedClaim = repository.clone(claim, isLossTolerant)
         logger.info("Cloned claim ${claim} to ${clonedClaim}")
 
-        // Independently access the persisted file and verify that the cloned content is encrypted
-        independentlyVerifyByteClaimEncryption(clonedClaim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first(), "cloned")
-
-        // Assert
-
         // Use the EFSR to decrypt the original claim content
         def retrievedOriginalBytes = verifyClaimDecryption(claim, plainBytes)
         assert retrievedOriginalBytes == plainBytes
@@ -689,9 +450,6 @@
         int claimCount = 2
         def claims = createClaims(claimCount, isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File textFile = new File("src/test/resources/longtext.txt")
         byte[] plainBytes = textFile.bytes
         String plainContent = textFile.text
@@ -702,8 +460,6 @@
         // Write each piece of content to the respective claim
         writeContentToClaims(formClaimMap(claims, content))
 
-        // Act
-
         // Merge the two content claims
         logger.info("Preparing to merge claims ${claims}")
         ContentClaim mergedClaim = repository.create(isLossTolerant)
@@ -711,11 +467,6 @@
         long bytesWrittenDuringMerge = repository.merge(claims, mergedClaim, null, null, null)
         logger.info("Merged ${claims.size()} claims (${bytesWrittenDuringMerge} bytes) to ${mergedClaim}")
 
-        // Assert
-
-        // Verify the bytes on disk are encrypted successfully
-        independentlyVerifyTextClaimEncryption(mergedClaim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first(), plainContent, "merged")
-
         // Use the EFSR to decrypt the original claims content
         claims.eachWithIndex { ContentClaim claim, int i ->
             verifyClaimDecryption(claim, content[i].bytes)
@@ -734,9 +485,6 @@
         int claimCount = 4
         def claims = createClaims(claimCount, isLossTolerant)
 
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
         File textFile = new File("src/test/resources/longtext.txt")
         String plainContent = textFile.text
 
@@ -752,8 +500,6 @@
         String footer = "\n---Footer---"
         final String EXPECTED_MERGED_CONTENT = header + content.join(demarcator) + footer
 
-        // Act
-
         // Merge the content claims
         logger.info("Preparing to merge claims ${claims}")
         ContentClaim mergedClaim = repository.create(isLossTolerant)
@@ -761,11 +507,6 @@
         long bytesWrittenDuringMerge = repository.merge(claims, mergedClaim, header.bytes, footer.bytes, demarcator.bytes)
         logger.info("Merged ${claims.size()} claims (${bytesWrittenDuringMerge} bytes) to ${mergedClaim}")
 
-        // Assert
-
-        // Verify the bytes on disk are encrypted successfully
-        independentlyVerifyTextClaimEncryption(mergedClaim, EXPECTED_MERGED_CONTENT.bytes, mockKeyProvider, mockKeyProvider.availableKeyIds.first(), EXPECTED_MERGED_CONTENT, "merged")
-
         // Use the EFSR to decrypt the original claims content
         claims.eachWithIndex { ContentClaim claim, int i ->
             verifyClaimDecryption(claim, content[i].bytes)
@@ -776,62 +517,6 @@
     }
 
     /**
-     * Simple test to merge two encrypted content claims (each encrypted with a different key) and ensure that the merged encryption metadata accurately reflects the new claim and allows for decryption.
-     */
-    @Test
-    void testMergeWithDifferentSourceKeysShouldUpdateEncryptionMetadata() {
-        // Arrange
-        int claimCount = 2
-        def claims = createClaims(claimCount, isLossTolerant)
-
-        // Set up mock key provider and inject into repository
-        KeyProvider mockKeyProvider = injectDefaultMockKeyProviderToRepository()
-
-        File textFile = new File("src/test/resources/longtext.txt")
-        byte[] plainBytes = textFile.bytes
-        String plainContent = textFile.text
-
-        // Split the long text into two claims
-        def content = splitTextIntoSections(plainContent, claimCount)
-
-        // Write each piece of content to the respective claim (using a different key)
-        writeContentToClaim(claims.first(), content.first().bytes)
-
-        // Set new active key
-        repository.setActiveKeyId(mockKeyProvider.availableKeyIds[1])
-
-        // Write the second piece of content
-        writeContentToClaim(claims.last(), content.last().bytes)
-
-        // Act
-
-        // Switch to a new key for the merged claim
-        repository.setActiveKeyId(mockKeyProvider.availableKeyIds.last())
-
-        // Merge the two content claims
-        logger.info("Preparing to merge claims ${claims}")
-        ContentClaim mergedClaim = repository.create(isLossTolerant)
-        // The header, footer, and demarcator are null in this case
-        long bytesWrittenDuringMerge = repository.merge(claims, mergedClaim, null, null, null)
-        logger.info("Merged ${claims.size()} claims (${bytesWrittenDuringMerge} bytes) to ${mergedClaim}")
-
-        // Assert
-
-        // Verify the bytes on disk are encrypted successfully
-        independentlyVerifyTextClaimEncryption(mergedClaim, plainBytes, mockKeyProvider, mockKeyProvider.availableKeyIds.last(), plainContent, "merged")
-
-        // Use the EFSR to decrypt the original claims content
-        claims.eachWithIndex { ContentClaim claim, int i ->
-            verifyClaimDecryption(claim, content[i].bytes)
-        }
-
-        // Use the EFSR to decrypt the merged claim content
-        verifyClaimDecryption(mergedClaim, plainBytes, "merged")
-    }
-
-    // TODO: Test archiving & cleanup
-
-    /**
      * Returns a {@code List<String>} with length {@code N}, where N is the number of elements requested. Each element
      * will be roughly the same size.
      *
@@ -852,20 +537,6 @@
     }
 
     /**
-     * Helper method to configure the default mock {@link KeyProvider}, inject it into the
-     * {@link EncryptedFileSystemRepository}, and set the active key ID to the first available
-     * key ID.
-     *
-     * @return the configured mock key provider
-     */
-    private KeyProvider injectDefaultMockKeyProviderToRepository() {
-        KeyProvider mockKeyProvider = createMockKeyProvider()
-        repository.keyProvider = mockKeyProvider
-        repository.setActiveKeyId(mockKeyProvider.getAvailableKeyIds().first())
-        mockKeyProvider
-    }
-
-    /**
      * Helper method to verify the provided claim content equals the expected plain content,
      * decrypted via the {@link EncryptedFileSystemRepository#read()} method.
      *
@@ -885,68 +556,6 @@
     }
 
     /**
-     * Internal helper method to independently examine the claim as persisted on disk and
-     * generate a {@link Cipher} to decrypt the bytes and compare them to an expected value.
-     * This method expects the persisted content to be UTF-8 text.
-     *
-     * @param claim the claim under examination
-     * @param plainBytes the expected plain byte[]
-     * @param keyProvider the key provider
-     * @param expectedKeyId the expected key ID used for encryption (verifies)
-     * @param plainContent the expected plain text
-     * @param description used for contextualized log statements
-     */
-    private void independentlyVerifyTextClaimEncryption(ContentClaim claim, byte[] plainBytes, KeyProvider keyProvider, String expectedKeyId, String plainContent, String description = "claim") {
-        byte[] recoveredBytes = independentlyVerifyByteClaimEncryption(claim, plainBytes, keyProvider, expectedKeyId, description)
-        logger.verify("Decrypted text ${new String(recoveredBytes, StandardCharsets.UTF_8)}")
-        assert new String(recoveredBytes, StandardCharsets.UTF_8) == plainContent
-    }
-
-    /**
-     * Internal helper method to independently examine the claim as persisted on disk and
-     * generate a {@link Cipher} to decrypt the bytes and compare them to an expected value.
-     * This method expects the persisted content to be arbitrary bytes.
-     *
-     * @param claim the claim under examination
-     * @param plainBytes the expected plain byte[]
-     * @param keyProvider the key provider
-     * @param expectedKeyId the expected key ID used for encryption (verifies)
-     * @param description used for contextualized log statements
-     * @return the retrieved, decrypted byte[]
-     */
-    private byte[] independentlyVerifyByteClaimEncryption(ContentClaim claim, byte[] plainBytes, KeyProvider mockKeyProvider, String expectedKeyId, String description = "claim") {
-        // Independently access the persisted file and verify that the content is encrypted
-        String persistedFilePath = getPersistedFilePath(claim)
-        logger.verify("Persisted file: ${persistedFilePath}")
-        def persistedBytes = new File(persistedFilePath).bytes
-        logger.verify("Read bytes (${persistedBytes.length}): ${pba(persistedBytes)}")
-
-        // Extract the claim (using the claim offset)
-        byte[] persistedClaimBytes = Arrays.copyOfRange(persistedBytes, claim.offset as int, persistedBytes.length)
-        logger.verify("Persisted ${description} bytes (encrypted) (last ${persistedClaimBytes.length}) [${Hex.toHexString(persistedClaimBytes)}] != plain bytes (${plainBytes.length}) [${Hex.toHexString(plainBytes)}]")
-        assert persistedClaimBytes != plainBytes
-
-        // Extract the persisted encryption metadata for the claim
-        RepositoryObjectEncryptionMetadata metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(new ByteArrayInputStream(persistedClaimBytes))
-        logger.verify("Parsed ${description} encryption metadata: ${metadata}")
-        assert metadata.keyId == expectedKeyId
-
-        // Ensure the persisted bytes are encrypted
-        Cipher verificationCipher = RepositoryEncryptorUtils.initCipher(mockCipherProvider, EncryptionMethod.AES_CTR, Cipher.DECRYPT_MODE, mockKeyProvider.getKey(metadata.keyId), metadata.ivBytes)
-        logger.verify("Created cipher: ${verificationCipher}")
-
-        // Skip the encryption metadata
-        byte[] cipherBytes = RepositoryEncryptorUtils.extractCipherBytes(persistedClaimBytes, metadata)
-        CipherInputStream verificationCipherStream = new CipherInputStream(new ByteArrayInputStream(cipherBytes), verificationCipher)
-
-        // Use #bytes rather than #read(byte[]) because read only gets 512 bytes at a time (the internal buffer size)
-        byte[] recoveredBytes = verificationCipherStream.bytes
-        logger.verify("Decrypted bytes (${recoveredBytes.length}): ${Hex.toHexString(recoveredBytes)}")
-        assert recoveredBytes == plainBytes
-        recoveredBytes
-    }
-
-    /**
      * Helper method to create <em>n</em> claims.
      *
      * @param n the number of claims to create
@@ -1004,39 +613,6 @@
     }
 
     /**
-     * Helper method to create a mock {@link KeyProvider} with stubbed functionality.
-     *
-     * @return the mock key provider
-     */
-    private KeyProvider createMockKeyProvider() {
-        KeyProvider mockKeyProvider = [
-                getKey            : { String keyId ->
-                    logger.mock("Requesting key ${keyId}")
-                    KEYS[keyId]
-                },
-                keyExists         : { String keyId ->
-                    logger.mock("Checking existence of ${keyId}")
-                    KEYS.containsKey(keyId)
-                },
-                getAvailableKeyIds: { ->
-                    logger.mock("Listing available keys")
-                    KEYS.keySet() as List
-                }
-        ] as KeyProvider
-        mockKeyProvider
-    }
-
-    /**
-     * Helper method to form the file path on disk from a claim.
-     *
-     * @param claim the claim to retrieve
-     * @return the file path
-     */
-    private String getPersistedFilePath(ContentClaim claim) {
-        [rootFile, claim.resourceClaim.section, claim.resourceClaim.id].join(File.separator)
-    }
-
-    /**
      * Returns a truncated byte[] in hexadecimal encoding as a String.
      *
      * @param bytes the byte[]
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
index f50a160..71b96b0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
@@ -24,8 +24,8 @@
 import org.apache.nifi.controller.repository.claim.ResourceClaimManager
 import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager
 import org.apache.nifi.repository.schema.NoOpFieldCache
-import org.apache.nifi.security.kms.CryptoUtils
-import org.apache.nifi.security.repository.config.FlowFileRepositoryEncryptionConfiguration
+import org.apache.nifi.security.kms.StaticKeyProvider
+import org.apache.nifi.util.NiFiProperties
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.junit.*
 import org.junit.rules.TestName
@@ -39,14 +39,10 @@
 
 import java.security.Security
 
-import static org.apache.nifi.security.kms.CryptoUtils.STATIC_KEY_PROVIDER_CLASS_NAME
-
 @RunWith(JUnit4.class)
 class EncryptedSequentialAccessWriteAheadLogTest extends GroovyTestCase {
     private static final Logger logger = LoggerFactory.getLogger(EncryptedSequentialAccessWriteAheadLogTest.class)
 
-    private static final String REPO_LOG_PACKAGE = "org.apache.nifi.security.repository"
-
     public static final String TEST_QUEUE_IDENTIFIER = "testQueueIdentifier"
 
     private ResourceClaimManager claimManager
@@ -58,26 +54,20 @@
     // TODO: Make integration test with real wrapped serde
     private SerDe<SerializedRepositoryRecord> wrappedSerDe
 
-    private static final String KPI = STATIC_KEY_PROVIDER_CLASS_NAME
-    private static final String KPL = ""
+    private static final String KPI = StaticKeyProvider.class.name
     private static final String KEY_ID = "K1"
-    private static final Map<String, String> KEYS = [K1: "0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210"]
-    // TODO: Change to WAL impl name
-    private static final String REPO_IMPL = CryptoUtils.EWAFFR_CLASS_NAME
+    private static final String KEY = "0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210"
 
-    private FlowFileRepositoryEncryptionConfiguration flowFileREC
+    private NiFiProperties properties
 
     private EncryptedSchemaRepositoryRecordSerde esrrs
 
-    private final EncryptedSequentialAccessWriteAheadLog<SerializedRepositoryRecord> encryptedWAL
-
     @Rule
     public TestName testName = new TestName()
 
     @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.debug("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
@@ -92,9 +82,13 @@
         dataOutputStream = new DataOutputStream(byteArrayOutputStream)
         wrappedSerDe = new SchemaRepositoryRecordSerde(claimManager, new NoOpFieldCache())
 
-        flowFileREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, KEYS, REPO_IMPL, null)
+        properties = NiFiProperties.createBasicNiFiProperties(null, [
+                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS): KPI,
+                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID)                           : KEY_ID,
+                (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY)                              : KEY
+        ])
 
-        esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, flowFileREC)
+        esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, properties)
     }
 
     @After
@@ -120,7 +114,7 @@
         ffrb.addAttributes([uuid: getMockUUID()] + attributes as Map<String, String>)
         record.setWorking(ffrb.build(), false)
 
-        return new LiveSerializedRepositoryRecord(record);
+        return new LiveSerializedRepositoryRecord(record)
     }
 
     private String getMockUUID() {
@@ -186,14 +180,10 @@
         assert recovered.every { it.type == RepositoryRecordType.CREATE }
     }
 
-    private EncryptedSchemaRepositoryRecordSerde buildEncryptedSerDe(FlowFileRepositoryEncryptionConfiguration ffrec = flowFileREC) {
+    private EncryptedSchemaRepositoryRecordSerde buildEncryptedSerDe() {
         final StandardRepositoryRecordSerdeFactory factory = new StandardRepositoryRecordSerdeFactory(claimManager)
         SchemaRepositoryRecordSerde wrappedSerDe = factory.createSerDe() as SchemaRepositoryRecordSerde
-        return new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, ffrec)
-    }
-
-    private SequentialAccessWriteAheadLog<SerializedRepositoryRecord> createWriteRepo() throws IOException {
-        return createWriteRepo(buildEncryptedSerDe())
+        return new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, properties)
     }
 
     private SequentialAccessWriteAheadLog<SerializedRepositoryRecord> createWriteRepo(final SerDe<SerializedRepositoryRecord> serde) throws IOException {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestEncryptedFileSystemSwapManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestEncryptedFileSystemSwapManager.java
index 6ef5115..a96dcfd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestEncryptedFileSystemSwapManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestEncryptedFileSystemSwapManager.java
@@ -26,7 +26,6 @@
 import org.apache.nifi.controller.repository.SwapManagerInitializationContext;
 import org.apache.nifi.controller.repository.claim.ResourceClaimManager;
 import org.apache.nifi.events.EventReporter;
-import org.apache.nifi.security.kms.EncryptionException;
 import org.apache.nifi.security.kms.StaticKeyProvider;
 import org.apache.nifi.util.NiFiProperties;
 import org.junit.Assert;
@@ -41,7 +40,6 @@
 import java.util.List;
 import java.util.Properties;
 import java.util.UUID;
-import java.util.logging.Logger;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
@@ -50,16 +48,13 @@
  * Test cases for {@link EncryptedFileSystemSwapManager}.
  */
 public class TestEncryptedFileSystemSwapManager {
-    private static final Logger logger = Logger.getLogger(TestEncryptedFileSystemSwapManager.class.getName());
-
     /**
      * Test a simple swap to disk / swap from disk operation.  Configured to use {@link StaticKeyProvider}.
      */
     @Test
-    public void testSwapOutSwapIn() throws GeneralSecurityException, EncryptionException, IOException {
+    public void testSwapOutSwapIn() throws GeneralSecurityException, IOException {
         // use temp folder on filesystem to temporarily hold swap content (clean up after test)
         final File folderRepository = Files.createTempDirectory(getClass().getSimpleName()).toFile();
-        logger.info(folderRepository.getPath());
         folderRepository.deleteOnExit();
         new File(folderRepository, "swap").deleteOnExit();
 
@@ -97,8 +92,7 @@
     /**
      * Borrowed from "nifi-framework-core/src/test/java/org/apache/nifi/controller/TestFileSystemSwapManager.java".
      */
-    private FlowFileSwapManager createSwapManager(NiFiProperties nifiProperties)
-            throws IOException, GeneralSecurityException, EncryptionException {
+    private FlowFileSwapManager createSwapManager(final NiFiProperties nifiProperties) throws GeneralSecurityException {
         final FlowFileRepository flowFileRepo = Mockito.mock(FlowFileRepository.class);
         when(flowFileRepo.isValidSwapLocationSuffix(any())).thenReturn(true);
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
index 71d9393..cfbb442 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
@@ -86,10 +86,6 @@
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
 nifi.provenance.repository.debug.frequency=1_000_000
-nifi.provenance.repository.encryption.key.provider.implementation=
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=
-nifi.provenance.repository.encryption.key=
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=./target/int-tests/provenance_repository
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/default-nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/default-nifi.properties
index b5baccb..2959474 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/default-nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/default-nifi.properties
@@ -86,10 +86,6 @@
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
 nifi.provenance.repository.debug.frequency=1_000_000
-nifi.provenance.repository.encryption.key.provider.implementation=
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=
-nifi.provenance.repository.encryption.key=
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=./target/int-tests/provenance_repository
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 ace5016..cf565b6 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
@@ -56,7 +56,8 @@
             PROVENANCE_REPO_ENCRYPTION_KEY,
             PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD,
             FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD,
-            CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD
+            CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD,
+            REPOSITORY_ENCRYPTION_KEY_PROVIDER_KEYSTORE_PASSWORD
     ));
 
     public ProtectedNiFiProperties() {
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
index adb309c..f6faea5 100644
--- 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
@@ -44,7 +44,8 @@
             "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.content.repository.encryption.key.provider.password",
+            "nifi.repository.encryption.key.provider.keystore.password"
     ]
 
     final def COMMON_ADDITIONAL_SENSITIVE_PROPERTIES = [
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 329577e..e3d4ef6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -58,10 +58,6 @@
         <nifi.flowfile.repository.directory>./flowfile_repository</nifi.flowfile.repository.directory>
         <nifi.flowfile.repository.checkpoint.interval>20 secs</nifi.flowfile.repository.checkpoint.interval>
         <nifi.flowfile.repository.always.sync>false</nifi.flowfile.repository.always.sync>
-        <nifi.flowfile.repository.encryption.key.provider.implementation />
-        <nifi.flowfile.repository.encryption.key.provider.location />
-        <nifi.flowfile.repository.encryption.key.id />
-        <nifi.flowfile.repository.encryption.key />
         <nifi.flowfile.repository.retain.orphaned.flowfiles>true</nifi.flowfile.repository.retain.orphaned.flowfiles>
         <nifi.swap.manager.implementation>org.apache.nifi.controller.FileSystemSwapManager</nifi.swap.manager.implementation>
         <nifi.queue.swap.threshold>20000</nifi.queue.swap.threshold>
@@ -73,10 +69,6 @@
         <nifi.content.repository.archive.max.usage.percentage>50%</nifi.content.repository.archive.max.usage.percentage>
         <nifi.content.repository.archive.enabled>true</nifi.content.repository.archive.enabled>
         <nifi.content.repository.always.sync>false</nifi.content.repository.always.sync>
-        <nifi.content.repository.encryption.key.provider.implementation />
-        <nifi.content.repository.encryption.key.provider.location />
-        <nifi.content.repository.encryption.key.id />
-        <nifi.content.repository.encryption.key />
         <nifi.content.viewer.url>../nifi-content-viewer/</nifi.content.viewer.url>
 
         <nifi.restore.directory />
@@ -98,10 +90,6 @@
 
         <!-- persistent provenance repository properties -->
         <nifi.provenance.repository.implementation>org.apache.nifi.provenance.WriteAheadProvenanceRepository</nifi.provenance.repository.implementation>
-        <nifi.provenance.repository.encryption.key.provider.implementation />
-        <nifi.provenance.repository.encryption.key.provider.location />
-        <nifi.provenance.repository.encryption.key.id />
-        <nifi.provenance.repository.encryption.key />
         <nifi.provenance.repository.directory.default>./provenance_repository</nifi.provenance.repository.directory.default>
         <nifi.provenance.repository.max.storage.time>30 days</nifi.provenance.repository.max.storage.time>
         <nifi.provenance.repository.max.storage.size>10 GB</nifi.provenance.repository.max.storage.size>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index ed27041..fdbe39d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -52,22 +52,23 @@
 # Properties file that provides the ZooKeeper properties to use if <nifi.state.management.embedded.zookeeper.start> is set to true
 nifi.state.management.embedded.zookeeper.properties=${nifi.state.management.embedded.zookeeper.properties}
 
-
 # H2 Settings
 nifi.database.directory=${nifi.database.directory}
 nifi.h2.url.append=${nifi.h2.url.append}
 
+# Repository Encryption properties override individual repository implementation properties
+nifi.repository.encryption.protocol.version=
+nifi.repository.encryption.key.id=
+nifi.repository.encryption.key.provider=
+nifi.repository.encryption.key.provider.keystore.location=
+nifi.repository.encryption.key.provider.keystore.password=
+
 # FlowFile Repository
 nifi.flowfile.repository.implementation=${nifi.flowfile.repository.implementation}
 nifi.flowfile.repository.wal.implementation=${nifi.flowfile.repository.wal.implementation}
 nifi.flowfile.repository.directory=${nifi.flowfile.repository.directory}
 nifi.flowfile.repository.checkpoint.interval=${nifi.flowfile.repository.checkpoint.interval}
 nifi.flowfile.repository.always.sync=${nifi.flowfile.repository.always.sync}
-nifi.flowfile.repository.encryption.key.provider.implementation=${nifi.flowfile.repository.encryption.key.provider.implementation}
-nifi.flowfile.repository.encryption.key.provider.location=${nifi.flowfile.repository.encryption.key.provider.location}
-nifi.flowfile.repository.encryption.key.provider.password=
-nifi.flowfile.repository.encryption.key.id=${nifi.flowfile.repository.encryption.key.id}
-nifi.flowfile.repository.encryption.key=${nifi.flowfile.repository.encryption.key}
 nifi.flowfile.repository.retain.orphaned.flowfiles=${nifi.flowfile.repository.retain.orphaned.flowfiles}
 
 nifi.swap.manager.implementation=${nifi.swap.manager.implementation}
@@ -82,19 +83,9 @@
 nifi.content.repository.archive.enabled=${nifi.content.repository.archive.enabled}
 nifi.content.repository.always.sync=${nifi.content.repository.always.sync}
 nifi.content.viewer.url=${nifi.content.viewer.url}
-nifi.content.repository.encryption.key.provider.implementation=${nifi.content.repository.encryption.key.provider.implementation}
-nifi.content.repository.encryption.key.provider.location=${nifi.content.repository.encryption.key.provider.location}
-nifi.content.repository.encryption.key.provider.password=
-nifi.content.repository.encryption.key.id=${nifi.content.repository.encryption.key.id}
-nifi.content.repository.encryption.key=${nifi.content.repository.encryption.key}
 
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=${nifi.provenance.repository.implementation}
-nifi.provenance.repository.encryption.key.provider.implementation=${nifi.provenance.repository.encryption.key.provider.implementation}
-nifi.provenance.repository.encryption.key.provider.location=${nifi.provenance.repository.encryption.key.provider.location}
-nifi.provenance.repository.encryption.key.provider.password=
-nifi.provenance.repository.encryption.key.id=${nifi.provenance.repository.encryption.key.id}
-nifi.provenance.repository.encryption.key=${nifi.provenance.repository.encryption.key}
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=${nifi.provenance.repository.directory.default}
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
index fb6a2cc..b20e312 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
@@ -62,6 +62,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-repository-encryption</artifactId>
+            <version>1.15.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-properties</artifactId>
             <version>1.15.0-SNAPSHOT</version>
         </dependency>
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordReader.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordReader.java
index e83ce20..daf12cf 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordReader.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordReader.java
@@ -26,26 +26,20 @@
 
 import org.apache.nifi.provenance.schema.LookupTableEventRecord;
 import org.apache.nifi.provenance.toc.TocReader;
+import org.apache.nifi.repository.encryption.RepositoryEncryptor;
 import org.apache.nifi.repository.schema.Record;
 import org.apache.nifi.stream.io.LimitingInputStream;
 import org.apache.nifi.stream.io.StreamUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class EncryptedSchemaRecordReader extends EventIdFirstSchemaRecordReader {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedSchemaRecordReader.class);
-
-    private ProvenanceEventEncryptor provenanceEventEncryptor;
-
-    public static final int SERIALIZATION_VERSION = 1;
+    private RepositoryEncryptor<byte[], byte[]> repositoryEncryptor;
 
     public static final String SERIALIZATION_NAME = "EncryptedSchemaRecordWriter";
 
-
     public EncryptedSchemaRecordReader(final InputStream inputStream, final String filename, final TocReader tocReader, final int maxAttributeChars,
-                                       ProvenanceEventEncryptor provenanceEventEncryptor) throws IOException {
+                                       final RepositoryEncryptor<byte[], byte[]> repositoryEncryptor) throws IOException {
         super(inputStream, filename, tocReader, maxAttributeChars);
-        this.provenanceEventEncryptor = provenanceEventEncryptor;
+        this.repositoryEncryptor = repositoryEncryptor;
     }
 
     @Override
@@ -60,32 +54,27 @@
     }
 
     private StandardProvenanceEventRecord readRecord(final DataInputStream inputStream, final long eventId, final long startOffset, final int recordLength) throws IOException {
-        try {
-            final InputStream limitedIn = new LimitingInputStream(inputStream, recordLength);
+        final InputStream limitedIn = new LimitingInputStream(inputStream, recordLength);
 
-            byte[] encryptedSerializedBytes = new byte[recordLength];
-            DataInputStream encryptedInputStream = new DataInputStream(limitedIn);
-            encryptedInputStream.readFully(encryptedSerializedBytes);
+        final byte[] encryptedSerializedBytes = new byte[recordLength];
+        final DataInputStream encryptedInputStream = new DataInputStream(limitedIn);
+        encryptedInputStream.readFully(encryptedSerializedBytes);
 
-            byte[] plainSerializedBytes = decrypt(encryptedSerializedBytes, Long.toString(eventId));
-            InputStream plainStream = new ByteArrayInputStream(plainSerializedBytes);
+        final byte[] plainSerializedBytes = repositoryEncryptor.decrypt(encryptedSerializedBytes, Long.toString(eventId));
+        final InputStream plainStream = new ByteArrayInputStream(plainSerializedBytes);
 
-            final Record eventRecord = getRecordReader().readRecord(plainStream);
-            if (eventRecord == null) {
-                return null;
-            }
-
-            final StandardProvenanceEventRecord deserializedEvent = LookupTableEventRecord.getEvent(eventRecord, getFilename(), startOffset, getMaxAttributeLength(),
-                    getFirstEventId(), getSystemTimeOffset(), getComponentIds(), getComponentTypes(), getQueueIds(), getEventTypes());
-            deserializedEvent.setEventId(eventId);
-            return deserializedEvent;
-        } catch (EncryptionException e) {
-            logger.error("Encountered an error reading the record: ", e);
-            throw new IOException(e);
+        final Record eventRecord = getRecordReader().readRecord(plainStream);
+        if (eventRecord == null) {
+            return null;
         }
+
+        final StandardProvenanceEventRecord deserializedEvent = LookupTableEventRecord.getEvent(eventRecord, getFilename(), startOffset, getMaxAttributeLength(),
+                getFirstEventId(), getSystemTimeOffset(), getComponentIds(), getComponentTypes(), getQueueIds(), getEventTypes());
+        deserializedEvent.setEventId(eventId);
+        return deserializedEvent;
     }
 
-    // TODO: Copied from EventIdFirstSchemaRecordReader to force local/overridden readRecord()
+    // Copied from EventIdFirstSchemaRecordReader to force local/overridden readRecord()
     @Override
     protected Optional<StandardProvenanceEventRecord> readToEvent(final long eventId, final DataInputStream dis, final int serializationVersion) throws IOException {
         verifySerializationVersion(serializationVersion);
@@ -107,15 +96,6 @@
         return Optional.empty();
     }
 
-    private byte[] decrypt(byte[] encryptedBytes, String eventId) throws IOException, EncryptionException {
-        try {
-            return provenanceEventEncryptor.decrypt(encryptedBytes, eventId);
-        } catch (Exception e) {
-            logger.error("Encountered an error: ", e);
-            throw new EncryptionException(e);
-        }
-    }
-
     @Override
     public String toString() {
         return getDescription();
@@ -133,9 +113,9 @@
      * Sets the encryptor to use (necessary because the
      * {@link org.apache.nifi.provenance.serialization.RecordReaders#newRecordReader(File, Collection, int)} method doesn't accept the encryptor.
      *
-     * @param provenanceEventEncryptor the encryptor
+     * @param repositoryEncryptor Repository Encryptor
      */
-    void setProvenanceEventEncryptor(ProvenanceEventEncryptor provenanceEventEncryptor) {
-        this.provenanceEventEncryptor = provenanceEventEncryptor;
+    void setRepositoryEncryptor(final RepositoryEncryptor<byte[], byte[]> repositoryEncryptor) {
+        this.repositoryEncryptor = repositoryEncryptor;
     }
 }
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordWriter.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordWriter.java
index bc88efe..b108410 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordWriter.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedSchemaRecordWriter.java
@@ -17,34 +17,25 @@
 package org.apache.nifi.provenance;
 
 import org.apache.nifi.provenance.toc.TocWriter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.nifi.repository.encryption.RepositoryEncryptor;
 
 import java.io.File;
 import java.io.IOException;
-import java.security.KeyManagementException;
 import java.util.concurrent.atomic.AtomicLong;
 
 public class EncryptedSchemaRecordWriter extends EventIdFirstSchemaRecordWriter {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedSchemaRecordWriter.class);
     public static final String SERIALIZATION_NAME = "EncryptedSchemaRecordWriter";
     public static final int SERIALIZATION_VERSION = 1;
 
-    private ProvenanceEventEncryptor provenanceEventEncryptor;
-    private String keyId;
+    private final RepositoryEncryptor<byte[], byte[]> repositoryEncryptor;
+    private final String keyId;
 
     public EncryptedSchemaRecordWriter(final File file, final AtomicLong idGenerator, final TocWriter writer, final boolean compressed,
                                        final int uncompressedBlockSize, final IdentifierLookup idLookup,
-                                       ProvenanceEventEncryptor provenanceEventEncryptor, int debugFrequency) throws IOException, EncryptionException {
+                                       final RepositoryEncryptor<byte[], byte[]> repositoryEncryptor, final String keyId) throws IOException {
         super(file, idGenerator, writer, compressed, uncompressedBlockSize, idLookup);
-        this.provenanceEventEncryptor = provenanceEventEncryptor;
-
-        try {
-            this.keyId = getNextAvailableKeyId();
-        } catch (KeyManagementException e) {
-            logger.error("Encountered an error initializing the encrypted schema record writer because the provided encryptor has no valid keys available: ", e);
-            throw new EncryptionException("No valid keys in the provenance event encryptor", e);
-        }
+        this.repositoryEncryptor = repositoryEncryptor;
+        this.keyId = keyId;
     }
 
     @Override
@@ -52,27 +43,7 @@
         final byte[] serialized = super.serializeEvent(event);
         final String eventId = event.getBestEventIdentifier();
 
-        try {
-            final byte[] cipherBytes = encrypt(serialized, eventId);
-            return cipherBytes;
-        } catch (EncryptionException e) {
-            logger.error("Encountered an error: ", e);
-            throw new IOException("Error encrypting the provenance record", e);
-        }
-    }
-
-    private byte[] encrypt(byte[] serialized, String eventId) throws EncryptionException {
-        final String keyId = getKeyId();
-        try {
-            return provenanceEventEncryptor.encrypt(serialized, eventId, keyId);
-        } catch (Exception e) {
-            logger.error("Encountered an error: ", e);
-            throw new EncryptionException(e);
-        }
-    }
-
-    private String getNextAvailableKeyId() throws KeyManagementException {
-        return provenanceEventEncryptor.getNextKeyId();
+        return repositoryEncryptor.encrypt(serialized, eventId, keyId);
     }
 
     @Override
@@ -85,12 +56,8 @@
         return SERIALIZATION_NAME;
     }
 
-    public String getKeyId() {
-        return keyId;
-    }
-
     @Override
     public String toString() {
-        return "EncryptedSchemaRecordWriter[keyId=" + keyId + ", encryptor=" + provenanceEventEncryptor + "]";
+        return "EncryptedSchemaRecordWriter[keyId=" + keyId + ", encryptor=" + repositoryEncryptor + "]";
     }
 }
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
index 198f362..398eaa2 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
@@ -25,16 +25,16 @@
 import org.apache.nifi.provenance.toc.StandardTocWriter;
 import org.apache.nifi.provenance.toc.TocUtil;
 import org.apache.nifi.provenance.toc.TocWriter;
+import org.apache.nifi.repository.encryption.AesGcmByteArrayRepositoryEncryptor;
+import org.apache.nifi.repository.encryption.RepositoryEncryptor;
+import org.apache.nifi.repository.encryption.configuration.EncryptedRepositoryType;
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader;
+import org.apache.nifi.repository.encryption.configuration.kms.RepositoryKeyProviderFactory;
+import org.apache.nifi.repository.encryption.configuration.kms.StandardRepositoryKeyProviderFactory;
 import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.config.ProvenanceRepositoryEncryptionConfiguration;
-import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
 import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.security.KeyManagementException;
 
 /**
  * This class is an implementation of the {@link WriteAheadProvenanceRepository} provenance repository which provides transparent
@@ -46,7 +46,9 @@
  * Repository Properties</a>.
  */
 public class EncryptedWriteAheadProvenanceRepository extends WriteAheadProvenanceRepository {
-    private static final Logger logger = LoggerFactory.getLogger(EncryptedWriteAheadProvenanceRepository.class);
+    private NiFiProperties niFiProperties;
+
+    private RepositoryEncryptor<byte[], byte[]> repositoryEncryptor;
 
     /**
      * This constructor exists solely for the use of the Java Service Loader mechanism and should not be used.
@@ -56,14 +58,17 @@
         super();
     }
 
-    // Created via reflection from FlowController
-    @SuppressWarnings("unused")
-    public EncryptedWriteAheadProvenanceRepository(final NiFiProperties nifiProperties) {
-        super(RepositoryConfiguration.create(nifiProperties));
-    }
-
-    public EncryptedWriteAheadProvenanceRepository(final RepositoryConfiguration config) {
-        super(config);
+    /**
+     * Encrypted Write Ahead Provenance Repository initialized using NiFi Properties
+     *
+     * @param niFiProperties NiFi Properties
+     */
+    public EncryptedWriteAheadProvenanceRepository(final NiFiProperties niFiProperties) {
+        super(RepositoryConfiguration.create(niFiProperties));
+        this.niFiProperties = niFiProperties;
+        final RepositoryKeyProviderFactory repositoryKeyProviderFactory = new StandardRepositoryKeyProviderFactory();
+        final KeyProvider keyProvider = repositoryKeyProviderFactory.getKeyProvider(EncryptedRepositoryType.PROVENANCE, niFiProperties);
+        this.repositoryEncryptor = new AesGcmByteArrayRepositoryEncryptor(keyProvider, EncryptionMetadataHeader.PROVENANCE);
     }
 
     /**
@@ -80,31 +85,11 @@
     @Override
     public synchronized void initialize(final EventReporter eventReporter, final Authorizer authorizer, final ProvenanceAuthorizableFactory resourceFactory,
                                         final IdentifierLookup idLookup) throws IOException {
-        // Initialize the encryption-specific fields
-        ProvenanceEventEncryptor provenanceEventEncryptor;
-        if (getConfig().supportsEncryption()) {
-            try {
-                final KeyProvider keyProvider = buildKeyProvider();
-                provenanceEventEncryptor = new AESProvenanceEventEncryptor();
-                provenanceEventEncryptor.initialize(keyProvider);
-            } catch (KeyManagementException e) {
-                String msg = "Encountered an error building the key provider";
-                logger.error(msg, e);
-                throw new IOException(msg, e);
-            }
-        } else {
-            throw new IOException("The provided configuration does not support a encrypted repository");
-        }
-
         // Build a factory using lambda which injects the encryptor
         final RecordWriterFactory recordWriterFactory = (file, idGenerator, compressed, createToc) -> {
-            try {
-                final TocWriter tocWriter = createToc ? new StandardTocWriter(TocUtil.getTocFile(file), false, false) : null;
-                return new EncryptedSchemaRecordWriter(file, idGenerator, tocWriter, compressed, BLOCK_SIZE, idLookup, provenanceEventEncryptor, getConfig().getDebugFrequency());
-            } catch (EncryptionException e) {
-                logger.error("Encountered an error building the schema record writer factory: ", e);
-                throw new IOException(e);
-            }
+            final TocWriter tocWriter = createToc ? new StandardTocWriter(TocUtil.getTocFile(file), false, false) : null;
+            final String keyId = niFiProperties.getProvenanceRepoEncryptionKeyId();
+            return new EncryptedSchemaRecordWriter(file, idGenerator, tocWriter, compressed, BLOCK_SIZE, idLookup, repositoryEncryptor, keyId);
         };
 
         // Build a factory using lambda which injects the encryptor
@@ -113,7 +98,7 @@
             fileManager.obtainReadLock(file);
             try {
                 EncryptedSchemaRecordReader tempReader = (EncryptedSchemaRecordReader) RecordReaders.newRecordReader(file, logs, maxChars);
-                tempReader.setProvenanceEventEncryptor(provenanceEventEncryptor);
+                tempReader.setRepositoryEncryptor(repositoryEncryptor);
                 return tempReader;
             } finally {
                 fileManager.releaseReadLock(file);
@@ -123,17 +108,4 @@
         // Delegate the init to the parent impl
         super.init(recordWriterFactory, recordReaderFactory, eventReporter, authorizer, resourceFactory);
     }
-
-    private KeyProvider buildKeyProvider() throws IOException {
-        final RepositoryConfiguration config = getConfig();
-        final RepositoryEncryptionConfiguration configuration = new ProvenanceRepositoryEncryptionConfiguration(
-                config.getKeyProviderImplementation(),
-                config.getKeyProviderLocation(),
-                config.getKeyId(),
-                config.getEncryptionKeys(),
-                getClass().getName(),
-                config.getKeyProviderPassword()
-        );
-        return RepositoryEncryptorUtils.validateAndBuildRepositoryKeyProvider(configuration);
-    }
 }
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
index 77c080a..54d879b 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
@@ -28,7 +28,6 @@
 import java.util.concurrent.TimeUnit;
 import org.apache.nifi.processor.DataUnit;
 import org.apache.nifi.provenance.search.SearchableField;
-import org.apache.nifi.security.kms.CryptoUtils;
 import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
@@ -54,13 +53,6 @@
     private int debugFrequency = 1_000_000;
     private long maintenanceFrequencyMillis = TimeUnit.MINUTES.toMillis(1L);
 
-    // TODO: Delegaate to RepositoryEncryptionConfiguration in NIFI-6617
-    private Map<String, String> encryptionKeys;
-    private String keyId;
-    private String keyProviderImplementation;
-    private String keyProviderLocation;
-    private String keyProviderPassword;
-
     private List<SearchableField> searchableFields = new ArrayList<>();
     private List<SearchableField> searchableAttributes = new ArrayList<>();
     private boolean compress = true;
@@ -370,54 +362,6 @@
         return Optional.ofNullable(warmCacheFrequencyMinutes);
     }
 
-    public boolean supportsEncryption() {
-        boolean keyProviderIsConfigured = CryptoUtils.isValidKeyProvider(keyProviderImplementation, keyProviderLocation, keyId, encryptionKeys);
-
-        return keyProviderIsConfigured;
-    }
-
-    // TODO: Add verbose error output for encryption support failure if requested
-
-    public Map<String, String> getEncryptionKeys() {
-        return encryptionKeys;
-    }
-
-    public void setEncryptionKeys(Map<String, String> encryptionKeys) {
-        this.encryptionKeys = encryptionKeys;
-    }
-
-    public String getKeyId() {
-        return keyId;
-    }
-
-    public void setKeyId(String keyId) {
-        this.keyId = keyId;
-    }
-
-    public String getKeyProviderImplementation() {
-        return keyProviderImplementation;
-    }
-
-    public void setKeyProviderImplementation(String keyProviderImplementation) {
-        this.keyProviderImplementation = keyProviderImplementation;
-    }
-
-    public String getKeyProviderLocation() {
-        return keyProviderLocation;
-    }
-
-    public void setKeyProviderLocation(String keyProviderLocation) {
-        this.keyProviderLocation = keyProviderLocation;
-    }
-
-    public String getKeyProviderPassword() {
-        return keyProviderPassword;
-    }
-
-    public void setKeyProviderPassword(final String keyProviderPassword) {
-        this.keyProviderPassword = keyProviderPassword;
-    }
-
     public int getDebugFrequency() {
         return debugFrequency;
     }
@@ -518,18 +462,6 @@
         config.setAlwaysSync(alwaysSync);
 
         config.setDebugFrequency(nifiProperties.getIntegerProperty(NiFiProperties.PROVENANCE_REPO_DEBUG_FREQUENCY, config.getDebugFrequency()));
-
-        // TODO: Check for multiple key loading (NIFI-6617)
-        // Encryption values may not be present but are only required for EncryptedWriteAheadProvenanceRepository
-        final String implementationClassName = nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS);
-        if (EncryptedWriteAheadProvenanceRepository.class.getName().equals(implementationClassName)) {
-            config.setEncryptionKeys(nifiProperties.getProvenanceRepoEncryptionKeys());
-            config.setKeyId(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_ID));
-            config.setKeyProviderImplementation(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS));
-            config.setKeyProviderLocation(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION));
-            config.setKeyProviderPassword(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD));
-        }
-
         return config;
     }
 }
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/serialization/RecordReaders.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/serialization/RecordReaders.java
index ad0c5b5..b008709 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/serialization/RecordReaders.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/serialization/RecordReaders.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.provenance.serialization;
 
-import org.apache.nifi.properties.NiFiPropertiesLoader;
 import org.apache.nifi.provenance.ByteArraySchemaRecordReader;
 import org.apache.nifi.provenance.ByteArraySchemaRecordWriter;
 import org.apache.nifi.provenance.EncryptedSchemaRecordReader;
@@ -27,11 +26,6 @@
 import org.apache.nifi.provenance.toc.StandardTocReader;
 import org.apache.nifi.provenance.toc.TocReader;
 import org.apache.nifi.provenance.toc.TocUtil;
-import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
-import org.apache.nifi.security.repository.RepositoryType;
-import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.BufferedInputStream;
 import java.io.DataInputStream;
@@ -46,12 +40,6 @@
 import java.util.zip.GZIPInputStream;
 
 public class RecordReaders {
-
-    private static Logger logger = LoggerFactory.getLogger(RecordReaders.class);
-
-    private static boolean isEncryptionAvailable = false;
-    private static boolean encryptionPropertiesRead = false;
-
     /**
      * Creates a new Record Reader that is capable of reading Provenance Event Journals
      *
@@ -166,10 +154,6 @@
                         throw new FileNotFoundException("Cannot create TOC Reader because the file " + tocFile + " does not exist");
                     }
 
-                    if (!isEncryptionAvailable()) {
-                        throw new IOException("Cannot read encrypted repository because this reader is not configured for encryption");
-                    }
-
                     final TocReader tocReader = new StandardTocReader(tocFile);
                     // Return a reader with no eventEncryptor because this method contract cannot change, then inject the encryptor from the writer in the calling method
                     return new EncryptedSchemaRecordReader(bufferedInStream, filename, tocReader, maxAttributeChars, null);
@@ -190,21 +174,4 @@
             throw ioe;
         }
     }
-
-    private static boolean isEncryptionAvailable() {
-        if (encryptionPropertiesRead) {
-            return isEncryptionAvailable;
-        } else {
-            try {
-                NiFiProperties niFiProperties = NiFiPropertiesLoader.loadDefaultWithKeyFromBootstrap();
-                isEncryptionAvailable = RepositoryEncryptorUtils.isRepositoryEncryptionConfigured(niFiProperties, RepositoryType.PROVENANCE);
-                encryptionPropertiesRead = true;
-            } catch (IOException e) {
-                logger.error("Encountered an error checking the provenance repository encryption configuration: ", e);
-                isEncryptionAvailable = false;
-            }
-            return isEncryptionAvailable;
-        }
-    }
-
 }
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedSchemaRecordReaderWriterTest.groovy b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedSchemaRecordReaderWriterTest.groovy
index 79d4912..1e0560e 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedSchemaRecordReaderWriterTest.groovy
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedSchemaRecordReaderWriterTest.groovy
@@ -24,6 +24,9 @@
 import org.apache.nifi.provenance.toc.TocReader
 import org.apache.nifi.provenance.toc.TocUtil
 import org.apache.nifi.provenance.toc.TocWriter
+import org.apache.nifi.repository.encryption.AesGcmByteArrayRepositoryEncryptor
+import org.apache.nifi.repository.encryption.RepositoryEncryptor
+import org.apache.nifi.repository.encryption.configuration.EncryptionMetadataHeader
 import org.apache.nifi.security.kms.KeyProvider
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.bouncycastle.util.encoders.Hex
@@ -33,7 +36,6 @@
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
-import javax.crypto.Cipher
 import javax.crypto.spec.SecretKeySpec
 import java.security.KeyManagementException
 import java.security.Security
@@ -61,7 +63,7 @@
     private File tocFile
 
     private static KeyProvider mockKeyProvider
-    private static ProvenanceEventEncryptor provenanceEventEncryptor = new AESProvenanceEventEncryptor()
+    private static RepositoryEncryptor<byte[], byte[]> repositoryEncryptor
 
     @BeforeAll
     static void setUpOnce() throws Exception {
@@ -81,7 +83,7 @@
                 keyExists         : { String keyId ->
                     keyId == KEY_ID
                 }] as KeyProvider
-        provenanceEventEncryptor.initialize(mockKeyProvider)
+        repositoryEncryptor = new AesGcmByteArrayRepositoryEncryptor(mockKeyProvider, EncryptionMetadataHeader.PROVENANCE)
     }
 
     @BeforeEach
@@ -116,22 +118,22 @@
     protected RecordWriter createWriter(
             final File file,
             final TocWriter tocWriter, final boolean compressed, final int uncompressedBlockSize) throws IOException {
-        createWriter(file, tocWriter, compressed, uncompressedBlockSize, provenanceEventEncryptor)
+        createWriter(file, tocWriter, compressed, uncompressedBlockSize, repositoryEncryptor)
     }
 
     protected static RecordWriter createWriter(
             final File file,
             final TocWriter tocWriter,
             final boolean compressed,
-            final int uncompressedBlockSize, ProvenanceEventEncryptor encryptor) throws IOException {
-        return new EncryptedSchemaRecordWriter(file, idGenerator, tocWriter, compressed, uncompressedBlockSize, IdentifierLookup.EMPTY, encryptor, 1)
+            final int uncompressedBlockSize, RepositoryEncryptor<byte[], byte[]> encryptor) throws IOException {
+        return new EncryptedSchemaRecordWriter(file, idGenerator, tocWriter, compressed, uncompressedBlockSize, IdentifierLookup.EMPTY, encryptor, KEY_ID)
     }
 
     @Override
     protected RecordReader createReader(
             final InputStream inputStream,
             final String journalFilename, final TocReader tocReader, final int maxAttributeSize) throws IOException {
-        return new EncryptedSchemaRecordReader(inputStream, journalFilename, tocReader, maxAttributeSize, provenanceEventEncryptor)
+        return new EncryptedSchemaRecordReader(inputStream, journalFilename, tocReader, maxAttributeSize, repositoryEncryptor)
     }
 
     /**
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepositoryTest.groovy b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepositoryTest.groovy
index d0ee95a..338aa7e 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepositoryTest.groovy
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/groovy/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepositoryTest.groovy
@@ -21,6 +21,7 @@
 import org.apache.nifi.provenance.serialization.RecordReaders
 import org.apache.nifi.reporting.Severity
 import org.apache.nifi.security.kms.StaticKeyProvider
+import org.apache.nifi.util.NiFiProperties
 import org.apache.nifi.util.file.FileUtils
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import org.junit.jupiter.api.AfterEach
@@ -65,10 +66,6 @@
     @AfterEach
     void tearDown() throws Exception {
         closeRepo(repo, config)
-
-        // Reset the boolean determiner
-        RecordReaders.encryptionPropertiesRead = false
-        RecordReaders.isEncryptionAvailable = false
     }
 
     private static RepositoryConfiguration createConfiguration() {
@@ -80,14 +77,6 @@
         return config
     }
 
-    private static RepositoryConfiguration createEncryptedConfiguration() {
-        RepositoryConfiguration config = createConfiguration()
-        config.setEncryptionKeys([(KEY_ID): KEY_HEX])
-        config.setKeyId(KEY_ID)
-        config.setKeyProviderImplementation(StaticKeyProvider.class.name)
-        config
-    }
-
     private EventReporter createMockEventReporter() {
         [reportEvent: { Severity s, String c, String m ->
             ReportedEvent event = new ReportedEvent(s, c, m)
@@ -213,16 +202,13 @@
         // Arrange
         final int RECORD_COUNT = 10
 
-        // Override the boolean determiner
-        RecordReaders.encryptionPropertiesRead = true
-        RecordReaders.isEncryptionAvailable = true
+        NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, [
+                (NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS): StaticKeyProvider.class.name,
+                (NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY): KEY_HEX,
+                (NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_ID): KEY_ID
+        ])
 
-        config = createEncryptedConfiguration()
-        // Needed until NIFI-3605 is implemented
-//        config.setMaxEventFileCapacity(1L)
-        config.setMaxEventFileCount(1)
-        config.setMaxEventFileLife(1, TimeUnit.SECONDS)
-        repo = new EncryptedWriteAheadProvenanceRepository(config)
+        repo = new EncryptedWriteAheadProvenanceRepository(properties)
         repo.initialize(eventReporter, null, null, IdentifierLookup.EMPTY)
 
         Map attributes = ["abc": "This is a plaintext attribute.",
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
index b4376ed..238fba3 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/security/util/crypto/PasswordBasedEncryptorGroovyTest.groovy
@@ -19,7 +19,6 @@
 import org.apache.commons.codec.binary.Hex
 import org.apache.nifi.processor.io.StreamCallback
 import org.apache.nifi.processors.standard.TestEncryptContentGroovy
-import org.apache.nifi.security.kms.CryptoUtils
 import org.apache.nifi.security.util.EncryptionMethod
 import org.apache.nifi.security.util.KeyDerivationFunction
 import org.apache.nifi.stream.io.ByteCountingInputStream
@@ -259,7 +258,7 @@
                 byte[] cipherBytes = legacyCipher.doFinal(PLAINTEXT.bytes)
                 logger.info("Cipher bytes: ${Hex.encodeHexString(cipherBytes)}")
 
-                byte[] completeCipherStreamBytes = CryptoUtils.concatByteArrays(legacySalt, cipherBytes)
+                byte[] completeCipherStreamBytes = org.bouncycastle.util.Arrays.concatenate(legacySalt, cipherBytes)
                 logger.info("Complete cipher stream: ${Hex.encodeHexString(completeCipherStreamBytes)}")
 
                 InputStream cipherStream = new ByteArrayInputStream(completeCipherStreamBytes)
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/nifi.properties b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/nifi.properties
index 67796ec..9faf779 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/nifi.properties
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/nifi.properties
@@ -86,10 +86,6 @@
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
 nifi.provenance.repository.debug.frequency=1_000_000
-nifi.provenance.repository.encryption.key.provider.implementation=
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=
-nifi.provenance.repository.encryption.key=
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=./provenance_repository
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/nifi.properties b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/nifi.properties
index a5dd0a4..ac1fd0e 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/nifi.properties
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/nifi.properties
@@ -86,10 +86,6 @@
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
 nifi.provenance.repository.debug.frequency=1_000_000
-nifi.provenance.repository.encryption.key.provider.implementation=
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=
-nifi.provenance.repository.encryption.key=
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=./provenance_repository
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/nifi.properties b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/nifi.properties
index 35404b3..a9ddd21 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/nifi.properties
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/nifi.properties
@@ -86,10 +86,6 @@
 # Provenance Repository Properties
 nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
 nifi.provenance.repository.debug.frequency=1_000_000
-nifi.provenance.repository.encryption.key.provider.implementation=
-nifi.provenance.repository.encryption.key.provider.location=
-nifi.provenance.repository.encryption.key.id=
-nifi.provenance.repository.encryption.key=
 
 # Persistent Provenance Repository Properties
 nifi.provenance.repository.directory.default=./provenance_repository