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:
- 拆分曲线与算法的对应关系:签名主密钥使用Ed25519曲线+EDDSA算法,加密子密钥使用X25519曲线+ECDH算法
- 删除主密钥的加密类权限标志
- 为ECDH子密钥补充KDF参数
- 统一使用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.
相关产品推荐
相关产品推荐

