You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bouncy Castle加密:子密钥与主密钥绑定机制解析及添加收件人邮箱的实现求助

Bouncy Castle加密:子密钥与主密钥绑定机制解析及添加收件人邮箱的实现求助

Hi 👋,针对你用Bouncy Castle碰到的两个问题,我来帮你拆解一下:

一、子密钥与主密钥的绑定机制解析

在PGP体系里,子密钥和主密钥的绑定靠的是数字签名,具体逻辑其实很直观:

  • 当你生成子密钥时,主密钥会对这个子密钥的公钥部分做一次签名,这个签名会被打包进子密钥的数据包里。
  • 要验证两者的绑定关系,任何人都可以用主公钥去校验子公钥上的签名:如果验证通过,就说明这个子密钥确实是主密钥持有者授权生成的,二者的绑定关系合法有效。
  • 这种设计的核心好处是:主密钥通常只用来做签名和密钥管理,不直接参与日常加密/解密,哪怕子密钥泄露,主密钥依然安全,不会影响整个密钥对的可信度;同时,主密钥的签名相当于给子密钥做了“身份背书”,其他人可以放心用子密钥加密数据。

简单说,主密钥就是子密钥的“官方担保人”,签名就是实打实的担保凭证。

二、加密时关联收件人邮箱的实现方案

你现在的代码是直接从公钥文件里找第一个符合条件的子密钥,但没有根据收件人邮箱筛选对应密钥。要实现指定邮箱加密,核心是先通过邮箱找到匹配的公钥环,再从中提取加密子密钥,具体修改如下:

1. 修改readPublicKey方法,支持按邮箱筛选

给方法加一个recipientEmail参数,遍历公钥环时检查主密钥的用户ID是否匹配目标邮箱,找到对应公钥环后再提取加密子密钥:

public static PGPPublicKey readPublicKey(final String path, String recipientEmail) throws Exception {
    try (InputStream in = new BufferedInputStream(new FileInputStream(path))) {

        PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
                PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());

        for (PGPPublicKeyRing keyRing : pgpPub) {
            PGPPublicKey primaryKey = keyRing.getPublicKey();
            Iterator<String> userIDs = primaryKey.getUserIDs();
            
            // 遍历用户ID,匹配目标邮箱
            boolean isMatchingRecipient = false;
            while (userIDs.hasNext()) {
                String userID = userIDs.next();
                if (userID.contains(recipientEmail)) {
                    isMatchingRecipient = true;
                    System.out.println("找到匹配的收件人密钥: " + userID);
                    break;
                }
            }
            
            // 如果找到匹配的收件人,再找对应的加密子密钥
            if (isMatchingRecipient) {
                for (PGPPublicKey key : keyRing) {
                    if (!key.isMasterKey() && key.isEncryptionKey()) {
                        return key;
                    }
                }
                throw new IllegalArgumentException("找到匹配收件人的公钥环,但未找到可用的加密子密钥");
            }
        }
        throw new IllegalArgumentException("未找到对应收件人邮箱的公钥,请检查公钥文件和邮箱地址是否正确");
    }
}

2. 调用加密方法时传入收件人邮箱

在调用readPublicKey时传入目标邮箱(比如example@gmail.com),拿到对应子密钥后再执行加密:

// 示例调用
PGPPublicKey encKey = readPublicKey("path/to/public/key.asc", "example@gmail.com");
encryptFile("output.gpg", "input.txt", encKey, true, true);

补充优化:简化加密流程(去掉临时文件)

你的原encryptFile方法用了临时文件中转,其实可以直接流式处理,代码更简洁高效:

public static void encryptFile(String outputFileName, String inputFileName, PGPPublicKey encKey, boolean armor, boolean withIntegrityCheck)
        throws IOException, PGPException {

    try (OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
         OutputStream encOut = armor ? new ArmoredOutputStream(out) : out) {

        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256)
                        .setWithIntegrityPacket(withIntegrityCheck)
                        .setSecureRandom(new SecureRandom())
                        .setProvider("BC"));

        encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey)
                .setProvider("BC"));

        // 直接流式处理:读取原文件 -> 压缩 -> 生成字面数据 -> 加密
        try (OutputStream cOut = encGen.open(encOut, new byte[4096]);
             PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
             OutputStream pOut = lData.open(cOut, PGPLiteralData.BINARY, 
                     new File(inputFileName).getName(), new Date(), new byte[4096]);
             GZIPOutputStream gzipOut = new GZIPOutputStream(pOut);
             InputStream in = new BufferedInputStream(new FileInputStream(inputFileName))) {

            byte[] buf = new byte[4096];
            int len;
            while ((len = in.read(buf)) > 0) {
                gzipOut.write(buf, 0, len);
            }
        }
    }
}

可选:在加密内容中显式附带收件人邮箱

如果需要在解密后能直接看到收件人邮箱,可以在生成字面数据时,先把邮箱信息写入流中(会被一起加密,只有解密后可见):

try (OutputStream pOut = lData.open(cOut, PGPLiteralData.BINARY, 
         new File(inputFileName).getName(), new Date(), new byte[4096]);
     GZIPOutputStream gzipOut = new GZIPOutputStream(pOut);
     InputStream in = new BufferedInputStream(new FileInputStream(inputFileName))) {

    // 先写入收件人邮箱信息(加密后存储,解密可见)
    gzipOut.write(("Recipient: example@gmail.com\n").getBytes(StandardCharsets.UTF_8));
    
    // 再写入原文件内容
    byte[] buf = new byte[4096];
    int len;
    while ((len = in.read(buf)) > 0) {
        gzipOut.write(buf, 0, len);
    }
}

备注:内容来源于stack exchange,提问作者Hiep To

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:04:35