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

martinzink pushed a commit to branch minifi_rust_pgp
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 97b24c8cf4c6f90813a085856abe8dc36182d754
Author: Martin Zink <[email protected]>
AuthorDate: Tue Jun 30 17:22:35 2026 +0200

    MINIFICPP-2749 Add EncryptContentPGP and DecryptContentPGP
---
 .../extensions/minifi_pgp/.cargo/config.toml       |   5 +
 minifi_rust/extensions/minifi_pgp/.gitignore       |   7 +
 minifi_rust/extensions/minifi_pgp/Cargo.toml       |  15 ++
 .../minifi_pgp/features/encrypt_decrypt.feature    |  54 +++++
 .../extensions/minifi_pgp/features/environment.py  |  73 ++++++
 .../extensions/minifi_pgp/features/steps/steps.py  | 100 ++++++++
 minifi_rust/extensions/minifi_pgp/minifi_pgp.md    | 120 ++++++++++
 .../src/controller_services/key_lookup.rs          |  56 +++++
 .../minifi_pgp/src/controller_services/mod.rs      |   3 +
 .../src/controller_services/private_key_service.rs |  94 ++++++++
 .../controller_service_definition.rs               |  10 +
 .../private_key_service/properties.rs              |  32 +++
 .../private_key_service/tests.rs                   | 214 +++++++++++++++++
 .../src/controller_services/public_key_service.rs  |  72 ++++++
 .../controller_service_definition.rs               |  10 +
 .../public_key_service/properties.rs               |  22 ++
 .../public_key_service/tests.rs                    | 245 ++++++++++++++++++++
 minifi_rust/extensions/minifi_pgp/src/lib.rs       |  23 ++
 .../minifi_pgp/src/processors/decrypt_content.rs   | 150 ++++++++++++
 .../decrypt_content/output_attributes.rs           |  13 ++
 .../decrypt_content/processor_definition.rs        |  22 ++
 .../src/processors/decrypt_content/properties.rs   |  36 +++
 .../processors/decrypt_content/relationships.rs    |  11 +
 .../src/processors/decrypt_content/tests.rs        | 253 +++++++++++++++++++++
 .../minifi_pgp/src/processors/encrypt_content.rs   | 152 +++++++++++++
 .../encrypt_content/output_attributes.rs           |   7 +
 .../encrypt_content/processor_definition.rs        |  20 ++
 .../src/processors/encrypt_content/properties.rs   |  46 ++++
 .../processors/encrypt_content/relationships.rs    |  11 +
 .../src/processors/encrypt_content/tests.rs        | 137 +++++++++++
 .../extensions/minifi_pgp/src/processors/mod.rs    |   2 +
 .../extensions/minifi_pgp/src/test_utils/mod.rs    |  15 ++
 minifi_rust/extensions/minifi_pgp/src/utils.rs     |  11 +
 .../extensions/minifi_pgp/test_keys/README.txt     |   8 +
 .../extensions/minifi_pgp/test_keys/alice.asc      |  50 ++++
 .../extensions/minifi_pgp/test_keys/alice.gpg      | Bin 0 -> 2175 bytes
 .../minifi_pgp/test_keys/alice_private.asc         |  92 ++++++++
 .../minifi_pgp/test_keys/alice_private.gpg         | Bin 0 -> 4220 bytes
 .../minifi_pgp/test_keys/bob_private.asc           |  71 ++++++
 .../minifi_pgp/test_keys/bob_private.gpg           | Bin 0 -> 3207 bytes
 .../extensions/minifi_pgp/test_keys/garbage.gpg    | Bin 0 -> 1024 bytes
 .../extensions/minifi_pgp/test_keys/keyring.asc    |  89 ++++++++
 .../extensions/minifi_pgp/test_keys/keyring.gpg    | Bin 0 -> 4080 bytes
 .../minifi_pgp/test_keys/secret_keyring.asc        | 159 +++++++++++++
 .../minifi_pgp/test_keys/secret_keyring.gpg        | Bin 0 -> 7427 bytes
 .../extensions/minifi_pgp/test_keys/truncated.asc  |  10 +
 .../minifi_pgp/test_keys/truncated_private.asc     |  17 ++
 .../minifi_pgp/test_messages/foo_for_alice.asc     |  12 +
 .../minifi_pgp/test_messages/foo_for_alice.gpg     | Bin 0 -> 346 bytes
 .../test_messages/password_encrypted_foo.asc       |   6 +
 .../test_messages/password_encrypted_foo.gpg       |   1 +
 minifi_rust/minifi_rs_behave/Dockerfile.alpine     |   2 +-
 minifi_rust/minifi_rs_behave/Dockerfile.debian     |   2 +-
 53 files changed, 2558 insertions(+), 2 deletions(-)

diff --git a/minifi_rust/extensions/minifi_pgp/.cargo/config.toml 
b/minifi_rust/extensions/minifi_pgp/.cargo/config.toml
new file mode 100644
index 000000000..cb8c02ddc
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/.cargo/config.toml
@@ -0,0 +1,5 @@
+[target.aarch64-apple-darwin]
+rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
+
+[target.x86_64-apple-darwin]
+rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
diff --git a/minifi_rust/extensions/minifi_pgp/.gitignore 
b/minifi_rust/extensions/minifi_pgp/.gitignore
new file mode 100644
index 000000000..f9f6d205f
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/.gitignore
@@ -0,0 +1,7 @@
+target
+output
+features/.venv
+features/output
+integration_tests/features/.venv
+integration_tests/features/linux_so
+integration_tests/.venv
\ No newline at end of file
diff --git a/minifi_rust/extensions/minifi_pgp/Cargo.toml 
b/minifi_rust/extensions/minifi_pgp/Cargo.toml
new file mode 100644
index 000000000..af2c6e174
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "minifi_pgp"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+minifi_native = { path = "../../minifi_native" }
+strum_macros = "0.28.0"
+strum = "0.28.0"
+pgp = "0.20.0"
+rand = "0.8.6" # pgp 0.20.0 doesnt support >= 0.9 rand yet
+
diff --git a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature 
b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature
new file mode 100644
index 000000000..3914e7130
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature
@@ -0,0 +1,54 @@
+@SUPPORTS_WINDOWS
+Feature: Test PGP extension's encryption and decryption capabilities
+
+  Background: The pgp library is successfully built on linux
+
+  Scenario: The pgp library is loaded into minifi
+    Given log property 
"logger.org::apache::nifi::minifi::core::extension::ExtensionManager" is set to 
"TRACE,stderr"
+    And log property "logger.org::apache::nifi::minifi::core::ClassLoader" is 
set to "TRACE,stderr"
+
+    When the MiNiFi instance starts up
+
+    Then the Minifi logs contain the following message: "Registering class 
'EncryptContentPGP' at '/minifi_pgp'" in less than 10 seconds
+    And the Minifi logs contain the following message: "Registering class 
'DecryptContentPGP' at '/minifi_pgp'" in less than 1 seconds
+    And the Minifi logs contain the following message: "Registering class 
'PGPPublicKeyService' at '/minifi_pgp'" in less than 1 seconds
+    And the Minifi logs contain the following message: "Registering class 
'PGPPrivateKeyService' at '/minifi_pgp'" in less than 1 seconds
+    And the Minifi logs do not contain errors
+    And the Minifi logs do not contain warnings
+
+  Scenario: Encrypted for Alice but not for Bob
+    Given log property 
"logger.minifi_pgp::processors::decrypt_content::DecryptContentPGP" is set to 
"TRACE,stderr"
+    And log property 
"logger.minifi_pgp::processors::encrypt_content::EncryptContentPGP" is set to 
"TRACE,stderr"
+
+    And a GetFile processor with the "Input Directory" property set to 
"/tmp/input"
+    And an EncryptContentPGP processor with a PGPPublicKeyService is set up
+    And a DecryptContentPGP processor named DecryptAlice with a 
PGPPrivateKeyService is set up for Alice
+    And a DecryptContentPGP processor named DecryptBob with a 
PGPPrivateKeyService is set up for Bob
+    And a PutFile processor with the name "AliceSuccess"
+    And a PutFile processor with the name "BobFailure"
+
+    And these processor properties are set
+      | processor name    | property name     | property value       |
+      | EncryptContentPGP | File Encoding     | ASCII                |
+      | EncryptContentPGP | Public Key Search | Alice                |
+      | AliceSuccess      | Directory         | /tmp/output/alice_ok |
+      | BobFailure        | Directory         | /tmp/output/bob_fail |
+
+    And the processors are connected up as described here
+      | source name       | relationship name | destination name  |
+      | GetFile           | success           | EncryptContentPGP |
+      | EncryptContentPGP | success           | DecryptAlice      |
+      | EncryptContentPGP | success           | DecryptBob        |
+      | DecryptAlice      | success           | AliceSuccess      |
+      | DecryptBob        | failure           | BobFailure        |
+
+    And AliceSuccess's success relationship is auto-terminated
+    And BobFailure's success relationship is auto-terminated
+
+    And a directory at "/tmp/input" has a file "test_file.log" with the 
content "test content"
+
+    When the MiNiFi instance starts up
+
+    Then at least one file with the content "test content" is placed in the 
"/tmp/output/alice_ok" directory in less than 5 seconds
+    And an encrypted armored pgp file is placed in the "/tmp/output/bob_fail" 
directory in less than 5 seconds
+    And the Minifi logs do not contain errors
diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py 
b/minifi_rust/extensions/minifi_pgp/features/environment.py
new file mode 100644
index 000000000..cdec44906
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/features/environment.py
@@ -0,0 +1,73 @@
+import os
+from typing import List
+
+from minifi_behave.containers.docker_image_builder import DockerImageBuilder
+from minifi_behave.core.hooks import common_after_scenario
+from minifi_behave.core.hooks import common_before_scenario, 
get_minifi_container_image
+from minifi_behave.core.minifi_test_context import MinifiTestContext
+
+
+def add_extension_to_minifi_container(
+    extension_name: str, possible_paths: List[str], context: MinifiTestContext
+):
+    new_container_name = f"apacheminificpp:{extension_name}"
+    is_windows = os.name == "nt"
+    if is_windows:
+        lib_filename = f"{extension_name}.dll"
+        container_extension_dir = (
+            "C:/Program Files/ApacheNiFiMiNiFi/nifi-minifi-cpp/extensions"
+        )
+    else:
+        lib_filename = f"lib{extension_name}.so"
+        container_extension_dir = "/opt/minifi/minifi-current/extensions/"
+
+    host_path = None
+    for path in possible_paths:
+        if os.path.exists(os.path.join(path, lib_filename)):
+            host_path = os.path.join(path, lib_filename)
+            break
+
+    assert host_path is not None, (
+        f"Could not find {lib_filename} in {[p for p in possible_paths]}"
+    )
+
+    with open(host_path, "rb") as f:
+        lib_content = f.read()
+
+    base_img = get_minifi_container_image()
+
+    if is_windows:
+        dockerfile = f"""
+FROM {base_img}
+COPY ["{lib_filename}", "{container_extension_dir}/{lib_filename}"]
+"""
+    else:
+        dockerfile = f"""
+FROM {base_img}
+COPY --chown=minificpp:minificpp {lib_filename} {container_extension_dir}
+RUN chmod 755 {container_extension_dir}{lib_filename}
+"""
+
+    builder = DockerImageBuilder(
+        image_tag=new_container_name,
+        dockerfile_content=dockerfile,
+        files_on_context={lib_filename: lib_content},
+    )
+
+    builder.build()
+    return new_container_name
+
+
+def before_all(context):
+    dir_path = os.path.dirname(os.path.realpath(__file__))
+    build_path = os.path.normpath(os.path.join(dir_path, 
"../../../target/release/"))
+    add_extension_to_minifi_container("minifi_pgp", [build_path], context)
+
+
+def before_scenario(context, scenario):
+    context.minifi_container_image = "apacheminificpp:minifi_pgp"
+    common_before_scenario(context, scenario)
+
+
+def after_scenario(context, scenario):
+    common_after_scenario(context, scenario)
diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py 
b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py
new file mode 100644
index 000000000..b2a4d6e63
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py
@@ -0,0 +1,100 @@
+import os
+from pathlib import Path
+
+import humanfriendly
+from behave import step, then
+
+from minifi_behave.steps import checking_steps  # noqa: F401
+from minifi_behave.steps import configuration_steps  # noqa: F401
+from minifi_behave.steps import core_steps  # noqa: F401
+from minifi_behave.steps import flow_building_steps  # noqa: F401
+from minifi_behave.core.helpers import wait_for_condition
+from minifi_behave.core.minifi_test_context import MinifiTestContext
+from minifi_behave.minifi.controller_service import ControllerService
+from minifi_behave.minifi.processor import Processor
+
+
+@step("an EncryptContentPGP processor with a PGPPublicKeyService is set up")
+def step_encrypt_content_with_service(context: MinifiTestContext):
+    dir_path = os.path.dirname(os.path.realpath(__file__))
+
+    public_key_service = ControllerService(
+        class_name="PGPPublicKeyService", service_name="my_public_keys"
+    )
+    alice_public_key = 
Path(f"{dir_path}/../../test_keys/keyring.asc").read_text()
+    public_key_service.add_property("Keyring", alice_public_key)
+    
context.get_or_create_default_minifi_container().flow_definition.controller_services.append(
+        public_key_service
+    )
+
+    processor = Processor("EncryptContentPGP", "EncryptContentPGP")
+    processor.add_property("Public Key Service", "my_public_keys")
+    
context.get_or_create_default_minifi_container().flow_definition.processors.append(
+        processor
+    )
+
+
+@step(
+    "a DecryptContentPGP processor named DecryptAlice with a 
PGPPrivateKeyService is set up for Alice"
+)
+def step_decrypt_content_for_alice(context: MinifiTestContext):
+    dir_path = os.path.dirname(os.path.realpath(__file__))
+
+    private_key_service = ControllerService(
+        class_name="PGPPrivateKeyService", service_name="alice_private_key"
+    )
+    alice_private_key = Path(
+        f"{dir_path}/../../test_keys/alice_private.asc"
+    ).read_text()
+    private_key_service.add_property("Key", alice_private_key)
+    private_key_service.add_property("Key Passphrase", "whiterabbit")
+    
context.get_or_create_default_minifi_container().flow_definition.controller_services.append(
+        private_key_service
+    )
+
+    processor = Processor("DecryptContentPGP", "DecryptAlice")
+    processor.add_property("Private Key Service", "alice_private_key")
+    
context.get_or_create_default_minifi_container().flow_definition.processors.append(
+        processor
+    )
+
+
+@step(
+    "a DecryptContentPGP processor named DecryptBob with a 
PGPPrivateKeyService is set up for Bob"
+)
+def step_decrypt_content_for_bob(context: MinifiTestContext):
+    dir_path = os.path.dirname(os.path.realpath(__file__))
+
+    private_key_service = ControllerService(
+        class_name="PGPPrivateKeyService", service_name="bob_private_key"
+    )
+    bob_private_key = 
Path(f"{dir_path}/../../test_keys/bob_private.asc").read_text()
+    private_key_service.add_property("Key", bob_private_key)
+    
context.get_or_create_default_minifi_container().flow_definition.controller_services.append(
+        private_key_service
+    )
+
+    processor = Processor("DecryptContentPGP", "DecryptBob")
+    processor.add_property("Private Key Service", "bob_private_key")
+    
context.get_or_create_default_minifi_container().flow_definition.processors.append(
+        processor
+    )
+
+
+@then(
+    'an encrypted armored pgp file is placed in the "{directory}" directory in 
less than {duration}'
+)
+def then_armored_pgp_file_in_dir(
+    context: MinifiTestContext, directory: str, duration: str
+):
+    duration_seconds = humanfriendly.parse_timespan(duration)
+    assert wait_for_condition(
+        condition=lambda: (
+            
context.get_or_create_default_minifi_container().directory_contains_file_with_regex(
+                directory, "-----BEGIN PGP MESSAGE-----"
+            )
+        ),
+        timeout_seconds=duration_seconds,
+        bail_condition=lambda: False,
+        context=context,
+    )
diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md 
b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md
new file mode 100644
index 000000000..f2d9cbeab
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md
@@ -0,0 +1,120 @@
+<!--
+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.
+-->
+
+## Table of Contents
+
+### Processors
+
+- [DecryptContentPGP](#DecryptContentPGP)
+- [EncryptContentPGP](#EncryptContentPGP)
+### Controller Services
+
+- [PGPPrivateKeyService](#PGPPrivateKeyService)
+- [PGPPublicKeyService](#PGPPublicKeyService)
+
+
+## DecryptContentPGP
+
+### Description
+
+Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy 
preserves OpenPGP encoding to support subsequent signature verification.
+
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other 
properties (not in bold) are considered optional. The table also indicates any 
default values, and whether a property supports the NiFi Expression Language.
+
+| Name                | Default Value | Allowable Values       | Description   
                                                                                
              |
+|---------------------|---------------|------------------------|-------------------------------------------------------------------------------------------------------------|
+| Decryption Strategy | DECRYPTED     | DECRYPTED<br/>PACKAGED | Strategy for 
writing files to success after decryption                                       
               |
+| Symmetric Password  |               |                        | Password used 
for decrypting data encrypted with Password-Based Encryption<br/>**Sensitive 
Property: true** |
+| Private Key Service |               |                        | PGP Private 
Key Service for decrypting data encrypted with Public Key Encryption            
                |
+
+### Relationships
+
+| Name    | Description          |
+|---------|----------------------|
+| success | Decryption Succeeded |
+| failure | Decryption Failed    |
+
+### Output Attributes
+
+| Attribute                 | Relationship | Description                       
                                                                                
                                                                                
                                                                                
                                                                           |
+|---------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| pgp.literal.data.filename | success      | Filename from decrypted Literal 
Data (Note that OpenPGP signatures do not include the formatting octet, the 
file name, and the date field of the Literal Data packet in a signature hash; 
therefore, those fields are not protected against tampering in a signed 
document. Therefore a lot of implementation omit these inherently malleable 
metadata)      |
+| pgp.literal.data.modified | success      | Modified Date from decrypted 
Literal Data (Note that OpenPGP signatures do not include the formatting octet, 
the file name, and the date field of the Literal Data packet in a signature 
hash; therefore, those fields are not protected against tampering in a signed 
document. Therefore a lot of implementation omit these inherently malleable 
metadata) |
+
+
+## EncryptContentPGP
+
+### Description
+
+Encrypt contents using OpenPGP.
+
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other 
properties (not in bold) are considered optional. The table also indicates any 
default values, and whether a property supports the NiFi Expression Language.
+
+| Name               | Default Value | Allowable Values | Description          
                                                                                
                                                                                
|
+|--------------------|---------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **File Encoding**  | BINARY        | ASCII<br/>BINARY | File Encoding for 
encryption                                                                      
                                                                                
   |
+| Symmetric Password |               |                  | Password used for 
encrypting data with Password-Based Encryption<br/>**Sensitive Property: true** 
                                                                                
   |
+| Public Key Search  |               |                  | PGP Public Key 
Search will be used to match against the User ID or Key ID when formatted as 
uppercase hexadecimal string of 16 characters<br/>**Supports Expression 
Language: true** |
+| Public Key Service |               |                  | PGP Public Key 
Service for encrypting data with Public Key Encryption                          
                                                                                
      |
+
+### Relationships
+
+| Name    | Description          |
+|---------|----------------------|
+| success | Encryption Succeeded |
+| failure | Encryption Failed    |
+
+### Output Attributes
+
+| Attribute         | Relationship | Description   |
+|-------------------|--------------|---------------|
+| pgp.file.encoding | success      | File Encoding |
+
+
+## PGPPrivateKeyService
+
+### Description
+
+PGP Private Key Service provides Private Keys loaded from files or properties
+
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other 
properties (not in bold) are considered optional. The table also indicates any 
default values, and whether a property supports the NiFi Expression Language.
+
+| Name           | Default Value | Allowable Values | Description              
                                                                               |
+|----------------|---------------|------------------|---------------------------------------------------------------------------------------------------------|
+| Key File       |               |                  | File path to PGP Secret 
Key encoded in binary or ASCII Armor<br/>**Supports Expression Language: true** 
|
+| Key            |               |                  | Secret Key encoded in 
ASCII Armor<br/>**Sensitive Property: true**                                    
  |
+| Key Passphrase |               |                  | Passphrase used for 
decrypting Private Keys<br/>**Sensitive Property: true**                        
    |
+
+
+## PGPPublicKeyService
+
+### Description
+
+PGP Public Key Service providing Public Keys loaded from files
+
+### Properties
+
+In the list below, the names of required properties appear in bold. Any other 
properties (not in bold) are considered optional. The table also indicates any 
default values, and whether a property supports the NiFi Expression Language.
+
+| Name         | Default Value | Allowable Values | Description                
                                                                                
        |
+|--------------|---------------|------------------|--------------------------------------------------------------------------------------------------------------------|
+| Keyring File |               |                  | File path to PGP Keyring 
or Secret Key encoded in binary or ASCII Armor<br/>**Supports Expression 
Language: true** |
+| Keyring      |               |                  | PGP Keyring or Secret Key 
encoded in ASCII Armor<br/>**Sensitive Property: true**                         
         |
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs
new file mode 100644
index 000000000..cff723a71
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs
@@ -0,0 +1,56 @@
+use pgp::composed::SignedKeyDetails;
+use pgp::types::KeyId;
+
+/// Returns true when `target_id` matches either:
+/// - the key's Key ID formatted as 16-character hex (case-insensitive), or
+/// - any of its User IDs as a case-insensitive substring match.
+pub(crate) fn key_matches(key_id: &KeyId, details: &SignedKeyDetails, 
target_id: &str) -> bool {
+    let target = target_id.trim();
+    if target.is_empty() {
+        return false;
+    }
+
+    if key_id.to_string().eq_ignore_ascii_case(target) {
+        return true;
+    }
+
+    let target_lower = target.to_ascii_lowercase();
+    details.users.iter().any(|user| {
+        user.id
+            .as_str()
+            .map(|user_id| 
user_id.to_ascii_lowercase().contains(&target_lower))
+            .unwrap_or(false)
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn key_id_from_hex(hex: &str) -> KeyId {
+        let mut bytes = [0u8; 8];
+        for (i, chunk) in hex.as_bytes().chunks(2).take(8).enumerate() {
+            bytes[i] = u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 
16).unwrap();
+        }
+        KeyId::from(bytes)
+    }
+
+    #[test]
+    fn empty_target_never_matches() {
+        let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]);
+        let key_id = key_id_from_hex("1122334455667788");
+        assert!(!key_matches(&key_id, &details, ""));
+        assert!(!key_matches(&key_id, &details, "   "));
+    }
+
+    #[test]
+    fn matches_key_id_case_insensitive() {
+        let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]);
+        let key_id = key_id_from_hex("11ABcdEF33445566");
+
+        assert!(key_matches(&key_id, &details, "11abcdef33445566"));
+        assert!(key_matches(&key_id, &details, "11ABCDEF33445566"));
+        assert!(!key_matches(&key_id, &details, "11abcdef3344556")); // 15 
chars
+        assert!(!key_matches(&key_id, &details, "abcdef33445566"));
+    }
+}
diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs
new file mode 100644
index 000000000..d2c83be43
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs
@@ -0,0 +1,3 @@
+pub(crate) mod key_lookup;
+pub(crate) mod private_key_service;
+pub(crate) mod public_key_service;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs
new file mode 100644
index 000000000..bef534236
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs
@@ -0,0 +1,94 @@
+mod controller_service_definition;
+mod properties;
+
+#[cfg(test)]
+use crate::controller_services::key_lookup::key_matches;
+use 
crate::controller_services::private_key_service::properties::KEY_PASSPHRASE;
+use crate::utils;
+use minifi_native::macros::ComponentIdentifier;
+use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, 
warn};
+use pgp::composed::{Deserializable, SignedSecretKey, TheRing};
+#[cfg(test)]
+use pgp::types::KeyDetails;
+use std::path::PathBuf;
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct PGPPrivateKeyService {
+    private_keys: Vec<SignedSecretKey>,
+    passphrase: pgp::types::Password,
+}
+
+impl EnableControllerService for PGPPrivateKeyService {
+    fn enable<P: GetProperty, L: Logger>(context: &P, logger: &L) -> 
Result<Self, MinifiError>
+    where
+        Self: Sized,
+    {
+        let mut private_keys = vec![];
+        if let Some(keyring_file_path) = 
context.get_property::<PathBuf>(&properties::KEY_FILE)? {
+            if let Ok((keys, _headers)) = 
SignedSecretKey::from_armor_file_many(&keyring_file_path)
+            {
+                collect_keys(keys, &mut private_keys, logger);
+            } else if let Ok(keys) = 
SignedSecretKey::from_file_many(keyring_file_path) {
+                collect_keys(keys, &mut private_keys, logger);
+            }
+        }
+        if let Some(keyring_ascii) = 
context.get_property::<String>(&properties::KEY)?
+            && let Ok((keys, _headers)) = 
SignedSecretKey::from_armor_many(keyring_ascii.as_bytes())
+        {
+            collect_keys(keys, &mut private_keys, logger);
+        }
+
+        let passphrase = context
+            .get_property::<utils::Password>(&KEY_PASSPHRASE)?
+            .unwrap_or_default();
+
+        if private_keys.is_empty() {
+            return Err(MinifiError::controller_service_err(
+                "Could not load any valid keys",
+            ));
+        }
+        Ok(Self {
+            private_keys,
+            passphrase,
+        })
+    }
+}
+
+impl PGPPrivateKeyService {
+    pub fn get_the_ring(&'_ self) -> TheRing<'_> {
+        TheRing {
+            secret_keys: self.private_keys.iter().collect(),
+            key_passwords: vec![&self.passphrase],
+            message_password: vec![],
+            session_keys: vec![],
+            decrypt_options: Default::default(),
+        }
+    }
+
+    #[cfg(test)]
+    pub fn get_secret_key(&self, target_id: &str) -> Option<&SignedSecretKey> {
+        self.private_keys.iter().find(|private_key| {
+            key_matches(
+                &private_key.primary_key.legacy_key_id(),
+                &private_key.details,
+                target_id,
+            )
+        })
+    }
+}
+
+fn collect_keys<I, L>(keys: I, out: &mut Vec<SignedSecretKey>, logger: &L)
+where
+    I: Iterator<Item = pgp::errors::Result<SignedSecretKey>>,
+    L: Logger,
+{
+    for key in keys {
+        match key {
+            Ok(k) => out.push(k),
+            Err(e) => warn!(logger, "Skipping unparseable private key: {}", e),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs
new file mode 100644
index 000000000..11f80a911
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs
@@ -0,0 +1,10 @@
+use super::PGPPrivateKeyService;
+use super::properties::*;
+use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface};
+
+impl ControllerServiceDefinition for PGPPrivateKeyService {
+    const DESCRIPTION: &'static str =
+        "PGP Private Key Service provides Private Keys loaded from files or 
properties";
+    const PROPERTIES: &'static [Property] = &[KEY_FILE, KEY, KEY_PASSPHRASE];
+    const PROVIDED_APIS: &'static [ProvidedInterface<Self>] = &[];
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs
new file mode 100644
index 000000000..6cabc0086
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs
@@ -0,0 +1,32 @@
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::NoConstraints;
+
+pub(crate) const KEY_FILE: Property = Property {
+    name: "Key File",
+    description: "File path to PGP Secret Key encoded in binary or ASCII 
Armor",
+    is_required: false,
+    is_sensitive: false,
+    supports_expr_lang: true,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const KEY: Property = Property {
+    name: "Key",
+    description: "Secret Key encoded in ASCII Armor",
+    is_required: false,
+    is_sensitive: true,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const KEY_PASSPHRASE: Property = Property {
+    name: "Key Passphrase",
+    description: "Passphrase used for decrypting Private Keys",
+    is_required: false,
+    is_sensitive: true,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: NoConstraints,
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs
new file mode 100644
index 000000000..efdb6e39a
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs
@@ -0,0 +1,214 @@
+use super::PGPPrivateKeyService;
+use crate::test_utils::get_test_key_path;
+use minifi_native::MinifiError::ControllerServiceError;
+use minifi_native::{
+    ComponentIdentifier, EnableControllerService, 
MockControllerServiceContext, MockLogger,
+};
+
+fn assert_private_key_service_enable_fails_with_no_valid_keys(
+    context: &MockControllerServiceContext,
+) {
+    if let Err(ControllerServiceError(error)) =
+        PGPPrivateKeyService::enable(context, &MockLogger::new())
+    {
+        assert_eq!(error, "Could not load any valid keys");
+    } else {
+        panic!("Didnt fail with no_valid_keys");
+    }
+}
+
+#[test]
+fn test_component_id() {
+    assert_eq!(
+        PGPPrivateKeyService::CLASS_NAME,
+        
"minifi_pgp::controller_services::private_key_service::PGPPrivateKeyService"
+    );
+    assert_eq!(PGPPrivateKeyService::GROUP_NAME, "minifi_pgp");
+    assert_eq!(PGPPrivateKeyService::VERSION, "0.1.0");
+}
+
+#[test]
+fn default_fails() {
+    let context = MockControllerServiceContext::new();
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn corrupted_binary_keyring_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Key File".to_string(), get_test_key_path("garbage.gpg"));
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn armored_public_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("private_mistake.asc"),
+    );
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn corrupted_armored_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("truncated_private.asc"),
+    );
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn non_existent_keyfile() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("non_existent.asc"),
+    );
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn single_armored_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("alice_private.asc"),
+    );
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(
+        controller_service
+            .get_secret_key("[email protected]")
+            .is_some()
+    );
+
+    assert!(controller_service.get_secret_key("Bob").is_none());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn single_binary_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("alice_private.gpg"),
+    );
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("A").is_some());
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(
+        controller_service
+            .get_secret_key("Alice <[email protected]>")
+            .is_some()
+    );
+
+    assert!(controller_service.get_secret_key("<Alice>").is_none());
+
+    assert!(controller_service.get_secret_key("Bob").is_none());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn armored_keyring_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("secret_keyring.asc"),
+    );
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(controller_service.get_secret_key("Bob").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn binary_keyring_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Key File".to_string(),
+        get_test_key_path("secret_keyring.gpg"),
+    );
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(controller_service.get_secret_key("Bob").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn armored_keyring() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content = 
std::fs::read_to_string(get_test_key_path("secret_keyring.asc"))
+        .expect("required for test");
+
+    context.properties.insert("Key".to_string(), file_content);
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(controller_service.get_secret_key("Bob").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("[email protected]").is_some());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn armored_single_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required
 for test");
+
+    context.properties.insert("Key".to_string(), file_content);
+
+    let controller_service =
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
+    assert!(controller_service.get_secret_key("Alice").is_some());
+    assert!(controller_service.get_secret_key("Bob").is_none());
+    assert!(controller_service.get_secret_key("Carol").is_none());
+}
+
+#[test]
+fn corrupted_armored_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content = 
std::fs::read_to_string(get_test_key_path("truncated_private.asc"))
+        .expect("required for test");
+
+    context.properties.insert("Key".to_string(), file_content);
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn public_ascii_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for 
test");
+
+    context.properties.insert("Key".to_string(), file_content);
+
+    assert_private_key_service_enable_fails_with_no_valid_keys(&context);
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs
new file mode 100644
index 000000000..93f03d20e
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs
@@ -0,0 +1,72 @@
+mod controller_service_definition;
+mod properties;
+
+use crate::controller_services::key_lookup::key_matches;
+use crate::controller_services::public_key_service::properties::{KEYRING, 
KEYRING_FILE};
+use minifi_native::macros::ComponentIdentifier;
+use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, 
warn};
+use pgp::composed::{Deserializable, SignedPublicKey};
+use pgp::types::KeyDetails;
+use std::path::PathBuf;
+
+#[derive(Debug, ComponentIdentifier, PartialEq)]
+pub(crate) struct PGPPublicKeyService {
+    public_keys: Vec<SignedPublicKey>,
+}
+
+impl EnableControllerService for PGPPublicKeyService {
+    fn enable<P: GetProperty, L: Logger>(context: &P, logger: &L) -> 
Result<Self, MinifiError>
+    where
+        Self: Sized,
+    {
+        let mut public_keys = vec![];
+        if let Some(keyring_file_path) = 
context.get_property::<PathBuf>(&KEYRING_FILE)? {
+            if let Ok((keys, _headers)) = 
SignedPublicKey::from_armor_file_many(&keyring_file_path)
+            {
+                collect_keys(keys, &mut public_keys, logger);
+            } else if let Ok(keys) = 
SignedPublicKey::from_file_many(keyring_file_path) {
+                collect_keys(keys, &mut public_keys, logger);
+            }
+        }
+        if let Some(keyring_ascii) = context.get_property::<String>(&KEYRING)?
+            && let Ok((keys, _headers)) = 
SignedPublicKey::from_armor_many(keyring_ascii.as_bytes())
+        {
+            collect_keys(keys, &mut public_keys, logger);
+        }
+
+        if public_keys.is_empty() {
+            return Err(MinifiError::controller_service_err(
+                "Could not load any valid keys",
+            ));
+        }
+        Ok(Self { public_keys })
+    }
+}
+
+fn collect_keys<I, L>(keys: I, out: &mut Vec<SignedPublicKey>, logger: &L)
+where
+    I: Iterator<Item = pgp::errors::Result<SignedPublicKey>>,
+    L: Logger,
+{
+    for key in keys {
+        match key {
+            Ok(k) => out.push(k),
+            Err(e) => warn!(logger, "Skipping unparseable public key: {}", e),
+        }
+    }
+}
+
+impl PGPPublicKeyService {
+    pub fn get(&self, target_id: &str) -> Option<&SignedPublicKey> {
+        self.public_keys.iter().find(|public_key| {
+            key_matches(
+                &public_key.primary_key.legacy_key_id(),
+                &public_key.details,
+                target_id,
+            )
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs
new file mode 100644
index 000000000..77fb1c34a
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs
@@ -0,0 +1,10 @@
+use super::PGPPublicKeyService;
+use super::properties::*;
+use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface};
+
+impl ControllerServiceDefinition for PGPPublicKeyService {
+    const DESCRIPTION: &'static str =
+        "PGP Public Key Service providing Public Keys loaded from files";
+    const PROPERTIES: &'static [Property] = &[KEYRING_FILE, KEYRING];
+    const PROVIDED_APIS: &'static [ProvidedInterface<Self>] = &[];
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs
new file mode 100644
index 000000000..2087163ea
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs
@@ -0,0 +1,22 @@
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::NoConstraints;
+
+pub(crate) const KEYRING_FILE: Property = Property {
+    name: "Keyring File",
+    description: "File path to PGP Keyring or Secret Key encoded in binary or 
ASCII Armor",
+    is_required: false,
+    is_sensitive: false,
+    supports_expr_lang: true,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const KEYRING: Property = Property {
+    name: "Keyring",
+    description: "PGP Keyring or Secret Key encoded in ASCII Armor",
+    is_required: false,
+    is_sensitive: true,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: NoConstraints,
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs
new file mode 100644
index 000000000..4adbb14b4
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs
@@ -0,0 +1,245 @@
+use super::PGPPublicKeyService;
+use crate::test_utils::get_test_key_path;
+use minifi_native::MinifiError::ControllerServiceError;
+use minifi_native::{
+    ComponentIdentifier, EnableControllerService, 
MockControllerServiceContext, MockLogger,
+};
+use pgp::types::KeyDetails;
+
+fn assert_public_key_service_enable_fails_with_no_valid_keys(
+    context: &MockControllerServiceContext,
+) {
+    if let Err(ControllerServiceError(error)) =
+        PGPPublicKeyService::enable(context, &MockLogger::new())
+    {
+        assert_eq!(error, "Could not load any valid keys");
+    } else {
+        panic!("Didnt fail with no_valid_keys");
+    }
+}
+
+#[test]
+fn test_component_id() {
+    assert_eq!(
+        PGPPublicKeyService::CLASS_NAME,
+        
"minifi_pgp::controller_services::public_key_service::PGPPublicKeyService"
+    );
+    assert_eq!(PGPPublicKeyService::GROUP_NAME, "minifi_pgp");
+    assert_eq!(PGPPublicKeyService::VERSION, "0.1.0");
+}
+
+#[test]
+fn default_fails() {
+    let context = MockControllerServiceContext::new();
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn corrupted_binary_keyring_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("garbage.gpg"));
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn armored_private_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Keyring File".to_string(),
+        get_test_key_path("alice_private.asc"),
+    );
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn corrupted_armored_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Keyring File".to_string(),
+        get_test_key_path("truncated.asc"),
+    );
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn non_existent_keyfile() {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Keyring File".to_string(),
+        get_test_key_path("non_existent.asc"),
+    );
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn single_armored_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("alice.asc"));
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+
+    assert!(controller_service.get("Alice").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+
+    assert!(controller_service.get("Bob").is_none());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn single_binary_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("alice.gpg"));
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+    assert!(controller_service.get("A").is_some());
+    assert!(controller_service.get("Alice").is_some());
+    assert!(
+        controller_service
+            .get("Alice <[email protected]>")
+            .is_some()
+    );
+
+    assert!(controller_service.get("<Alice>").is_none());
+
+    assert!(controller_service.get("Bob").is_none());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn armored_keyring_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("keyring.asc"));
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+    assert!(controller_service.get("Alice").is_some());
+    assert!(controller_service.get("Bob").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn binary_keyring_key_file() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("keyring.gpg"));
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+    assert!(controller_service.get("Alice").is_some());
+    assert!(controller_service.get("Bob").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn armored_keyring() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("keyring.asc")).expect("required for 
test");
+
+    context
+        .properties
+        .insert("Keyring".to_string(), file_content);
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+    assert!(controller_service.get("Alice").is_some());
+    assert!(controller_service.get("Bob").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("[email protected]").is_some());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn armored_single_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for 
test");
+
+    context
+        .properties
+        .insert("Keyring".to_string(), file_content);
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+    assert!(controller_service.get("Alice").is_some());
+    assert!(controller_service.get("Bob").is_none());
+    assert!(controller_service.get("Carol").is_none());
+}
+
+#[test]
+fn corrupted_armored_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("truncated.asc")).expect("required 
for test");
+
+    context
+        .properties
+        .insert("Keyring".to_string(), file_content);
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn private_ascii_key() {
+    let mut context = MockControllerServiceContext::new();
+
+    let file_content =
+        
std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required
 for test");
+
+    context
+        .properties
+        .insert("Keyring".to_string(), file_content);
+
+    assert_public_key_service_enable_fails_with_no_valid_keys(&context);
+}
+
+#[test]
+fn looks_up_by_key_id_hex() {
+    let mut context = MockControllerServiceContext::new();
+    context
+        .properties
+        .insert("Keyring File".to_string(), get_test_key_path("alice.asc"));
+
+    let controller_service =
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed");
+
+    // Get Alice's Key ID from the loaded key so the test doesn't hard-code 
hex bytes.
+    let alice = controller_service.get("Alice").expect("Alice should exist");
+    let key_id_hex = alice.primary_key.legacy_key_id().to_string();
+    assert_eq!(key_id_hex.len(), 16);
+
+    // Full 16-char hex, both cases, should match.
+    assert!(controller_service.get(&key_id_hex).is_some());
+    assert!(
+        controller_service
+            .get(&key_id_hex.to_ascii_uppercase())
+            .is_some()
+    );
+
+    // A partial or unrelated hex string should not.
+    assert!(controller_service.get(&key_id_hex[..8]).is_none());
+    assert!(controller_service.get("0123456789abcdef").is_none());
+}
diff --git a/minifi_rust/extensions/minifi_pgp/src/lib.rs 
b/minifi_rust/extensions/minifi_pgp/src/lib.rs
new file mode 100644
index 000000000..6c0125687
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/lib.rs
@@ -0,0 +1,23 @@
+mod controller_services;
+mod processors;
+
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+use crate::controller_services::public_key_service::PGPPublicKeyService;
+use crate::processors::decrypt_content::DecryptContentPGP;
+use crate::processors::encrypt_content::EncryptContentPGP;
+use minifi_native::{FlowFileStreamTransformProcessorType, MultiThreaded};
+
+minifi_native::declare_minifi_extension!(
+    processors: [
+        (FlowFileStreamTransformProcessorType, MultiThreaded, 
EncryptContentPGP),
+        (FlowFileStreamTransformProcessorType, MultiThreaded, 
DecryptContentPGP),
+    ],
+    controllers: [
+        PGPPublicKeyService,
+        PGPPrivateKeyService,
+    ]
+);
+
+#[cfg(test)]
+mod test_utils;
+mod utils;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs
new file mode 100644
index 000000000..e97b15e54
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs
@@ -0,0 +1,150 @@
+mod output_attributes;
+pub(crate) mod properties;
+mod relationships;
+
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+use crate::processors::decrypt_content::properties::{
+    DECRYPTION_STRATEGY, PRIVATE_KEY_SERVICE, SYMMETRIC_PASSWORD,
+};
+use crate::processors::decrypt_content::relationships::{FAILURE, SUCCESS};
+use crate::utils;
+use minifi_native::macros::{ComponentIdentifier, PropertyType};
+use minifi_native::{
+    FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, 
Logger, MinifiError,
+    OutputStream, Schedule, TransformStreamResult, warn,
+};
+use pgp::composed::{Message, TheRing};
+use std::collections::HashMap;
+use std::fmt::Debug;
+use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
+
+#[derive(
+    Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
+)]
+#[strum(serialize_all = "UPPERCASE", const_into_str)]
+enum DecryptionStrategy {
+    Decrypted,
+    Packaged,
+}
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct DecryptContentPGP {
+    decompress_data: bool,
+    symmetric_password: Option<pgp::types::Password>,
+}
+
+impl Schedule for DecryptContentPGP {
+    fn schedule<P: GetProperty, L>(context: &P, _logger: &L) -> Result<Self, 
MinifiError>
+    where
+        Self: Sized,
+        L: Logger,
+    {
+        let decryption_strategy =
+            
context.get_req_property::<DecryptionStrategy>(&DECRYPTION_STRATEGY)?;
+
+        let symmetric_password = 
context.get_property::<utils::Password>(&SYMMETRIC_PASSWORD)?;
+        let has_context_service = context
+            .get_property::<String>(&PRIVATE_KEY_SERVICE)?
+            .is_some();
+        if !has_context_service && symmetric_password.is_none() {
+            Err(MinifiError::schedule_err(
+                "Either Symmetric Password or Private Key Service must be set",
+            ))
+        } else {
+            Ok(DecryptContentPGP {
+                decompress_data: decryption_strategy == 
DecryptionStrategy::Decrypted,
+                symmetric_password,
+            })
+        }
+    }
+}
+
+impl DecryptContentPGP {
+    fn decrypt_msg<'a>(
+        &'a self,
+        msg: Message<'a>,
+        private_key_service: Option<&'a PGPPrivateKeyService>,
+    ) -> pgp::errors::Result<Message<'a>> {
+        let mut ring = if let Some(pks) = private_key_service {
+            pks.get_the_ring()
+        } else {
+            TheRing::default()
+        };
+
+        ring.decrypt_options = ring.decrypt_options.enable_gnupg_aead();
+
+        if let Some(sym_passwd) = &self.symmetric_password {
+            ring.message_password.push(sym_passwd);
+        }
+        let (decrypted_msg, _ring_result) = msg.decrypt_the_ring(ring, false)?;
+        Ok(decrypted_msg)
+    }
+
+    fn extract_attributes_from_decrypted_message(
+        decrypted_msg: &Message,
+    ) -> HashMap<String, String> {
+        let mut attributes_to_add = HashMap::new();
+        if let Some(literal_data_header) = decrypted_msg.literal_data_header() 
{
+            if let Ok(file_name) = 
str::from_utf8(literal_data_header.file_name()) {
+                attributes_to_add.insert(
+                    output_attributes::LITERAL_DATA_FILENAME.name.to_string(),
+                    file_name.to_string(),
+                );
+            }
+            attributes_to_add.insert(
+                output_attributes::LITERAL_DATA_MODIFIED.name.to_string(),
+                (1000u64 * literal_data_header.created().as_secs() as 
u64).to_string(), // Nifi uses ms timestamp
+            );
+        }
+        attributes_to_add
+    }
+}
+
+impl FlowFileStreamTransform for DecryptContentPGP {
+    fn transform<Ctx: GetProperty + GetControllerService, LoggerImpl: Logger>(
+        &self,
+        context: &Ctx,
+        input_stream: &mut dyn InputStream,
+        output_stream: &mut dyn OutputStream,
+        logger: &LoggerImpl,
+    ) -> Result<TransformStreamResult, MinifiError> {
+        let Ok(msg) = Message::from_reader(input_stream).map(|(msg, _header)| 
msg) else {
+            warn!(logger, "No valid PGP message found");
+            return Ok(TransformStreamResult::route_without_changes(&FAILURE));
+        };
+
+        let private_key_service =
+            
context.get_controller_service::<PGPPrivateKeyService>(&PRIVATE_KEY_SERVICE)?;
+
+        let Ok(mut decrypted_msg) = self.decrypt_msg(msg, private_key_service) 
else {
+            warn!(logger, "Failed to decrypt data");
+            return Ok(TransformStreamResult::route_without_changes(&FAILURE));
+        };
+
+        if self.decompress_data && decrypted_msg.is_compressed() {
+            match decrypted_msg.decompress() {
+                Ok(decompressed_data) => {
+                    decrypted_msg = decompressed_data;
+                }
+                Err(e) => {
+                    warn!(logger, "Failed to decompress data: {}", e);
+                    return 
Ok(TransformStreamResult::route_without_changes(&FAILURE));
+                }
+            }
+        };
+
+        let attributes_to_add = 
Self::extract_attributes_from_decrypted_message(&decrypted_msg);
+        let Ok(_written_bytes) = std::io::copy(&mut 
decrypted_msg.into_inner(), output_stream)
+        else {
+            warn!(logger, "Failed to extract raw data from decrypted message");
+            return Ok(TransformStreamResult::route_without_changes(&FAILURE));
+        };
+
+        Ok(TransformStreamResult::new(&SUCCESS, attributes_to_add))
+    }
+}
+
+#[cfg(test)]
+mod tests;
+
+pub(crate) mod processor_definition;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs
new file mode 100644
index 000000000..e4a894a1c
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs
@@ -0,0 +1,13 @@
+use minifi_native::OutputAttribute;
+
+pub(crate) const LITERAL_DATA_FILENAME: OutputAttribute = OutputAttribute {
+    name: "pgp.literal.data.filename",
+    relationships: &["success"],
+    description: "Filename from decrypted Literal Data (Note that OpenPGP 
signatures do not include the formatting octet, the file name, and the date 
field of the Literal Data packet in a signature hash; therefore, those fields 
are not protected against tampering in a signed document. Therefore a lot of 
implementations omit these inherently malleable metadata)",
+};
+
+pub(crate) const LITERAL_DATA_MODIFIED: OutputAttribute = OutputAttribute {
+    name: "pgp.literal.data.modified",
+    relationships: &["success"],
+    description: "Modified Date from decrypted Literal Data (Note that OpenPGP 
signatures do not include the formatting octet, the file name, and the date 
field of the Literal Data packet in a signature hash; therefore, those fields 
are not protected against tampering in a signed document. Therefore a lot of 
implementations omit these inherently malleable metadata)",
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs
new file mode 100644
index 000000000..c9c6fddf6
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs
@@ -0,0 +1,22 @@
+use super::{DecryptContentPGP, output_attributes, properties, relationships};
+use minifi_native::{
+    OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, 
Relationship,
+};
+
+impl ProcessorDefinition for DecryptContentPGP {
+    const DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages. 
Using the Packaged Decryption Strategy preserves OpenPGP encoding to support 
subsequent signature verification.";
+    const INPUT_REQUIREMENT: ProcessorInputRequirement = 
ProcessorInputRequirement::Required;
+    const SUPPORTS_DYNAMIC_PROPERTIES: bool = false;
+    const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false;
+    const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[
+        output_attributes::LITERAL_DATA_FILENAME,
+        output_attributes::LITERAL_DATA_MODIFIED,
+    ];
+    const RELATIONSHIPS: &'static [Relationship] =
+        &[relationships::SUCCESS, relationships::FAILURE];
+    const PROPERTIES: &'static [Property] = &[
+        properties::DECRYPTION_STRATEGY,
+        properties::SYMMETRIC_PASSWORD,
+        properties::PRIVATE_KEY_SERVICE,
+    ];
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs
new file mode 100644
index 000000000..d642e7f35
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs
@@ -0,0 +1,36 @@
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+use crate::processors::decrypt_content::DecryptionStrategy;
+use minifi_native::ComponentIdentifier;
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, 
NoConstraints};
+use strum::VariantNames;
+
+pub(crate) const DECRYPTION_STRATEGY: Property = Property {
+    name: "Decryption Strategy",
+    description: "Strategy for writing files to success after decryption",
+    is_required: true,
+    is_sensitive: false,
+    supports_expr_lang: false,
+    default_value: Some(DecryptionStrategy::Decrypted.into_str()),
+    constraints: AllowedValues(DecryptionStrategy::VARIANTS),
+};
+
+pub(crate) const SYMMETRIC_PASSWORD: Property = Property {
+    name: "Symmetric Password",
+    description: "Password used for decrypting data encrypted with 
Password-Based Encryption",
+    is_required: false,
+    is_sensitive: true,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const PRIVATE_KEY_SERVICE: Property = Property {
+    name: "Private Key Service",
+    description: "PGP Private Key Service for decrypting data encrypted with 
Public Key Encryption",
+    is_required: false,
+    is_sensitive: false,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: AllowedType(PGPPrivateKeyService::CLASS_NAME),
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs
new file mode 100644
index 000000000..a4403da17
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs
@@ -0,0 +1,11 @@
+use minifi_native::Relationship;
+
+pub(crate) const SUCCESS: Relationship = Relationship {
+    name: "success",
+    description: "Decryption Succeeded",
+};
+
+pub(crate) const FAILURE: Relationship = Relationship {
+    name: "failure",
+    description: "Decryption Failed",
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs
new file mode 100644
index 000000000..65d4167dc
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs
@@ -0,0 +1,253 @@
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+use crate::processors::decrypt_content::{DecryptContentPGP, output_attributes};
+use crate::test_utils;
+use crate::test_utils::get_test_message;
+use minifi_native::{
+    ComponentIdentifier, EnableControllerService, FlowFileStreamTransform, 
IoState,
+    MockControllerServiceContext, MockLogger, MockProcessContext, Schedule,
+};
+
+#[test]
+fn test_ids() {
+    assert_eq!(
+        DecryptContentPGP::CLASS_NAME,
+        "minifi_pgp::processors::decrypt_content::DecryptContentPGP"
+    );
+    assert_eq!(DecryptContentPGP::GROUP_NAME, "minifi_pgp");
+    assert_eq!(DecryptContentPGP::VERSION, "0.1.0");
+}
+
+#[test]
+fn fails_to_schedule_by_default() {
+    let decrypt_content =
+        DecryptContentPGP::schedule(&MockProcessContext::new(), 
&MockLogger::new());
+    assert!(decrypt_content.is_err());
+}
+
+#[test]
+fn schedules_with_password() {
+    let mut context = MockProcessContext::new();
+    context.properties.insert(
+        super::properties::SYMMETRIC_PASSWORD.name.to_string(),
+        "my_secret_password".to_string(),
+    );
+    let decrypt_content = DecryptContentPGP::schedule(&context, 
&MockLogger::new());
+    assert!(decrypt_content.is_ok());
+}
+
+#[test]
+fn schedules_with_controller() {
+    let mut context = MockProcessContext::new();
+    context.properties.insert(
+        super::properties::PRIVATE_KEY_SERVICE.name.to_string(),
+        "my_private_key_service".to_string(),
+    );
+    let decrypt_content = DecryptContentPGP::schedule(&context, 
&MockLogger::new());
+    assert!(decrypt_content.is_ok());
+}
+
+#[test]
+fn schedule_rejects_invalid_strategy_without_panicking() {
+    let mut context = MockProcessContext::new();
+    context.properties.insert(
+        super::properties::DECRYPTION_STRATEGY.name.to_string(),
+        "NOT_A_STRATEGY".to_string(),
+    );
+    context.properties.insert(
+        super::properties::SYMMETRIC_PASSWORD.name.to_string(),
+        "my_secret_password".to_string(),
+    );
+    // Must return Err, not panic.
+    let result = DecryptContentPGP::schedule(&context, &MockLogger::new());
+    assert!(result.is_err(), "expected schedule to fail on bad strategy");
+}
+
+#[derive(Copy, Clone)]
+struct PrivateKeyData {
+    key_filename: &'static str,
+    passphrase: Option<&'static str>,
+}
+
+impl PrivateKeyData {
+    fn into_controller(self) -> PGPPrivateKeyService {
+        let mut context = MockControllerServiceContext::new();
+        context
+            .properties
+            .insert("Key File", 
test_utils::get_test_key_path(self.key_filename));
+
+        if let Some(passphrase) = self.passphrase {
+            context.properties.insert("Key Passphrase", passphrase);
+        }
+
+        PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable")
+    }
+}
+
+fn test_decryption(
+    message_file_name: &str,
+    private_key_data: Option<PrivateKeyData>,
+    symmetric_password: Option<&'static str>,
+    expected_result: Result<&[u8], ()>,
+) {
+    let mut processor_context = MockProcessContext::new();
+    if let Some(private_key) = private_key_data {
+        processor_context.controller_services.insert(
+            "my_private_key_service".to_string(),
+            Box::new(private_key.into_controller()),
+        );
+        processor_context.properties.insert(
+            super::properties::PRIVATE_KEY_SERVICE.name.to_string(),
+            "my_private_key_service".to_string(),
+        );
+    }
+    if let Some(symmetric_password) = symmetric_password {
+        processor_context.properties.insert(
+            super::properties::SYMMETRIC_PASSWORD.name.to_string(),
+            symmetric_password.to_string(),
+        );
+    }
+
+    let decrypt_content = DecryptContentPGP::schedule(&processor_context, 
&MockLogger::new())
+        .expect("Should schedule with the configured properties");
+    let mut output: Vec<u8> = Vec::new();
+    let mut flow_file_stream = 
std::io::Cursor::new(get_test_message(message_file_name));
+    let res = decrypt_content
+        .transform(
+            &processor_context,
+            &mut flow_file_stream,
+            &mut output,
+            &MockLogger::new(),
+        )
+        .expect("Should be able to transform");
+
+    match expected_result {
+        Ok(_result_bytes) => {
+            assert_eq!(
+                res.target_relationship_name(),
+                super::relationships::SUCCESS.name
+            );
+            assert_eq!(res.write_status(), IoState::Ok);
+            let data_modified = res
+                .get_attribute(output_attributes::LITERAL_DATA_MODIFIED.name)
+                .unwrap()
+                .parse::<u64>()
+                .expect("Should be u64");
+            assert!(data_modified > 1770000000000);
+            assert!(data_modified < 1780000000000);
+            assert!(
+                
res.get_attribute(output_attributes::LITERAL_DATA_FILENAME.name)
+                    .is_some()
+            );
+        }
+        Err(_) => {
+            assert_eq!(
+                res.target_relationship_name(),
+                super::relationships::FAILURE.name
+            );
+            assert_eq!(res.write_status(), IoState::Cancel);
+        }
+    }
+}
+
+#[test]
+fn decrypts_with_password() {
+    test_decryption(
+        "password_encrypted_foo.gpg",
+        None,
+        Some("my_secret_password"),
+        Ok("foo\n".as_bytes()),
+    );
+    test_decryption(
+        "password_encrypted_foo.asc",
+        None,
+        Some("my_secret_password"),
+        Ok("foo\n".as_bytes()),
+    );
+    test_decryption(
+        "foo_for_alice.gpg",
+        None,
+        Some("my_secret_password"),
+        Err(()),
+    );
+    test_decryption(
+        "foo_for_alice.asc",
+        None,
+        Some("my_secret_password"),
+        Err(()),
+    );
+}
+
+#[test]
+fn decrypts_for_alice() {
+    let alice_private_key_data = PrivateKeyData {
+        key_filename: "alice_private.asc",
+        passphrase: Some("whiterabbit"),
+    };
+
+    test_decryption(
+        "foo_for_alice.asc",
+        Some(alice_private_key_data),
+        None,
+        Ok("foo\n".as_bytes()),
+    );
+
+    test_decryption(
+        "foo_for_alice.gpg",
+        Some(alice_private_key_data),
+        None,
+        Ok("foo\n".as_bytes()),
+    );
+
+    test_decryption(
+        "password_encrypted_foo.gpg",
+        Some(alice_private_key_data),
+        None,
+        Err(()),
+    );
+
+    test_decryption(
+        "password_encrypted_foo.asc",
+        Some(alice_private_key_data),
+        None,
+        Err(()),
+    );
+}
+
+#[test]
+fn decryption_of_not_encrypted_data() {
+    let alice_private_key = PrivateKeyData {
+        key_filename: "alice_private.asc",
+        passphrase: Some("whiterabbit"),
+    };
+
+    let mut processor_context = MockProcessContext::new();
+    processor_context.controller_services.insert(
+        "my_private_key_service".to_string(),
+        Box::new(alice_private_key.into_controller()),
+    );
+    processor_context.properties.insert(
+        super::properties::PRIVATE_KEY_SERVICE.name.to_string(),
+        "my_private_key_service".to_string(),
+    );
+
+    let logger = MockLogger::new();
+
+    let decrypt_content = DecryptContentPGP::schedule(&processor_context, 
&logger)
+        .expect("Should schedule without any properties");
+    let mut result: Vec<u8> = vec![];
+    let mut flow_file_stream = std::io::Cursor::new("something not 
encrypted".as_bytes());
+    let res = decrypt_content
+        .transform(
+            &processor_context,
+            &mut flow_file_stream,
+            &mut result,
+            &logger,
+        )
+        .expect("Should be able to transform");
+
+    assert_eq!(
+        res.target_relationship_name(),
+        super::relationships::FAILURE.name
+    );
+    assert_eq!(res.write_status(), IoState::Cancel);
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs
new file mode 100644
index 000000000..1c1d47d7d
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs
@@ -0,0 +1,152 @@
+use minifi_native::{
+    FlowFileStreamTransform, GetAttribute, GetControllerService, GetProperty, 
InputStream, Logger,
+    MinifiError, OutputStream, Schedule, TransformStreamResult, warn,
+};
+use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey};
+use pgp::types::StringToKey;
+use std::collections::HashMap;
+
+mod output_attributes;
+mod properties;
+mod relationships;
+
+use crate::controller_services::public_key_service::PGPPublicKeyService;
+use crate::processors::encrypt_content::output_attributes::FILE_ENCODING;
+use crate::processors::encrypt_content::properties::{
+    PASSWORD, PUBLIC_KEY_SEARCH, PUBLIC_KEY_SERVICE,
+};
+use crate::processors::encrypt_content::relationships::{FAILURE, SUCCESS};
+use minifi_native::macros::{ComponentIdentifier, PropertyType};
+use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
+
+#[derive(
+    Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
+)]
+#[strum(serialize_all = "UPPERCASE", const_into_str)]
+enum FileEncoding {
+    Ascii,
+    Binary,
+}
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct EncryptContentPGP {
+    file_encoding: FileEncoding,
+}
+
+#[cfg(not(test))]
+fn string_to_key() -> StringToKey {
+    StringToKey::new_argon2(rand::thread_rng(), 3, 4, 16) // 64 MiB with 
rpgp's recommended parameter choice
+}
+
+#[cfg(test)]
+fn string_to_key() -> StringToKey {
+    StringToKey::new_argon2(rand::thread_rng(), 1, 1, 10) // fast for unit 
tests
+}
+
+impl EncryptContentPGP {
+    fn encrypt_bytes(
+        &self,
+        input_stream: &mut dyn InputStream,
+        output_stream: &mut dyn OutputStream,
+        pub_key: Option<&SignedPublicKey>,
+        passphrase: Option<&str>,
+        file_name: String,
+    ) -> pgp::errors::Result<()> {
+        let mut builder = MessageBuilder::from_reader(file_name, 
input_stream).seipd_v1(
+            rand::thread_rng(),
+            pgp::crypto::sym::SymmetricKeyAlgorithm::AES256,
+        );
+
+        if let Some(pub_key) = pub_key {
+            builder.encrypt_to_key(rand::thread_rng(), pub_key)?;
+        }
+
+        if let Some(passphrase) = passphrase {
+            builder.encrypt_with_password(string_to_key(), 
&passphrase.into())?;
+        }
+
+        match self.file_encoding {
+            FileEncoding::Ascii => builder.to_armored_writer(
+                rand::thread_rng(),
+                ArmorOptions::default(),
+                output_stream,
+            ),
+            FileEncoding::Binary => builder.to_writer(rand::thread_rng(), 
output_stream),
+        }
+    }
+}
+
+impl Schedule for EncryptContentPGP {
+    fn schedule<P: GetProperty, L: Logger>(context: &P, _logger: &L) -> 
Result<Self, MinifiError>
+    where
+        Self: Sized,
+    {
+        let file_encoding = 
context.get_req_property::<FileEncoding>(&properties::FILE_ENCODING)?;
+
+        let has_password = 
context.get_property::<String>(&PASSWORD)?.is_some();
+        let has_public_key = context
+            .get_property::<String>(&PUBLIC_KEY_SERVICE)?
+            .is_some()
+            && context
+                .get_property::<String>(&PUBLIC_KEY_SEARCH)?
+                .is_some();
+
+        if !has_password && !has_public_key {
+            Err(MinifiError::schedule_err(
+                "Either a password or Public Key Service with Public Key 
Search should be configured to encrypt files",
+            ))
+        } else {
+            Ok(EncryptContentPGP { file_encoding })
+        }
+    }
+}
+
+impl FlowFileStreamTransform for EncryptContentPGP {
+    fn transform<Ctx: GetProperty + GetControllerService + GetAttribute, 
LoggerImpl: Logger>(
+        &self,
+        context: &Ctx,
+        input_stream: &mut dyn InputStream,
+        output_stream: &mut dyn OutputStream,
+        logger: &LoggerImpl,
+    ) -> Result<TransformStreamResult, MinifiError> {
+        let file_name = context.get_attribute("filename")?.unwrap_or_default();
+        let public_key = if let (Some(pub_key_search), 
Some(public_key_service)) = (
+            context.get_property::<String>(&PUBLIC_KEY_SEARCH)?,
+            
context.get_controller_service::<PGPPublicKeyService>(&PUBLIC_KEY_SERVICE)?,
+        ) {
+            public_key_service.get(&pub_key_search)
+        } else {
+            None
+        };
+        let password = context.get_property::<String>(&PASSWORD)?;
+        if public_key.is_none() && password.is_none() {
+            warn!(logger, "No password or public key to encrypt with");
+            return Ok(TransformStreamResult::route_without_changes(&FAILURE));
+        }
+
+        match self.encrypt_bytes(
+            input_stream,
+            output_stream,
+            public_key,
+            password.as_deref(),
+            file_name,
+        ) {
+            Ok(_) => Ok(TransformStreamResult::new(
+                &SUCCESS,
+                HashMap::from([(
+                    FILE_ENCODING.name.to_string(),
+                    self.file_encoding.to_string(),
+                )]),
+            )),
+            Err(e) => {
+                warn!(logger, "Failed to encrypt content {:?}", e);
+                Ok(TransformStreamResult::route_without_changes(&FAILURE))
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests;
+
+pub(crate) mod processor_definition;
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs
new file mode 100644
index 000000000..07f339678
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs
@@ -0,0 +1,7 @@
+use minifi_native::OutputAttribute;
+
+pub(crate) const FILE_ENCODING: OutputAttribute = OutputAttribute {
+    name: "pgp.file.encoding",
+    relationships: &["success"],
+    description: "File Encoding",
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs
new file mode 100644
index 000000000..4ac06faa7
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs
@@ -0,0 +1,20 @@
+use super::{EncryptContentPGP, output_attributes, properties, relationships};
+use minifi_native::{
+    OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, 
Relationship,
+};
+
+impl ProcessorDefinition for EncryptContentPGP {
+    const DESCRIPTION: &'static str = "Encrypt contents using OpenPGP.";
+    const INPUT_REQUIREMENT: ProcessorInputRequirement = 
ProcessorInputRequirement::Required;
+    const SUPPORTS_DYNAMIC_PROPERTIES: bool = false;
+    const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false;
+    const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = 
&[output_attributes::FILE_ENCODING];
+    const RELATIONSHIPS: &'static [Relationship] =
+        &[relationships::SUCCESS, relationships::FAILURE];
+    const PROPERTIES: &'static [Property] = &[
+        properties::FILE_ENCODING,
+        properties::PASSWORD,
+        properties::PUBLIC_KEY_SEARCH,
+        properties::PUBLIC_KEY_SERVICE,
+    ];
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs
new file mode 100644
index 000000000..70ebdb404
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs
@@ -0,0 +1,46 @@
+use crate::controller_services::public_key_service::PGPPublicKeyService;
+use crate::processors::encrypt_content::FileEncoding;
+use minifi_native::ComponentIdentifier;
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, 
NoConstraints};
+use strum::VariantNames;
+
+pub(crate) const FILE_ENCODING: Property = Property {
+    name: "File Encoding",
+    description: "File Encoding for encryption",
+    is_required: true,
+    is_sensitive: false,
+    supports_expr_lang: false,
+    default_value: Some(FileEncoding::Binary.into_str()),
+    constraints: AllowedValues(FileEncoding::VARIANTS),
+};
+
+pub(crate) const PASSWORD: Property = Property {
+    name: "Symmetric Password",
+    description: "Password used for encrypting data with Password-Based 
Encryption",
+    is_required: false,
+    is_sensitive: true,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const PUBLIC_KEY_SEARCH: Property = Property {
+    name: "Public Key Search",
+    description: "PGP Public Key Search will be used to match against the User 
ID or Key ID when formatted as uppercase hexadecimal string of 16 characters",
+    is_required: false,
+    is_sensitive: false,
+    supports_expr_lang: true,
+    default_value: None,
+    constraints: NoConstraints,
+};
+
+pub(crate) const PUBLIC_KEY_SERVICE: Property = Property {
+    name: "Public Key Service",
+    description: "PGP Public Key Service for encrypting data with Public Key 
Encryption",
+    is_required: false,
+    is_sensitive: false,
+    supports_expr_lang: false,
+    default_value: None,
+    constraints: AllowedType(PGPPublicKeyService::CLASS_NAME),
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs
 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs
new file mode 100644
index 000000000..fcdda9921
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs
@@ -0,0 +1,11 @@
+use minifi_native::Relationship;
+
+pub(crate) const SUCCESS: Relationship = Relationship {
+    name: "success",
+    description: "Encryption Succeeded",
+};
+
+pub(crate) const FAILURE: Relationship = Relationship {
+    name: "failure",
+    description: "Encryption Failed",
+};
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs
new file mode 100644
index 000000000..2e961dd7e
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs
@@ -0,0 +1,137 @@
+use super::*;
+use crate::test_utils;
+use minifi_native::{
+    ComponentIdentifier, EnableControllerService, IoState, 
MockControllerServiceContext,
+    MockLogger, MockProcessContext,
+};
+
+#[test]
+fn test_ids() {
+    assert_eq!(
+        EncryptContentPGP::CLASS_NAME,
+        "minifi_pgp::processors::encrypt_content::EncryptContentPGP"
+    );
+    assert_eq!(EncryptContentPGP::GROUP_NAME, "minifi_pgp");
+    assert_eq!(EncryptContentPGP::VERSION, "0.1.0");
+}
+
+#[test]
+fn cannot_schedule_without_password_or_public_key() {
+    assert!(EncryptContentPGP::schedule(&MockProcessContext::new(), 
&MockLogger::new()).is_err());
+}
+
+fn assert_content(transform_result: &TransformStreamResult, is_ascii: bool) {
+    assert_eq!(transform_result.target_relationship_name(), SUCCESS.name);
+    assert_eq!(transform_result.write_status(), IoState::Ok);
+    assert_eq!(
+        transform_result.get_attribute("pgp.file.encoding").unwrap(),
+        if is_ascii { "ASCII" } else { "BINARY" }
+    );
+}
+
+#[test]
+fn encrypts_via_passphrase() {
+    let mut context = MockProcessContext::new();
+    context.properties.insert(PASSWORD.name, "password");
+    context
+        .attributes
+        .insert("filename".to_owned(), "mammut".to_owned());
+
+    let mut result: Vec<u8> = Vec::new();
+    let mut input_stream = std::io::Cursor::new("foo".as_bytes());
+    let processor =
+        EncryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+    let transformed_ff = processor
+        .transform(&context, &mut input_stream, &mut result, 
&MockLogger::new())
+        .expect("should transform");
+
+    assert!(!result.is_ascii());
+    assert_content(&transformed_ff, false);
+}
+
+fn public_key_service() -> PGPPublicKeyService {
+    let mut context = MockControllerServiceContext::new();
+    context.properties.insert(
+        "Keyring File".to_string(),
+        test_utils::get_test_key_path("keyring.asc"),
+    );
+
+    PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("should 
enable")
+}
+
+#[test]
+fn encrypts_ascii_for_alice() {
+    let mut context = MockProcessContext::new();
+    context.properties.extend([
+        ("Public Key Service", "my_controller_service"),
+        ("Public Key Search", "Alice"),
+        ("File Encoding", "ASCII"),
+    ]);
+
+    context.controller_services.insert(
+        "my_controller_service".to_string(),
+        Box::new(public_key_service()),
+    );
+
+    let mut result: Vec<u8> = Vec::new();
+    let mut input_stream = std::io::Cursor::new("foo".as_bytes());
+    let processor =
+        EncryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+    let transformed_ff = processor
+        .transform(&context, &mut input_stream, &mut result, 
&MockLogger::new())
+        .expect("should transform");
+
+    assert!(result.is_ascii());
+    assert_content(&transformed_ff, true);
+}
+
+#[test]
+fn encrypts_binary_for_bob() {
+    let mut context = MockProcessContext::new();
+    context.properties.extend([
+        ("Public Key Service", "my_controller_service"),
+        ("Public Key Search", "Bob"),
+        ("File Encoding", "BINARY"),
+    ]);
+
+    context.controller_services.insert(
+        "my_controller_service".to_string(),
+        Box::new(public_key_service()),
+    );
+
+    let mut result: Vec<u8> = Vec::new();
+    let mut input_stream = std::io::Cursor::new("foo".as_bytes());
+    let processor =
+        EncryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+    let transformed_ff = processor
+        .transform(&context, &mut input_stream, &mut result, 
&MockLogger::new())
+        .expect("should transform");
+
+    assert!(!result.is_ascii());
+    assert_content(&transformed_ff, false);
+}
+
+#[test]
+fn cannot_encrypt_for_carol() {
+    let mut context = MockProcessContext::new();
+    context.properties.extend([
+        ("Public Key Service", "my_controller_service"),
+        ("Public Key Search", "Carol"),
+    ]);
+
+    context.controller_services.insert(
+        "my_controller_service".to_string(),
+        Box::new(public_key_service()),
+    );
+
+    let mut result: Vec<u8> = Vec::new();
+    let mut input_stream = std::io::Cursor::new("foo".as_bytes());
+    let processor =
+        EncryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+    let transformed_ff = processor
+        .transform(&context, &mut input_stream, &mut result, 
&MockLogger::new())
+        .expect("should transform");
+
+    assert_eq!(transformed_ff.target_relationship_name(), FAILURE.name);
+    assert_eq!(transformed_ff.write_status(), IoState::Cancel);
+}
diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs
new file mode 100644
index 000000000..ac45357fe
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod decrypt_content;
+pub(crate) mod encrypt_content;
diff --git a/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs 
b/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs
new file mode 100644
index 000000000..963af7161
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs
@@ -0,0 +1,15 @@
+use std::path::PathBuf;
+
+pub fn get_test_key_path(filename: &str) -> String {
+    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+    path.push("test_keys");
+    path.push(filename);
+    path.display().to_string()
+}
+
+pub fn get_test_message(filename: &str) -> Vec<u8> {
+    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+    path.push("test_messages");
+    path.push(filename);
+    std::fs::read(path).expect("test message should be readable")
+}
diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs 
b/minifi_rust/extensions/minifi_pgp/src/utils.rs
new file mode 100644
index 000000000..95b346238
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs
@@ -0,0 +1,11 @@
+use minifi_native::{MinifiError, PropertyType};
+
+pub(crate) struct Password {}
+
+impl PropertyType for Password {
+    type Output = pgp::types::Password;
+
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        Ok(pgp::types::Password::from(s))
+    }
+}
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt 
b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt
new file mode 100644
index 000000000..7b8ad1a61
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt
@@ -0,0 +1,8 @@
+Testing keys v2
+------------------------
+uid           [ultimate] Alice <[email protected]>
+passphrase    whiterabbit
+
+uid           [ultimate] Bob Personal <[email protected]>
+uid           [ultimate] Bob Primary <[email protected]>
+no passphrase
\ No newline at end of file
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc
new file mode 100644
index 000000000..8ae976d7b
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc
@@ -0,0 +1,50 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+q+zIM+a7/I/TcOmDRO5s
+=EEA6
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg
new file mode 100644
index 000000000..71af43989
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc
new file mode 100644
index 000000000..955e52584
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc
@@ -0,0 +1,92 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=6nxq
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg
new file mode 100644
index 000000000..5f385d9ee
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc
new file mode 100644
index 000000000..09fc1bc99
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc
@@ -0,0 +1,71 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=ePbg
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg
new file mode 100644
index 000000000..a39370243
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg
new file mode 100644
index 000000000..9e603b5c3
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc
new file mode 100644
index 000000000..0fa816a55
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc
@@ -0,0 +1,89 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=fkCr
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg
new file mode 100644
index 000000000..6a84506f9
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc
new file mode 100644
index 000000000..6430facf8
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc
@@ -0,0 +1,159 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=f4lJ
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg
new file mode 100644
index 000000000..4a07229aa
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc
new file mode 100644
index 000000000..d25ddfb62
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc
@@ -0,0 +1,10 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBGmJ4OIBCACz9RXNN6lFaUi0b4V6PTyjc27g9G0OCBMy6H/lcjROMGupqPm1
+9QzEzTIrxkc1LlPx31qzb6SwzQWkKiDnmObcZzG43Yiz1aD0YOqJsHBb9klrdWFx
+VbGTtaDmZg/xAS+VseYTijiucydURPzIKDb25vWl7r+iAdhZY3eo8Zif7g7LDpU6
+hsqAQOVgIGCokbbS4GFTeOIl6uwS1Gchq40vY5AM7o4/AObANNstyROgQrQqq19Y
+QEjnLT6GsxF6jpbrcb+8No6JWJSaqhDjIVug+psaeuqruQkN6o3B85izGk0fu4QD
+kSYGW3/A9ArGrLhGMtFnTyo/fg9sEGqWxLoJABEBAAG0GUFsaWNlIDxhbGljZUBl
+eGFtcGxlLmNvbT6JAVIEEwEIADwWIQQR1fT4Ba73eK2U4NIbsOxL81Ml9gUCaYng
+4gIbLwU
\ No newline at end of file
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc
new file mode 100644
index 000000000..9cc58539a
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQPGBGmJ4OIBCACz9RXNN6lFaUi0b4V6PTyjc27g9G0OCBMy6H/lcjROMGupqPm1
+9QzEzTIrxkc1LlPx31qzb6SwzQWkKiDnmObcZzG43Yiz1aD0YOqJsHBb9klrdWFx
+VbGTtaDmZg/xAS+VseYTijiucydURPzIKDb25vWl7r+iAdhZY3eo8Zif7g7LDpU6
+hsqAQOVgIGCokbbS4GFTeOIl6uwS1Gchq40vY5AM7o4/AObANNstyROgQrQqq19Y
+QEjnLT6GsxF6jpbrcb+8No6JWJSaqhDjIVug+psaeuqruQkN6o3B85izGk0fu4QD
+kSYGW3/A9ArGrLhGMtFnTyo/fg9sEGqWxLoJABEBAAH+BwMCYkH6w6nO395gtdQ6
+zQJvZ1itO9NCbRtI20iVkqWmwtr2FwgN8AZ9sGZss6zdpfxh85Ef1kHvP1nkbedk
+/8OmBljPooqKB7MTwCCxOC53Mf6wNMijlBYsY8YUyi4dwaHoxnDFnaCeITSHHehY
+07ifnInvrTkbJ41JzfP124xQ804voehm7merA91Vtpvg/hoYqJ/Sxo22UpTwvuw/
+aKqoetlJWqRk8VmBpcuuVFYcF9jaOPB51WG8fRDj66eINg2zXL49WRvwlUtbAHvS
+cbglkBzMFHqljx0KJWX/QMO64X894eFafVFvSiYf+fn80wv9h7IKjj413itlbF0r
+X+DckGQ9b50XAD3kZgDOMr5dKTGQ9ytChl7hpy38ucQqJM0qRlTSG2mp05qBO+XZ
+CNNE7qvVNJNP5nD/3xkWD8oi+nhmqqg4bZ/QDUcoTVrWW7L1er2fgREJg7xL7xuD
+QYu/T0A6N9PxWefYdEN/jjcLqks/Pjdy3DfGtlDysj88GpLh3diNgQ2EnNgQ32pq
+JmxwEWIQ4VV30Ms1D0Uh7g4Ksq0lq1/LjOll3FSyjr3Ihpesopj
\ No newline at end of file
diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc 
b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc
new file mode 100644
index 000000000..94b8561c9
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP MESSAGE-----
+
+hQEMA7zOP9+6AZ1+AQf/WlAEDriFTKHJfn5KXAi123WGeDJBoRi/etl7GJ8MO5+8
+crdou58wMcqRJ8u3wNgKWDYm+QknLhQK5+3dJajwQeKH18uruTkEmFQB/wArHsOX
+62UhFf2qbAzvUuTH5kPyt1d/Wt51T9+K/xlEPJr+DiK0uHlXZPu7rEnqk9pcKikC
+/dYAuljnkNigDoykHwEBRcBfQu5t/hIe/Bii3wTZPm2w0YneyjOtd7Yq3mDlfmDW
+cy3bdDjuwP4npCxcnHi7WkbElTyCJMybKVwwLjugihGI+4r8itO2wknAT5GDGMQ8
+u2FfOfGnIYTK2mBAQgyM7gtBX2qS28uWYlj5gehngdRJAQkCEFMqwWDAaUXAi6QU
+xhn/O+wEEpUVYnGuEpIqG9KTW3qYl+vTxLkeNzg2NL255QP9gLbdlKFJYkC60c4I
+JJXhkwkPTSC+Xw==
+=UAju
+-----END PGP MESSAGE-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg 
b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg
new file mode 100644
index 000000000..bd3d9b476
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg differ
diff --git 
a/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc 
b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc
new file mode 100644
index 000000000..22d639e71
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc
@@ -0,0 +1,6 @@
+-----BEGIN PGP MESSAGE-----
+
+jA0ECQMCU2B2LnRTkyNg0jkBhgVPotvo6S9iLOTWhzglgsjR/6QB2v7vUNImzkh7
+fjhd17fG5tjB1RPRgW3bR12BidV6TQKuwLs=
+=AXoJ
+-----END PGP MESSAGE-----
diff --git 
a/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg 
b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg
new file mode 100644
index 000000000..40ab6a354
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg
@@ -0,0 +1 @@
+�     ��t�?`�9���B�2;��Œ����g]N)�!r2�!F���nl./��a�9._�}_2�,�ɴ��
\ No newline at end of file
diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.alpine 
b/minifi_rust/minifi_rs_behave/Dockerfile.alpine
index 032c26ec5..e3c155779 100644
--- a/minifi_rust/minifi_rs_behave/Dockerfile.alpine
+++ b/minifi_rust/minifi_rs_behave/Dockerfile.alpine
@@ -22,4 +22,4 @@ RUN cargo build --release
 
 # Export Stage
 FROM scratch AS bin-export
-COPY --from=builder /app/target/release/libminifi_rs_playground.so /
+COPY --from=builder /app/target/release/libminifi_*.so /
diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.debian 
b/minifi_rust/minifi_rs_behave/Dockerfile.debian
index 274ed3cb5..a3c4aa03d 100644
--- a/minifi_rust/minifi_rs_behave/Dockerfile.debian
+++ b/minifi_rust/minifi_rs_behave/Dockerfile.debian
@@ -22,4 +22,4 @@ RUN cargo build --release
 
 # Export Stage
 FROM scratch AS bin-export
-COPY --from=builder /app/target/release/libminifi_rs_playground.so /
+COPY --from=builder /app/target/release/libminifi_*.so /

Reply via email to