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


The following commit(s) were added to refs/heads/minifi_rust_pgp by this push:
     new 6adb638ed fixing opus 5's findings
6adb638ed is described below

commit 6adb638ed3f305e5ad267503f1bed72d1b3f6df5
Author: Martin Zink <[email protected]>
AuthorDate: Wed Sep 23 16:57:53 2026 +0200

    fixing opus 5's findings
    
    -support for encrypting when the encryption key is a sub-key and not 
primary key
    -fixing ambigious key search
    -allow multiple key password (new line delimited)
    -lazily evaulate get_id
    -import add_extension_to_minifi_container after rebase and remove 
duplicated impl
    -dry fixes for parse::impls
    -test keys regenerated for more extensive testing
---
 .../ubuntu_22_04_clang_arm_manifest.json           |   2 +-
 CONTROLLERS.md                                     |   2 +-
 .../extensions/minifi_pgp/features/environment.py  |  53 +----
 minifi_rust/extensions/minifi_pgp/minifi_pgp.md    |  10 +-
 .../src/controller_services/encryption_key.rs      | 131 +++++++++++
 .../src/controller_services/key_file_property.rs   |  31 +--
 .../src/controller_services/key_lookup.rs          | 114 ++++++++--
 .../src/controller_services/key_parsing.rs         |  90 ++++++++
 .../src/controller_services/key_property.rs        |  25 +-
 .../minifi_pgp/src/controller_services/mod.rs      |   2 +
 .../src/controller_services/private_key_service.rs |  94 ++++----
 .../src/controller_services/public_key_service.rs  | 130 +++++++----
 .../minifi_pgp/src/processors/decrypt_content.rs   | 112 +++++++++
 .../minifi_pgp/src/processors/encrypt_content.rs   |  26 ++-
 minifi_rust/extensions/minifi_pgp/src/utils.rs     |  43 ++++
 .../extensions/minifi_pgp/test_keys/README.txt     |  51 ++++-
 .../extensions/minifi_pgp/test_keys/alice.asc      |  62 ++---
 .../extensions/minifi_pgp/test_keys/alice.gpg      | Bin 2175 -> 640 bytes
 .../minifi_pgp/test_keys/alice_private.asc         | 117 +++-------
 .../minifi_pgp/test_keys/alice_private.gpg         | Bin 4220 -> 1291 bytes
 .../{keyring.gpg => ambiguous_keyring.gpg}         | Bin 4080 -> 3227 bytes
 .../extensions/minifi_pgp/test_keys/dave.asc       |  14 ++
 .../minifi_pgp/test_keys/dave_private.asc          |  18 ++
 .../extensions/minifi_pgp/test_keys/erin.asc       |  10 +
 .../extensions/minifi_pgp/test_keys/keyring.asc    | 141 +++++-------
 .../extensions/minifi_pgp/test_keys/keyring.gpg    | Bin 4080 -> 2545 bytes
 .../minifi_pgp/test_keys/mixed_secret_keyring.gpg  | Bin 0 -> 1920 bytes
 .../minifi_pgp/test_keys/secret_keyring.asc        | 251 ++++++++-------------
 .../minifi_pgp/test_keys/secret_keyring.gpg        | Bin 7427 -> 4498 bytes
 .../minifi_pgp/test_keys/spoofed_bob.asc           |  19 ++
 30 files changed, 934 insertions(+), 614 deletions(-)

diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json 
b/.github/references/ubuntu_22_04_clang_arm_manifest.json
index f9b4df62b..ed32c5ff7 100644
--- a/.github/references/ubuntu_22_04_clang_arm_manifest.json
+++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json
@@ -12239,7 +12239,7 @@
                         },
                         "Key Password": {
                             "name": "Key Password",
-                            "description": "Password used for decrypting 
Private Keys",
+                            "description": "Password used for decrypting 
Private Keys. Multiple passwords may be supplied one per line, each of them is 
tried in turn",
                             "validator": "NON_BLANK_VALIDATOR",
                             "required": "false",
                             "sensitive": "true",
diff --git a/CONTROLLERS.md b/CONTROLLERS.md
index 22a56add6..af57c16d1 100644
--- a/CONTROLLERS.md
+++ b/CONTROLLERS.md
@@ -261,7 +261,7 @@ In the list below, the names of required properties appear 
in bold. Any other pr
 
|--------------|---------------|------------------|---------------------------------------------------------------------------------------------------------|
 | Key          |               |                  | Secret Key encoded in 
ASCII Armor<br/>**Sensitive Property: true**                                    
  |
 | Key File     |               |                  | File path to PGP Secret 
Key encoded in binary or ASCII Armor<br/>**Supports Expression Language: true** 
|
-| Key Password |               |                  | Password used for 
decrypting Private Keys<br/>**Sensitive Property: true**                        
      |
+| Key Password |               |                  | Password used for 
decrypting Private Keys. Multiple passwords may be supplied one per line, each 
of them is tried in turn<br/>**Sensitive Property: true**                       
       |
 
 
 ## PGPPublicKeyService
diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py 
b/minifi_rust/extensions/minifi_pgp/features/environment.py
index d24d3e2db..4652c748a 100644
--- a/minifi_rust/extensions/minifi_pgp/features/environment.py
+++ b/minifi_rust/extensions/minifi_pgp/features/environment.py
@@ -18,54 +18,11 @@
 import os
 from pathlib import Path
 
-from minifi_behave.containers.docker_image_builder import DockerImageBuilder
-from minifi_behave.core.hooks import common_after_scenario, 
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
+from minifi_behave.core.hooks import (
+    add_extension_to_minifi_container,
+    common_after_scenario,
+    common_before_scenario,
+)
 
 
 def before_all(context):
diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md 
b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md
index e0c6a0b9d..5898d2519 100644
--- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md
+++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md
@@ -89,11 +89,11 @@ PGP Private Key Service provides Private Keys loaded from 
files or 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          |               |                  | Secret Key encoded in 
ASCII Armor<br/>**Sensitive Property: true**                                    
  |
-| Key File     |               |                  | File path to PGP Secret 
Key encoded in binary or ASCII Armor<br/>**Supports Expression Language: true** 
|
-| Key Password |               |                  | Password used for 
decrypting Private Keys<br/>**Sensitive Property: true**                        
      |
+| Name         | Default Value | Allowable Values | Description                
                                                                                
                                                |
+|--------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Key          |               |                  | Secret Key encoded in 
ASCII Armor<br/>**Sensitive Property: true**                                    
                                                     |
+| Key File     |               |                  | File path to PGP Secret 
Key encoded in binary or ASCII Armor<br/>**Supports Expression Language: true** 
                                                   |
+| Key Password |               |                  | Password used for 
decrypting Private Keys. Multiple passwords may be supplied one per line, each 
of them is tried in turn<br/>**Sensitive Property: true** |
 
 
 ## PGPPublicKeyService
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs
new file mode 100644
index 000000000..173f7d436
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs
@@ -0,0 +1,131 @@
+// 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
+//
+//   https://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.
+
+use minifi_native::MinifiError;
+use pgp::composed::{SignedPublicKey, SignedPublicSubKey};
+use pgp::packet::{PublicKey, SignatureType};
+use pgp::types::KeyDetails;
+
+/// The component key of a certificate that a message should actually be 
encrypted to.
+///
+/// `SignedPublicKey`'s own `EncryptionKey` implementation always uses the 
primary key and
+/// ignores subkeys, which fails for the layout `gpg --gen-key` produces 
nowadays: a sign-only
+/// primary key (Ed25519) plus a dedicated encryption subkey (Cv25519).
+#[derive(Debug)]
+pub(crate) enum EncryptionTarget<'a> {
+    Primary(&'a PublicKey),
+    Subkey(&'a SignedPublicSubKey),
+}
+
+/// Picks the key of `certificate` to encrypt to.
+///
+/// Encryption subkeys are preferred, newest first, the way GnuPG picks them; 
the primary key is
+/// only used when the certificate has no usable encryption subkey.
+pub(crate) fn select_encryption_target(
+    certificate: &SignedPublicKey,
+) -> Result<EncryptionTarget<'_>, MinifiError> {
+    let newest_encryption_subkey = certificate
+        .public_subkeys
+        .iter()
+        .filter(|subkey| is_encryption_subkey(subkey))
+        .max_by_key(|subkey| subkey.created_at());
+
+    if let Some(subkey) = newest_encryption_subkey {
+        return Ok(EncryptionTarget::Subkey(subkey));
+    }
+
+    if certificate.primary_key.algorithm().can_encrypt() {
+        return Ok(EncryptionTarget::Primary(&certificate.primary_key));
+    }
+
+    Err(MinifiError::custom(format!(
+        "Key {} cannot be used for encryption, it has no encryption subkey and 
its primary key is {:?} which cannot encrypt",
+        certificate.primary_key.fingerprint(),
+        certificate.primary_key.algorithm()
+    )))
+}
+
+fn is_encryption_subkey(subkey: &SignedPublicSubKey) -> bool {
+    if !subkey.key.algorithm().can_encrypt() {
+        return false;
+    }
+
+    let is_revoked = subkey
+        .signatures
+        .iter()
+        .any(|signature| signature.typ() == 
Some(SignatureType::SubkeyRevocation));
+    if is_revoked {
+        return false;
+    }
+
+    subkey
+        .signatures
+        .iter()
+        .filter(|signature| signature.typ() == 
Some(SignatureType::SubkeyBinding))
+        .any(|signature| {
+            let key_flags = signature.key_flags();
+            key_flags.encrypt_comms() || key_flags.encrypt_storage()
+        })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::get_test_key_path;
+    use pgp::composed::Deserializable;
+
+    fn load_certificate(file_name: &str) -> SignedPublicKey {
+        let (certificate, _headers) =
+            
SignedPublicKey::from_armor_file(get_test_key_path(file_name)).unwrap();
+        certificate
+    }
+
+    #[test]
+    fn rsa_primary_key_is_used_when_there_is_no_encryption_subkey() {
+        // alice.asc is an RSA key whose primary key carries the encrypt 
capability itself.
+        let certificate = load_certificate("alice.asc");
+        assert!(certificate.primary_key.algorithm().can_encrypt());
+        assert!(matches!(
+            select_encryption_target(&certificate).unwrap(),
+            EncryptionTarget::Primary(_)
+        ));
+    }
+
+    #[test]
+    fn encryption_subkey_is_preferred_over_a_sign_only_primary_key() {
+        // dave.asc has an Ed25519 sign-only primary key and a Cv25519 
encryption subkey,
+        // the layout `gpg --gen-key` produces by default.
+        let certificate = load_certificate("dave.asc");
+        assert!(!certificate.primary_key.algorithm().can_encrypt());
+
+        let target = select_encryption_target(&certificate).unwrap();
+        let EncryptionTarget::Subkey(subkey) = target else {
+            panic!("expected the encryption subkey to be selected");
+        };
+        assert!(subkey.key.algorithm().can_encrypt());
+    }
+
+    #[test]
+    fn sign_only_key_without_encryption_subkey_is_rejected() {
+        // erin.asc is an Ed25519 sign-only primary key with no subkeys at all.
+        let certificate = load_certificate("erin.asc");
+        let err = select_encryption_target(&certificate)
+            .unwrap_err()
+            .to_string();
+        assert!(err.contains("cannot be used for encryption"), "{err}");
+    }
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs
 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs
index 3f59bc357..d367d404f 100644
--- 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs
+++ 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs
@@ -15,8 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::controller_services::key_parsing::{KeyKind, parse_key_file};
 use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, 
PropertyType};
-use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
+use pgp::composed::{SignedPublicKey, SignedSecretKey};
 
 pub(crate) struct SecretKeyFile {}
 
@@ -29,19 +30,7 @@ impl PropertyType for SecretKeyFile {
     type Output = Vec<SignedSecretKey>;
 
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
-        let mut result: Vec<SignedSecretKey> = Vec::new();
-        if let Ok((keys, _headers)) = SignedSecretKey::from_armor_file_many(s) 
{
-            result.extend(keys.filter_map(Result::ok));
-        } else if let Ok(keys) = SignedSecretKey::from_file_many(s) {
-            result.extend(keys.filter_map(Result::ok));
-        }
-        if result.is_empty() {
-            Err(MinifiError::validation(
-                "Couldn't load any valid secret keys",
-            ))
-        } else {
-            Ok(result)
-        }
+        parse_key_file(s, KeyKind::Secret)
     }
 }
 
@@ -55,19 +44,7 @@ impl PropertyType for PublicKeyFile {
     type Output = Vec<SignedPublicKey>;
 
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
-        let mut result: Vec<SignedPublicKey> = Vec::new();
-        if let Ok((keys, _headers)) = SignedPublicKey::from_armor_file_many(s) 
{
-            result.extend(keys.filter_map(Result::ok));
-        } else if let Ok(keys) = SignedPublicKey::from_file_many(s) {
-            result.extend(keys.filter_map(Result::ok));
-        }
-        if result.is_empty() {
-            Err(MinifiError::validation(
-                "Couldn't load any valid public keys",
-            ))
-        } else {
-            Ok(result)
-        }
+        parse_key_file(s, KeyKind::Public)
     }
 }
 
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
index 9205ff0a7..26481bac9 100644
--- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs
@@ -15,34 +15,80 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use minifi_native::MinifiError;
 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 {
+fn matches_key_id(key_id: &KeyId, target: &str) -> bool {
+    key_id.to_string().eq_ignore_ascii_case(target)
+}
+
+fn matches_user_id(details: &SignedKeyDetails, target_lower: &str) -> bool {
+    details.users.iter().any(|user| {
+        user.id
+            .as_str()
+            .map(|user_id| user_id.to_ascii_lowercase().contains(target_lower))
+            .unwrap_or(false)
+    })
+}
+
+pub(crate) fn find_unique_key<'a, K, F>(
+    keys: &'a [K],
+    target_id: &str,
+    key_parts: F,
+) -> Result<&'a K, MinifiError>
+where
+    F: Fn(&'a K) -> (KeyId, &'a SignedKeyDetails),
+{
     let target = target_id.trim();
     if target.is_empty() {
-        return false;
+        return Err(MinifiError::custom("No key search string was given"));
     }
 
-    if key_id.to_string().eq_ignore_ascii_case(target) {
-        return true;
+    if let Some(key) = keys
+        .iter()
+        .find(|key| matches_key_id(&key_parts(key).0, target))
+    {
+        return Ok(key);
     }
 
     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)
-    })
+    let mut matches = keys
+        .iter()
+        .filter(|key| matches_user_id(key_parts(key).1, &target_lower));
+
+    let Some(first_match) = matches.next() else {
+        return Err(MinifiError::custom(format!(
+            "No key matching '{target}' was found"
+        )));
+    };
+
+    let ambiguous: Vec<String> = std::iter::once(first_match)
+        .chain(matches)
+        .map(|key| key_parts(key).0.to_string())
+        .collect();
+    if ambiguous.len() > 1 {
+        return Err(MinifiError::custom(format!(
+            "'{target}' is ambiguous, it matches {} keys: {}",
+            ambiguous.len(),
+            ambiguous.join(", ")
+        )));
+    }
+
+    Ok(first_match)
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pgp::composed::SignedKeyDetails;
+
+    /// A stand-in for a key: just the parts `find_unique_key` looks at.
+    #[derive(Debug)]
+    struct TestKey {
+        key_id: KeyId,
+        details: SignedKeyDetails,
+    }
 
     fn key_id_from_hex(hex: &str) -> KeyId {
         let mut bytes = [0u8; 8];
@@ -52,22 +98,44 @@ mod tests {
         KeyId::from(bytes)
     }
 
+    fn find<'a>(keys: &'a [TestKey], target: &str) -> Result<&'a TestKey, 
MinifiError> {
+        find_unique_key(keys, target, |key| (key.key_id, &key.details))
+    }
+
+    fn no_details() -> SignedKeyDetails {
+        SignedKeyDetails::new(vec![], vec![], vec![], vec![])
+    }
+
     #[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, "   "));
+        let keys = [TestKey {
+            key_id: key_id_from_hex("1122334455667788"),
+            details: no_details(),
+        }];
+        assert!(find(&keys, "").is_err());
+        assert!(find(&keys, "   ").is_err());
     }
 
     #[test]
     fn matches_key_id_case_insensitive() {
-        let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]);
-        let key_id = key_id_from_hex("11ABcdEF33445566");
+        let keys = [TestKey {
+            key_id: key_id_from_hex("11ABcdEF33445566"),
+            details: no_details(),
+        }];
 
-        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"));
+        assert!(find(&keys, "11abcdef33445566").is_ok());
+        assert!(find(&keys, "11ABCDEF33445566").is_ok());
+        assert!(find(&keys, "11abcdef3344556").is_err()); // 15 chars
+        assert!(find(&keys, "abcdef33445566").is_err());
+    }
+
+    #[test]
+    fn missing_key_reports_the_search_string() {
+        let keys = [TestKey {
+            key_id: key_id_from_hex("1122334455667788"),
+            details: no_details(),
+        }];
+        let err = find(&keys, "99aabbccddeeff00").unwrap_err().to_string();
+        assert!(err.contains("99aabbccddeeff00"), "{err}");
     }
 }
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs
new file mode 100644
index 000000000..70acdcb27
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs
@@ -0,0 +1,90 @@
+// 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
+//
+//   https://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.
+
+use minifi_native::{GetProperty, MinifiError, Property, PropertyType};
+use pgp::composed::Deserializable;
+
+/// Describes which flavour of key is being loaded, used for error messages 
only.
+#[derive(Copy, Clone)]
+pub(crate) enum KeyKind {
+    Public,
+    Secret,
+}
+
+impl KeyKind {
+    fn no_valid_keys(self) -> MinifiError {
+        match self {
+            KeyKind::Public => MinifiError::validation("Couldn't load any 
valid public keys"),
+            KeyKind::Secret => MinifiError::validation("Couldn't load any 
valid secret keys"),
+        }
+    }
+}
+
+/// Parses every key found in ASCII Armored `input`.
+pub(crate) fn parse_armored_keys<T: Deserializable>(
+    input: &str,
+    kind: KeyKind,
+) -> Result<Vec<T>, MinifiError> {
+    let mut keys: Vec<T> = Vec::new();
+    if let Ok((parsed, _headers)) = T::from_armor_many(input.as_bytes()) {
+        keys.extend(parsed.filter_map(Result::ok));
+    }
+    non_empty(keys, kind)
+}
+
+/// Parses every key found in the file at `path`, which may be ASCII Armored 
or binary.
+pub(crate) fn parse_key_file<T: Deserializable>(
+    path: &str,
+    kind: KeyKind,
+) -> Result<Vec<T>, MinifiError> {
+    let mut keys: Vec<T> = Vec::new();
+    if let Ok((parsed, _headers)) = T::from_armor_file_many(path) {
+        keys.extend(parsed.filter_map(Result::ok));
+    } else if let Ok(parsed) = T::from_file_many(path) {
+        keys.extend(parsed.filter_map(Result::ok));
+    }
+    non_empty(keys, kind)
+}
+
+/// Loads the keys of a controller service from its file property and its 
inline property,
+/// failing when neither yields a usable key.
+pub(crate) fn load_service_keys<T, File, Inline, Ctx>(
+    context: &Ctx,
+    file_property: &Property<Option<File>>,
+    inline_property: &Property<Option<Inline>>,
+) -> Result<Vec<T>, MinifiError>
+where
+    Ctx: GetProperty,
+    File: PropertyType<Output = Vec<T>>,
+    Inline: PropertyType<Output = Vec<T>>,
+{
+    let mut keys = context.get_property(file_property)?.unwrap_or_default();
+    keys.extend(context.get_property(inline_property)?.unwrap_or_default());
+
+    if keys.is_empty() {
+        return Err(MinifiError::validation("Could not load any valid keys"));
+    }
+    Ok(keys)
+}
+
+fn non_empty<T>(keys: Vec<T>, kind: KeyKind) -> Result<Vec<T>, MinifiError> {
+    if keys.is_empty() {
+        Err(kind.no_valid_keys())
+    } else {
+        Ok(keys)
+    }
+}
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs
index 62fa932e0..fc5c629a2 100644
--- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs
@@ -15,8 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::controller_services::key_parsing::{KeyKind, parse_armored_keys};
 use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, 
PropertyType};
-use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
+use pgp::composed::{SignedPublicKey, SignedSecretKey};
 
 pub(crate) struct SecretKey {}
 
@@ -29,16 +30,7 @@ impl PropertyType for SecretKey {
     type Output = Vec<SignedSecretKey>;
 
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
-        let mut secret_keys: Vec<SignedSecretKey> = Vec::new();
-        if let Ok((keys, _headers)) = 
SignedSecretKey::from_armor_many(s.as_bytes()) {
-            secret_keys.extend(keys.filter_map(Result::ok));
-        }
-        if secret_keys.is_empty() {
-            return Err(MinifiError::validation(
-                "Couldn't load any valid secret keys",
-            ));
-        }
-        Ok(secret_keys)
+        parse_armored_keys(s, KeyKind::Secret)
     }
 }
 
@@ -52,15 +44,6 @@ impl PropertyType for PublicKey {
     type Output = Vec<SignedPublicKey>;
 
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
-        let mut public_keys: Vec<SignedPublicKey> = Vec::new();
-        if let Ok((keys, _headers)) = 
SignedPublicKey::from_armor_many(s.as_bytes()) {
-            public_keys.extend(keys.filter_map(Result::ok));
-        }
-        if public_keys.is_empty() {
-            return Err(MinifiError::validation(
-                "Couldn't load any valid public keys",
-            ));
-        }
-        Ok(public_keys)
+        parse_armored_keys(s, KeyKind::Public)
     }
 }
diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs 
b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs
index bf3fe01e3..3aa314bc0 100644
--- a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs
@@ -15,8 +15,10 @@
 // specific language governing permissions and limitations
 // under the License.
 
+pub(crate) mod encryption_key;
 mod key_file_property;
 mod key_lookup;
+mod key_parsing;
 mod key_property;
 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
index 0fab61fb2..cf363da2b 100644
--- 
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
@@ -16,7 +16,8 @@
 // under the License.
 
 #[cfg(test)]
-use crate::controller_services::key_lookup::key_matches;
+use crate::controller_services::key_lookup::find_unique_key;
+use crate::controller_services::key_parsing::load_service_keys;
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError};
 use pgp::composed::{SignedSecretKey, TheRing};
@@ -27,7 +28,7 @@ use service_def::*;
 #[derive(Debug, ComponentIdentifier)]
 pub(crate) struct PGPPrivateKeyService {
     private_keys: Vec<SignedSecretKey>,
-    passphrase: pgp::types::Password,
+    passphrases: Vec<pgp::types::Password>,
 }
 
 impl EnableControllerService for PGPPrivateKeyService {
@@ -35,17 +36,12 @@ impl EnableControllerService for PGPPrivateKeyService {
     where
         Self: Sized,
     {
-        let mut private_keys = 
context.get_property(&KEY_FILE)?.unwrap_or_default();
-        private_keys.extend(context.get_property(&KEY)?.unwrap_or_default());
+        let private_keys = load_service_keys(context, &KEY_FILE, &KEY)?;
+        let passphrases = 
context.get_property(&KEY_PASSWORD)?.unwrap_or_default();
 
-        let passphrase = 
context.get_property(&KEY_PASSWORD)?.unwrap_or_default();
-
-        if private_keys.is_empty() {
-            return Err(MinifiError::validation("Could not load any valid 
keys"));
-        }
         Ok(Self {
             private_keys,
-            passphrase,
+            passphrases,
         })
     }
 }
@@ -54,7 +50,7 @@ impl PGPPrivateKeyService {
     pub fn get_the_ring(&'_ self) -> TheRing<'_> {
         TheRing {
             secret_keys: self.private_keys.iter().collect(),
-            key_passwords: vec![&self.passphrase],
+            key_passwords: self.passphrases.iter().collect(),
             message_password: vec![],
             session_keys: vec![],
             decrypt_options: Default::default(),
@@ -62,12 +58,11 @@ impl PGPPrivateKeyService {
     }
 
     #[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(),
+    pub fn get_secret_key(&self, target_id: &str) -> Result<&SignedSecretKey, 
MinifiError> {
+        find_unique_key(&self.private_keys, target_id, |private_key| {
+            (
+                private_key.primary_key.legacy_key_id(),
                 &private_key.details,
-                target_id,
             )
         })
     }
@@ -92,8 +87,11 @@ mod service_def {
     pub(super) const KEY: Property<Option<SecretKey>> =
         Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive();
 
-    pub(super) const KEY_PASSWORD: Property<Option<utils::Password>> =
-        Property::new("Key Password", "Password used for decrypting Private 
Keys").sensitive();
+    pub(super) const KEY_PASSWORD: Property<Option<utils::Passwords>> = 
Property::new(
+        "Key Password",
+        "Password used for decrypting Private Keys. Multiple passwords may be 
supplied one per line, each of them is tried in turn",
+    )
+    .sensitive();
 
     impl ControllerServiceDefinition for PGPPrivateKeyService {
         const DESCRIPTION: &'static str =
@@ -136,11 +134,11 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
 
-        assert!(service.get_secret_key("Bob").is_none());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Bob").is_err());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
@@ -153,18 +151,14 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("A").is_some());
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(
-            service
-                .get_secret_key("Alice <[email protected]>")
-                .is_some()
-        );
+        assert!(service.get_secret_key("A").is_ok());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("Alice <[email protected]>").is_ok());
 
-        assert!(service.get_secret_key("<Alice>").is_none());
+        assert!(service.get_secret_key("<Alice>").is_err());
 
-        assert!(service.get_secret_key("Bob").is_none());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Bob").is_err());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
@@ -177,11 +171,11 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(service.get_secret_key("Bob").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("Bob").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
@@ -194,11 +188,11 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(service.get_secret_key("Bob").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("Bob").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
@@ -212,11 +206,11 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(service.get_secret_key("Bob").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("[email protected]").is_some());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("Bob").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("[email protected]").is_ok());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
@@ -230,9 +224,9 @@ mod tests {
 
         let service =
             PGPPrivateKeyService::enable(&context, 
&MockLogger::new()).expect("should enable");
-        assert!(service.get_secret_key("Alice").is_some());
-        assert!(service.get_secret_key("Bob").is_none());
-        assert!(service.get_secret_key("Carol").is_none());
+        assert!(service.get_secret_key("Alice").is_ok());
+        assert!(service.get_secret_key("Bob").is_err());
+        assert!(service.get_secret_key("Carol").is_err());
     }
 
     #[test]
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
index c956ad18a..1fc0987c5 100644
--- 
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
@@ -15,7 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::controller_services::key_lookup::key_matches;
+use crate::controller_services::key_lookup::find_unique_key;
+use crate::controller_services::key_parsing::load_service_keys;
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError};
 use pgp::composed::SignedPublicKey;
@@ -32,24 +33,15 @@ impl EnableControllerService for PGPPublicKeyService {
     where
         Self: Sized,
     {
-        let mut public_keys = 
context.get_property(&KEYRING_FILE)?.unwrap_or_default();
-        
public_keys.extend(context.get_property(&KEYRING)?.unwrap_or_default());
-
-        if public_keys.is_empty() {
-            return Err(MinifiError::validation("Could not load any valid 
keys"));
-        }
+        let public_keys = load_service_keys(context, &KEYRING_FILE, &KEYRING)?;
         Ok(Self { public_keys })
     }
 }
 
 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,
-            )
+    pub fn get(&self, target_id: &str) -> Result<&SignedPublicKey, 
MinifiError> {
+        find_unique_key(&self.public_keys, target_id, |public_key| {
+            (public_key.primary_key.legacy_key_id(), &public_key.details)
         })
     }
 }
@@ -127,11 +119,11 @@ mod tests {
         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("Alice").is_ok());
+        assert!(controller_service.get("[email protected]").is_ok());
 
-        assert!(controller_service.get("Bob").is_none());
-        assert!(controller_service.get("Carol").is_none());
+        assert!(controller_service.get("Bob").is_err());
+        assert!(controller_service.get("Carol").is_err());
     }
 
     #[test]
@@ -143,14 +135,14 @@ mod tests {
 
         let service = PGPPublicKeyService::enable(&context, &MockLogger::new())
             .expect("enable should succeed");
-        assert!(service.get("A").is_some());
-        assert!(service.get("Alice").is_some());
-        assert!(service.get("Alice <[email protected]>").is_some());
+        assert!(service.get("A").is_ok());
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("Alice <[email protected]>").is_ok());
 
-        assert!(service.get("<Alice>").is_none());
+        assert!(service.get("<Alice>").is_err());
 
-        assert!(service.get("Bob").is_none());
-        assert!(service.get("Carol").is_none());
+        assert!(service.get("Bob").is_err());
+        assert!(service.get("Carol").is_err());
     }
 
     #[test]
@@ -162,11 +154,11 @@ mod tests {
 
         let service = PGPPublicKeyService::enable(&context, &MockLogger::new())
             .expect("enable should succeed");
-        assert!(service.get("Alice").is_some());
-        assert!(service.get("Bob").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("Carol").is_none());
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("Bob").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("Carol").is_err());
     }
 
     #[test]
@@ -178,11 +170,11 @@ mod tests {
 
         let service = PGPPublicKeyService::enable(&context, &MockLogger::new())
             .expect("enable should succeed");
-        assert!(service.get("Alice").is_some());
-        assert!(service.get("Bob").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("Carol").is_none());
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("Bob").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("Carol").is_err());
     }
 
     #[test]
@@ -198,11 +190,11 @@ mod tests {
 
         let service = PGPPublicKeyService::enable(&context, &MockLogger::new())
             .expect("enable should succeed");
-        assert!(service.get("Alice").is_some());
-        assert!(service.get("Bob").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("[email protected]").is_some());
-        assert!(service.get("Carol").is_none());
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("Bob").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("Carol").is_err());
     }
 
     #[test]
@@ -218,9 +210,9 @@ mod tests {
 
         let service = PGPPublicKeyService::enable(&context, &MockLogger::new())
             .expect("enable should succeed");
-        assert!(service.get("Alice").is_some());
-        assert!(service.get("Bob").is_none());
-        assert!(service.get("Carol").is_none());
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("Bob").is_err());
+        assert!(service.get("Carol").is_err());
     }
 
     #[test]
@@ -250,9 +242,55 @@ mod tests {
         let alice = 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);
-        assert!(service.get(&key_id_hex).is_some());
-        assert!(service.get(&key_id_hex.to_ascii_uppercase()).is_some());
-        assert!(service.get(&key_id_hex[..8]).is_none());
-        assert!(service.get("0123456789abcdef").is_none());
+        assert!(service.get(&key_id_hex).is_ok());
+        assert!(service.get(&key_id_hex.to_ascii_uppercase()).is_ok());
+        assert!(service.get(&key_id_hex[..8]).is_err());
+        assert!(service.get("0123456789abcdef").is_err());
+    }
+
+    fn ambiguous_keyring_service() -> PGPPublicKeyService {
+        let mut context = MockControllerServiceContext::new();
+        context.properties.insert(
+            "Keyring File".to_string(),
+            get_test_key_path("ambiguous_keyring.gpg"),
+        );
+
+        PGPPublicKeyService::enable(&context, 
&MockLogger::new()).expect("enable should succeed")
+    }
+
+    /// A User ID search that matches more than one key must fail loudly 
instead of picking one
+    /// of them, otherwise a look-alike key silently becomes the recipient.
+    #[test]
+    fn a_user_id_matching_several_keys_is_reported_as_ambiguous() {
+        let service = ambiguous_keyring_service();
+
+        // "[email protected]" is a substring of the look-alike 
"[email protected]" too.
+        let err = service.get("[email protected]").unwrap_err().to_string();
+        assert!(err.contains("ambiguous"), "{err}");
+        assert!(err.contains("[email protected]"), "{err}");
+
+        // Unaffected searches still resolve.
+        assert!(service.get("Alice").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+        assert!(service.get("[email protected]").is_ok());
+    }
+
+    /// An exact Key ID is never ambiguous, so it stays usable as the way to 
disambiguate.
+    #[test]
+    fn a_key_id_search_wins_over_an_ambiguous_user_id() {
+        let service = ambiguous_keyring_service();
+
+        let real_bob = service
+            .get("[email protected]")
+            .expect("the real Bob should be found by his unique User ID");
+        let real_bob_key_id = real_bob.primary_key.legacy_key_id().to_string();
+
+        let found = service
+            .get(&real_bob_key_id)
+            .expect("a Key ID search should never be ambiguous");
+        assert_eq!(
+            found.primary_key.legacy_key_id(),
+            real_bob.primary_key.legacy_key_id()
+        );
     }
 }
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs
index 73fe83d98..e74d9896f 100644
--- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs
@@ -326,6 +326,118 @@ mod tests {
         );
     }
 
+    /// Encrypts `content` for `public_key_search` using `keyring_file` and 
returns the ciphertext.
+    fn encrypt_for(keyring_file: &str, public_key_search: &str, content: 
&[u8]) -> Vec<u8> {
+        use 
crate::controller_services::public_key_service::PGPPublicKeyService;
+        use crate::processors::encrypt_content::EncryptContentPGP;
+
+        let mut key_service_context = MockControllerServiceContext::new();
+        key_service_context.properties.insert(
+            "Keyring File".to_string(),
+            test_utils::get_test_key_path(keyring_file),
+        );
+        let key_service = PGPPublicKeyService::enable(&key_service_context, 
&MockLogger::new())
+            .expect("should enable");
+
+        let mut context = MockProcessContext::new();
+        context.properties.extend([
+            ("Public Key Service", "my_public_key_service"),
+            ("Public Key Search", public_key_search),
+        ]);
+        context
+            .controller_services
+            .insert("my_public_key_service".to_string(), 
Box::new(key_service));
+
+        let mut ciphertext: Vec<u8> = Vec::new();
+        let mut plaintext = std::io::Cursor::new(content);
+        let encrypt_content =
+            EncryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+        encrypt_content
+            .transform(
+                &context,
+                &mut plaintext,
+                &mut ciphertext,
+                &MockLogger::new(),
+            )
+            .expect("should encrypt");
+        ciphertext
+    }
+
+    fn decrypt_with(private_key_data: PrivateKeyData, ciphertext: Vec<u8>) -> 
Vec<u8> {
+        let mut context = MockProcessContext::new();
+        context.controller_services.insert(
+            "my_private_key_service".to_string(),
+            Box::new(private_key_data.into_controller()),
+        );
+        context.properties.insert(
+            PRIVATE_KEY_SERVICE.name(),
+            "my_private_key_service".to_string(),
+        );
+
+        let decrypt_content =
+            DecryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+        let mut output: Vec<u8> = Vec::new();
+        let mut ciphertext = std::io::Cursor::new(ciphertext);
+        let res = decrypt_content
+            .transform(&context, &mut ciphertext, &mut output, 
&MockLogger::new())
+            .expect("should decrypt");
+        assert_eq!(res.target_relationship_name(), SUCCESS.name);
+        assert_eq!(res.write_status(), IoState::Ok);
+        output
+    }
+
+    /// Dave's primary key is Ed25519, which cannot encrypt; only his Cv25519 
subkey can. This
+    /// round trip only works if EncryptContentPGP encrypts to the subkey.
+    #[test]
+    fn round_trip_with_a_sign_only_primary_key() {
+        let ciphertext = encrypt_for("dave.asc", "[email protected]", b"for 
dave only");
+
+        let dave_private_key = PrivateKeyData {
+            key_filename: "dave_private.asc",
+            passphrase: Some("gardenparty"),
+        };
+        assert_eq!(decrypt_with(dave_private_key, ciphertext), b"for dave 
only");
+    }
+
+    /// A keyring holding keys with different passphrases needs every one of 
those passphrases,
+    /// which "Key Password" accepts one per line.
+    #[test]
+    fn decrypts_with_one_of_several_key_passwords() {
+        let ciphertext = encrypt_for("dave.asc", "[email protected]", b"for 
dave only");
+
+        // mixed_secret_keyring.gpg holds Alice (whiterabbit) and Dave 
(gardenparty).
+        let both_passwords = PrivateKeyData {
+            key_filename: "mixed_secret_keyring.gpg",
+            passphrase: Some("whiterabbit\ngardenparty"),
+        };
+        assert_eq!(
+            decrypt_with(both_passwords, ciphertext.clone()),
+            b"for dave only"
+        );
+
+        // Alice's passphrase alone cannot unlock Dave's key.
+        let alice_password_only = PrivateKeyData {
+            key_filename: "mixed_secret_keyring.gpg",
+            passphrase: Some("whiterabbit"),
+        };
+        let mut context = MockProcessContext::new();
+        context.controller_services.insert(
+            "my_private_key_service".to_string(),
+            Box::new(alice_password_only.into_controller()),
+        );
+        context.properties.insert(
+            PRIVATE_KEY_SERVICE.name(),
+            "my_private_key_service".to_string(),
+        );
+        let decrypt_content =
+            DecryptContentPGP::schedule(&context, 
&MockLogger::new()).expect("should schedule");
+        let mut output: Vec<u8> = Vec::new();
+        let mut ciphertext = std::io::Cursor::new(ciphertext);
+        let res =
+            decrypt_content.transform(&context, &mut ciphertext, &mut output, 
&MockLogger::new());
+        test::assert_routed_to(res, &FAILURE);
+    }
+
     #[test]
     fn decryption_of_not_encrypted_data() {
         let alice_private_key = PrivateKeyData {
diff --git 
a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs 
b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs
index 6b4346ee7..185ad7953 100644
--- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::controller_services::encryption_key::{EncryptionTarget, 
select_encryption_target};
 use minifi_native::{
     FlowFileStreamTransform, GetAttribute, GetControllerService, GetId, 
GetProperty, InputStream,
     Logger, MinifiError, OutputStream, ProcessError, RouteErrorExt, Schedule,
@@ -73,9 +74,14 @@ impl EncryptContentPGP {
         );
 
         if let Some(pub_key) = pub_key {
-            builder
-                .encrypt_to_key(rand::thread_rng(), pub_key)
-                .map_err(MinifiError::other)?;
+            match select_encryption_target(pub_key)? {
+                EncryptionTarget::Primary(primary_key) => builder
+                    .encrypt_to_key(rand::thread_rng(), primary_key)
+                    .map_err(MinifiError::other)?,
+                EncryptionTarget::Subkey(subkey) => builder
+                    .encrypt_to_key(rand::thread_rng(), subkey)
+                    .map_err(MinifiError::other)?,
+            };
         }
 
         if let Some(password) = &self.symmetric_password {
@@ -131,12 +137,7 @@ impl EncryptContentPGP {
             context.get_property(&PUBLIC_KEY_SEARCH)?,
             context.get_controller_service(&PUBLIC_KEY_SERVICE)?,
         ) {
-            match public_key_service.get(&pub_key_search) {
-                Some(public_key) => Ok(Some(public_key)),
-                None => Err(MinifiError::custom(format!(
-                    "No public key matching '{pub_key_search}' found in the 
configured Public Key Service"
-                ))),
-            }
+            Ok(Some(public_key_service.get(&pub_key_search)?))
         } else {
             Ok(None)
         }
@@ -154,9 +155,10 @@ impl FlowFileStreamTransform for EncryptContentPGP {
         output_stream: &mut dyn OutputStream,
         _logger: &LoggerImpl,
     ) -> Result<TransformStreamResult, ProcessError> {
-        let file_name = context
-            .get_attribute("filename")?
-            .unwrap_or(context.get_id()?);
+        let file_name = match context.get_attribute("filename")? {
+            Some(file_name) => file_name,
+            None => context.get_id()?,
+        };
         let public_key = Self::get_public_key(context).route_err_to_failure()?;
 
         self.encrypt_bytes(input_stream, output_stream, public_key, file_name)
diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs 
b/minifi_rust/extensions/minifi_pgp/src/utils.rs
index 877cc7005..7528e06b3 100644
--- a/minifi_rust/extensions/minifi_pgp/src/utils.rs
+++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs
@@ -35,3 +35,46 @@ impl PropertyType for Password {
         Ok(pgp::types::Password::from(s))
     }
 }
+
+/// A newline separated list of passwords, each of which is tried in turn when 
unlocking a key.
+pub(crate) struct Passwords {}
+
+impl PropertySchema for Passwords {
+    const CONSTRAINT: Option<PropertyConstraints> = 
Some(PropertyConstraints::Validator(
+        StandardPropertyValidator::NonBlankValidator,
+    ));
+    const IS_REQUIRED: bool = true;
+}
+
+impl PropertyType for Passwords {
+    type Output = Vec<pgp::types::Password>;
+
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        Ok(s.lines()
+            .filter(|line| !line.is_empty())
+            .map(pgp::types::Password::from)
+            .collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn single_password() {
+        assert_eq!(Passwords::parse("hunter2").unwrap().len(), 1);
+    }
+
+    #[test]
+    fn one_password_per_line() {
+        let passwords = Passwords::parse("alice-pw\nbob-pw").unwrap();
+        assert_eq!(passwords.len(), 2);
+    }
+
+    #[test]
+    fn blank_lines_are_dropped() {
+        let passwords = Passwords::parse("alice-pw\n\nbob-pw\n").unwrap();
+        assert_eq!(passwords.len(), 2);
+    }
+}
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt 
b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt
index 7b8ad1a61..2b403e327 100644
--- a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt
@@ -1,8 +1,55 @@
-Testing keys v2
+Testing keys v3
 ------------------------
 uid           [ultimate] Alice <[email protected]>
+keyid         BCCE3FDFBA019D7E
 passphrase    whiterabbit
+RSA, the primary key carries the encrypt capability itself, no subkeys
 
 uid           [ultimate] Bob Personal <[email protected]>
 uid           [ultimate] Bob Primary <[email protected]>
-no passphrase
\ No newline at end of file
+keyid         A06749BA4F34B0E5
+no passphrase
+
+uid           [ultimate] Dave <[email protected]>
+keyid         297B6A88887FB64F
+passphrase    gardenparty
+Ed25519 sign-only primary key plus a Cv25519 encryption subkey, which is the
+layout `gpg --gen-key` produces by default. Encrypting to this key only works
+if the encryption subkey is selected rather than the primary key.
+  gpg --quick-generate-key 'Dave <[email protected]>' ed25519 sign never
+  gpg --quick-add-key <fingerprint> cv25519 encr never
+
+uid           [ultimate] Erin <[email protected]>
+keyid         8C96441440A22F74
+no passphrase
+Ed25519 sign-only primary key with no subkeys at all, so it cannot be used for
+encryption. Public key only.
+  gpg --quick-generate-key 'Erin <[email protected]>' ed25519 sign never
+
+uid           [ultimate] Bob Personal <[email protected]>
+keyid         756FC2EBECB8747C
+no passphrase
+A look-alike of Bob whose User ID has Bob's own address as a prefix, so that a
+"[email protected]" search matches both keys. Only used to build
+ambiguous_keyring.gpg. Public key only.
+
+Keyrings
+------------------------
+keyring.{asc,gpg}               public:  Alice + Bob
+secret_keyring.{asc,gpg}        secret:  Alice + Bob
+ambiguous_keyring.gpg           public:  Alice + Bob + the look-alike Bob, so
+                                that a "[email protected]" User ID search is
+                                ambiguous
+mixed_secret_keyring.gpg        secret:  Alice + Dave, i.e. two keys whose
+                                passphrases differ, which requires more than
+                                one Key Password to unlock both
+
+The binary keyrings are concatenations of the individual dearmored keys:
+  gpg --dearmor < keyring.asc > keyring.bin
+  gpg --dearmor < spoofed_bob.asc > spoofed.bin
+  cat keyring.bin spoofed.bin > ambiguous_keyring.gpg
+
+v3 note: v2 shipped two distinct Alice keys sharing the User ID
+"Alice <[email protected]>", which made a "Alice" key search ambiguous. The
+stray key (keyid 1BB0EC4BF35325F6) was dropped; the remaining Alice key is the
+one the messages in test_messages/ were encrypted to.
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc
index 8ae976d7b..ebe21a684 100644
--- a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc
@@ -1,50 +1,18 @@
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 
-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
-q+zIM+a7/I/TcOmDRO5s
-=EEA6
+mQENBGmJ4mMBCADEYKe0I+Mr/IPKlYUVEgb0yC+iRRBfkhPfMQjbjfM3sD2jyf0g
+k983pVWF5I4Tl3AFW+D2wAJCayFnIYQWaMnK133EdxZE61ewhvS9g3Zk2Q1JzO6F
+B/w4hfCSlPkK82j3Xrd1U0ZJ+YP60DucYlanFPu8+Iw7reusOT9JOJWazzCo4XQX
+ImXomT+KVFsN9UDaKv0mSoH5VgG6thQb6veajcYhrN7rU7isTA2hICxo1nLlnCpy
+7LbAcN9sP1r5Ukpw0WsXKItol0f29p72biFrLbL7670lRxl/CuHTbHYpbCo7Cdng
+R1ieQ3yEqX4qqrKbNocpylCrSSuc78F7pxYNABEBAAG0GUFsaWNlIDxhbGljZUBl
+eGFtcGxlLmNvbT6JAVIEEwEIADwWIQSZnIioWmY7HCCheVW8zj/fugGdfgUCaYni
+YwIbLwULCQgHAgMiAgEGFQoJCAsCBBYCAwECHgcCF4AACgkQvM4/37oBnX7RUQf7
+BZV8ZPlZxbQaZ8kMabC9meNVaYTOWIjJpDqJ7XqG6mMwvzeA3hvtgQOEEQTYbUcw
++ItlN9XQVrlNAyJZVGJQ+2UXp4XRmdogyShE/zFv/NkxvccfzC/BD5jMy88BS8lu
+Gv8bANioE8bF/IwC538A5/6oPN1weQ4OpjcBfeYoyDD32sMc2P5uAWe9pLTAR+vz
+F5mWG4K6TWrfH8/YuB6CCQSCr17N9ZPP2rpcPa2miyzQGJy5wdMOiahBFfdvHACO
+bJXeKYcxZdLs7ImMw78IbbVkzmEKzCStLTqntL8Bsh+eu4nvescoYkMPUzW1E3P3
+BavsyDPmu/yP03Dpg0TubA==
+=+8XC
 -----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
index 71af43989..8ef375e68 100644
Binary files a/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg 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
index 955e52584..b47c96820 100644
--- a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc
@@ -1,92 +1,31 @@
 -----BEGIN PGP PRIVATE KEY BLOCK-----
 
-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
-=6nxq
+lQOYBGmJ4mMBCADEYKe0I+Mr/IPKlYUVEgb0yC+iRRBfkhPfMQjbjfM3sD2jyf0g
+k983pVWF5I4Tl3AFW+D2wAJCayFnIYQWaMnK133EdxZE61ewhvS9g3Zk2Q1JzO6F
+B/w4hfCSlPkK82j3Xrd1U0ZJ+YP60DucYlanFPu8+Iw7reusOT9JOJWazzCo4XQX
+ImXomT+KVFsN9UDaKv0mSoH5VgG6thQb6veajcYhrN7rU7isTA2hICxo1nLlnCpy
+7LbAcN9sP1r5Ukpw0WsXKItol0f29p72biFrLbL7670lRxl/CuHTbHYpbCo7Cdng
+R1ieQ3yEqX4qqrKbNocpylCrSSuc78F7pxYNABEBAAEAB/9WdfHQa86M4shJzQwD
+i2TZqDvkt4Cue1vZdDbgp75ygduZvgh/K+vnDZm6cjclpBLToTDKox47jPxvcj+8
+OBXEg50hf4cj//QjSj/+Ip/hZfkmSZ6onqvrXPlfKE0AB8xqwV6Hvwre7gcwSjc8
+ssVRGfl+KXZgnkH5mVjmTY380r95ZXlH9vjdNhhONc9MeUwOb8OEfT5z1wB4sdJR
+p/ThjAdYRDLj0FV1TRtF5X3H5nrEPbxQCg3namJujiGqp4xRgAqMEjEGjr7sX6gg
+kS/3AH60BLiECwfO69ZQnbFwJAFa3+NBlLE/aGSdn+UgkJ2Umc/WeNnYxTp8ith0
+8Cs3BADVDRYWWQdwuDzLIxsWWhY3RtD4xO0VWKz8ETo+XcvXUBYHMTKLHSLiQXUx
+LJvLBp51wVPKQasyd1OgP08tYC8wY4mSrYqxvKHjjXbaIdYvGPDjk4PB6EZoaZx6
+Jxyjn9o3Klb6BGXFR17wdIqwhseUKPJmyxDkDEDWARN52XFWEwQA6/cY/te9LmK9
+4i3vfy0V4CZRzm+Lb4BbeioOO7tpxEvKXkVSQ5ZNrahgJ/8h6Es05BgFfjIGSF0c
+AYvI6aTtHYL2H8CLFuDMYrQCvkR9d643Bze4oOQwKcjdH0t/15iQgNw1kFIhAzhb
+sdzF4EETkpbOi4iZVMI1sxnBzjP/518D/jcYq6mkIY5rZIPnZ2DYL8Mzaa1aT9vt
+T6Q8ldS/Mcp7kaGfX1yd9aIj7W7o5ZvcZfoWiNbH41QXHOyFUbPpYkR5RwPBb6hC
+G5GMVAeLmlfXxDsSjA9/QaudORQxFdoAygVCa4M64eO6/t4PZMiMD9xspRvyJ7sw
+RmZMHam3iGymOcW0GUFsaWNlIDxhbGljZUBleGFtcGxlLmNvbT6JAVIEEwEIADwW
+IQSZnIioWmY7HCCheVW8zj/fugGdfgUCaYniYwIbLwULCQgHAgMiAgEGFQoJCAsC
+BBYCAwECHgcCF4AACgkQvM4/37oBnX7RUQf7BZV8ZPlZxbQaZ8kMabC9meNVaYTO
+WIjJpDqJ7XqG6mMwvzeA3hvtgQOEEQTYbUcw+ItlN9XQVrlNAyJZVGJQ+2UXp4XR
+mdogyShE/zFv/NkxvccfzC/BD5jMy88BS8luGv8bANioE8bF/IwC538A5/6oPN1w
+eQ4OpjcBfeYoyDD32sMc2P5uAWe9pLTAR+vzF5mWG4K6TWrfH8/YuB6CCQSCr17N
+9ZPP2rpcPa2miyzQGJy5wdMOiahBFfdvHACObJXeKYcxZdLs7ImMw78IbbVkzmEK
+zCStLTqntL8Bsh+eu4nvescoYkMPUzW1E3P3BavsyDPmu/yP03Dpg0TubA==
+=DPaN
 -----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
index 5f385d9ee..7cb214908 100644
Binary files a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg 
and b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/ambiguous_keyring.gpg
similarity index 53%
copy from minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg
copy to minifi_rust/extensions/minifi_pgp/test_keys/ambiguous_keyring.gpg
index 6a84506f9..c0e9da5ac 100644
Binary files a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg and 
b/minifi_rust/extensions/minifi_pgp/test_keys/ambiguous_keyring.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc
new file mode 100644
index 000000000..e413d1fba
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEarPhDRYJKwYBBAHaRw8BAQdAIutX9mnyeaFBjHNkOMPq9UsWaoZB3An5wfHn
+pns7UJ+0F0RhdmUgPGRhdmVAZXhhbXBsZS5jb20+iK8EExYKAFcWIQRTkbiPC69l
+G2IDzoApe2qIiH+2TwUCarPhDRsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMC
+GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQKXtqiIh/tk8qLgEA7oKJ
+95fgapuSqFLqDa2gAemjXHgeox4Jn2LayST3zrYA/ji0PvUzFxuFrgrl5eCGZfmr
+FM2rPzkppJ5PAxCA/xQBuDgEarPhDhIKKwYBBAGXVQEFAQEHQBKa7zXTR3mYQ7et
+L2oIYWHyRm4kbUmW3E4KGHfyOt4YAwEIB4iUBBgWCgA8FiEEU5G4jwuvZRtiA86A
+KXtqiIh/tk8FAmqz4Q4bFIAAAAAABAAObWFudTIsMi41KzEuMTIsMCwzAhsMAAoJ
+ECl7aoiIf7ZPnK0A/jt6JWNcf8f8GEQn57O8okYgmCgWFOBQCQ24JTiYfgCNAP9p
+Mg/o4PePUr9kkvSNEdK0JKqYAohHzsKj0JszrmWhBg==
+=D4Oc
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc
new file mode 100644
index 000000000..f0aef7b0e
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc
@@ -0,0 +1,18 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lIYEarPhDRYJKwYBBAHaRw8BAQdAIutX9mnyeaFBjHNkOMPq9UsWaoZB3An5wfHn
+pns7UJ/+BwMCEfVsHfEs1cP8LAg9S+l4EcQP9um39mYC3bQ+JWOWFHHoGsA20tu7
+2ZX0pdvjh46UMVr39w7DAXsgPULKp7Bq+4qudaT0mKLHTd57ENaEV7QXRGF2ZSA8
+ZGF2ZUBleGFtcGxlLmNvbT6IrwQTFgoAVxYhBFORuI8Lr2UbYgPOgCl7aoiIf7ZP
+BQJqs+ENGxSAAAAAAAQADm1hbnUyLDIuNSsxLjEyLDAsMwIbAwULCQgHAgIiAgYV
+CgkICwIEFgIDAQIeBwIXgAAKCRApe2qIiH+2TyouAQDugon3l+Bqm5KoUuoNraAB
+6aNceB6jHgmfYtrJJPfOtgD+OLQ+9TMXG4WuCuXl4IZl+asUzas/OSmknk8DEID/
+FAGciwRqs+EOEgorBgEEAZdVAQUBAQdAEprvNdNHeZhDt60vaghhYfJGbiRtSZbc
+TgoYd/I63hgDAQgH/gcDAvn3D6dzSBZu/L7P7nkJxxHoW5QfnEpAqoua9mIBSlC8
++UCTBeMNa13xUVNSlxaz8R+ukFhr4oX0e3nRs5YZm4rwa38rK8k1fOLJYmnD2tiI
+lAQYFgoAPBYhBFORuI8Lr2UbYgPOgCl7aoiIf7ZPBQJqs+EOGxSAAAAAAAQADm1h
+bnUyLDIuNSsxLjEyLDAsMwIbDAAKCRApe2qIiH+2T5ytAP47eiVjXH/H/BhEJ+ez
+vKJGIJgoFhTgUAkNuCU4mH4AjQD/aTIP6OD3j1K/ZJL0jRHStCSqmAKIR87Co9Cb
+M65loQY=
+=+PcV
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc
new file mode 100644
index 000000000..21efeead3
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc
@@ -0,0 +1,10 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEarPhDxYJKwYBBAHaRw8BAQdA4/aTB+10d9/3Ey4WHi5iXSlP9i9GWqE9xY8k
+c/zzqva0F0VyaW4gPGVyaW5AZXhhbXBsZS5jb20+iK8EExYKAFcWIQTRATOyPqzK
+Yn5F1i6MlkQUQKIvdAUCarPhDxsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMC
+GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQjJZEFECiL3SlhAD/cCjS
+ArPCcbLS8v5YUAJvw2M2/9fKSUsUA8zX/by17PUA/R50qJzXFWEbcG73cPda+PdZ
+qiCywhY+HrzMJJ5DE7wC
+=xbRI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc
index 0fa816a55..fb5c7cc62 100644
--- a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc
@@ -1,89 +1,58 @@
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 
-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
-=fkCr
+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
+wA==
+=/AVq
 -----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
index 6a84506f9..9bb901d97 100644
Binary files a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg and 
b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg differ
diff --git 
a/minifi_rust/extensions/minifi_pgp/test_keys/mixed_secret_keyring.gpg 
b/minifi_rust/extensions/minifi_pgp/test_keys/mixed_secret_keyring.gpg
new file mode 100644
index 000000000..f84d98dcb
Binary files /dev/null and 
b/minifi_rust/extensions/minifi_pgp/test_keys/mixed_secret_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
index 6430facf8..3af68369d 100644
--- a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc
@@ -1,159 +1,98 @@
 -----BEGIN PGP PRIVATE KEY BLOCK-----
 
-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
-=f4lJ
+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
+=pfOS
 -----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
index 4a07229aa..bc5eac469 100644
Binary files a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg 
and b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg differ
diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/spoofed_bob.asc 
b/minifi_rust/extensions/minifi_pgp/test_keys/spoofed_bob.asc
new file mode 100644
index 000000000..ea03803a6
--- /dev/null
+++ b/minifi_rust/extensions/minifi_pgp/test_keys/spoofed_bob.asc
@@ -0,0 +1,19 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBGqz4vkBCAC79KLH64ayyRJmSATxXWiaFBhYZIAcYz/AC4159XMMfbAhtaRr
+UvCiLjmlPDuA1QZtpR139v8UyTxmlkp5xul+Vgjd7dKc1FRFSdJKZVni2RxnC0Cr
+mD+zIzW2qS8kV4tcuxqE2mGVHlpUkivO41HNYnt2Ks5TNcochQMe/3gi+aHko6Pn
+8VlInu4aQx7wnDJBaKNNMajpGJgL/zCgw0TgG0v/m4LIgHFE9RE+bXT2YUpMIYVh
+o6vG82C0xy8uxCMcyHgbic57JsTgRF0TJlJiLn3eEjenjsjARqFEaniIAPvJ99JR
+wVB1v0es8dHfBx00+4XPU67d71l2VoLFHJXHABEBAAG0KEJvYiBQZXJzb25hbCA8
+Ym9iQGhvbWUuaW8uYXR0YWNrZXIudGVzdD6JAW0EEwEIAFcWIQR41anBL61W0dnR
+hWt1b8Lr7Lh0fAUCarPi+RsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMCGw0F
+CwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQdW/C6+y4dHzgPAf/c9R0Oo7t
+pVmfcE1buMa5jopGAugLYsGI4ChhjYILihPw3aCRMW+05wlVYJREAgXQmaigd99a
+Sdt0bqLTIYcY7xKlQuYRc0lJilo/xmak2rm2EJzVM13zGH7GK0pEXr9Mt9vLjdpe
+j0rbLIup9pZYYPtpYk0kz81T2GLqeSYnR7Wdm99Phdagrn3/RY8tzYt/+F6z8JTd
+yF+E955OsLE20xY4dS5j5GShf4xvjNCWhcLfst8Q2ZAyPIGcbpBGP4ww43jFIvjO
+q3wSh+VK3eSnB2gfy+sbwO24zL79udsPvT8a1NGUVjZidseYBvqAzs4m8e724l5F
+EgkpqCOdeyN8hg==
+=nqES
+-----END PGP PUBLIC KEY BLOCK-----

Reply via email to