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

Bouncy Castle(Java)如何正确生成兼容GnuPG的带EC密钥的PGP密钥环

问题根因

你遇到的两个报错均是代码不符合OpenPGP关于Curve25519系列密钥的规范导致的:

  • 报_gcry_mpi_ec_add_points: Montgomery not yet supported错误:ECDSA算法仅支持短魏尔斯特拉斯形式的椭圆曲线,你强制将蒙哥马利形式的Curve25519搭配ECDSA算法使用,GnuPG底层libgcrypt做签名校验时无法完成蒙哥马利曲线的ECDSA运算直接崩溃。
  • 子密钥无E权限、SHA1告警:算法不匹配导致主密钥自签名校验部分失败,GnuPG拒绝读取子密钥的权限标志;同时你使用SHA1作为主密钥的签名摘要,不符合现代OpenPGP实现的安全要求。
  • 额外错误:你给仅支持签名的Ed25519主密钥添加了加密权限标志,且ECDH子密钥未添加OpenPGP强制要求的KDF参数,也会导致解析异常。
修复方案

按如下规则修改代码即可兼容GnuPG和Thunderbird:

  1. 拆分曲线与算法的对应关系:签名主密钥使用Ed25519曲线+EDDSA算法,加密子密钥使用X25519曲线+ECDH算法
  2. 删除主密钥的加密类权限标志
  3. 为ECDH子密钥补充KDF参数
  4. 统一使用SHA256作为摘要算法,移除SHA1的使用

修改后的核心代码如下:

private static final Provider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
// 拆分两个曲线OID
private static final ASN1ObjectIdentifier ED25519_OID = EdDSAObjectIdentifiers.ed25519;
private static final ASN1ObjectIdentifier X25519_OID = CryptlibObjectIdentifiers.curvey25519;

// 主密钥用EDDSA而非ECDSA
private static final int MASTER_KEY_ALGORITHM = PublicKeyAlgorithmTags.EDDSA;
private static final int SUB_KEY_ALGORITHM = PublicKeyAlgorithmTags.ECDH;

// 主密钥去掉加密相关权限
private static final int MASTER_KEY_FLAGS = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA | KeyFlags.AUTHENTICATION;
private static final int SUB_KEY_FLAGS = KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;

private static final int[] PREFERRED_HASH_ALGORITHMS = {HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA224};
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = {SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128};

public PGPKeyRingGenerator createPGPKeyRingGenerator(String identity, String passphrase)
        throws PGPException, InvalidAlgorithmParameterException, IOException {
    // 分别生成两个不同曲线的密钥对
    PGPKeyPair masterKeyPair = generateEd25519PgpKeyPair();
    PGPKeyPair subKeyPair = generateX25519PgpKeyPair();

    PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256);

    PGPSignatureSubpacketVector masterKeySubPacket = generateMasterkeySubpacket(MASTER_KEY_FLAGS);
    PGPSignatureSubpacketVector subKeySubPacket = generateSubkeySubpacket(SUB_KEY_FLAGS);

    PGPKeyRingGenerator keyRingGenerator =
            new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, identity, sha256Calc, masterKeySubPacket,
                    null,
                    new JcaPGPContentSignerBuilder(masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256)
                            .setProvider(BOUNCY_CASTLE_PROVIDER),
                    new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256, sha256Calc)
                            .setProvider(BOUNCY_CASTLE_PROVIDER)
                            .build(passphrase.toCharArray()));

    keyRingGenerator.addSubKey(subKeyPair, subKeySubPacket, null);

    return keyRingGenerator;
}

// 生成Ed25519原生密钥对
private AsymmetricCipherKeyPair generateEd25519KeyPair() {
    Ed25519KeyPairGenerator keyPairGenerator = new Ed25519KeyPairGenerator();
    keyPairGenerator.init(new Ed25519KeyGenerationParameters(new SecureRandom()));
    return keyPairGenerator.generateKeyPair();
}

// 生成X25519原生密钥对
private AsymmetricCipherKeyPair generateX25519KeyPair() {
    X25519KeyPairGenerator keyPairGenerator = new X25519KeyPairGenerator();
    keyPairGenerator.init(new X25519KeyGenerationParameters(new SecureRandom()));
    return keyPairGenerator.generateKeyPair();
}

private BcPGPKeyPair generateEd25519PgpKeyPair() throws PGPException {
    return new BcPGPKeyPair(MASTER_KEY_ALGORITHM, generateEd25519KeyPair(), new Date());
}

private BcPGPKeyPair generateX25519PgpKeyPair() throws PGPException {
    return new BcPGPKeyPair(SUB_KEY_ALGORITHM, generateX25519KeyPair(), new Date());
}

private PGPSignatureSubpacketVector generateMasterkeySubpacket(int keyFlags) {
    PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
    subpacketGen.setKeyFlags(false, keyFlags);
    subpacketGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS);
    subpacketGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS);
    return subpacketGen.generate();
}

private PGPSignatureSubpacketVector generateSubkeySubpacket(int keyFlags) throws IOException {
    PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
    subpacketGen.setKeyFlags(false, keyFlags);
    // 补充ECDH子密钥必须的KDF参数
    subpacketGen.setKeyUsage(false, KeyUsageFlags.keyEncipherment);
    subpacketGen.setKDFParameters(false, HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_256);
    return subpacketGen.generate();
}
验证效果

修改后生成的密钥环可以直接被GnuPG 2.2+、Thunderbird 78+正常识别,主密钥显示[SC]权限,子密钥显示[E]权限,无SHA1告警。

内容的提问来源于stack exchange,提问作者Pawel Os.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 06:48:04