使用BouncyCastle解析EdDSA类型PGP公钥时指纹异常且UserID为空
问题与解决方案
问题描述
使用BouncyCastle解析PGP公钥时,RSA加密生成的公钥可正常读取信息,但EdDSA生成的公钥出现异常:获取到的指纹与Kleopatra显示的不一致,且UserIDs为空。
依赖与测试代码
依赖配置
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.72</version> </dependency>
测试代码
@Test public void test() throws NoSuchProviderException, IOException, PGPException { InputStream in = PGPUtil.getDecoderStream(new FileInputStream("public_EdDsa.asc")); PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new JcaKeyFingerprintCalculator()); List<PGPPublicKey> keys = new ArrayList<>(); Iterator<PGPPublicKeyRing> rIt = pgpPub.getKeyRings(); while (CollectionUtils.isEmpty(keys) && rIt.hasNext()) { PGPPublicKeyRing kRing = rIt.next(); Iterator<PGPPublicKey> kIt = kRing.getPublicKeys(); while (CollectionUtils.isEmpty(keys) && kIt.hasNext()) { PGPPublicKey k = kIt.next(); if (k.isEncryptionKey()) { keys.add(k); } } } for (PGPPublicKey pgpPublicKey : keys) { System.out.println(pgpPublicKey.getKeyID()); byte[] fingerprint = pgpPublicKey.getFingerprint(); for (byte b : fingerprint) { System.out.print(b); } System.out.println(); System.out.println(byte2Hex(fingerprint)); Iterator<String> userIDs = pgpPublicKey.getUserIDs(); while (userIDs.hasNext()){ String next = userIDs.next(); System.out.println(next); } System.out.println("============end============"); } }
解决方案
1. 升级BouncyCastle版本
BouncyCastle 1.72对EdDSA的PGP支持存在缺陷,建议升级到1.73及以上稳定版本(如1.77),后续版本修复了EdDSA密钥的解析问题,能正确读取指纹和关联信息。
修改依赖:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.77</version> </dependency>
2. 修正密钥筛选逻辑
EdDSA在PGP体系中默认作为签名密钥使用,而非加密密钥。原代码仅筛选isEncryptionKey()的密钥,会漏掉EdDSA主密钥,导致读取到的是无关子密钥(或无密钥),进而出现指纹不匹配、UserIDs为空的问题。
修改筛选条件,同时包含签名密钥:
// 替换原有的if判断 if (k.isEncryptionKey() || k.isSigningKey()) { keys.add(k); }
另外,原代码中CollectionUtils.isEmpty(keys) && ...的逻辑会在找到第一个符合条件的密钥后停止遍历,建议去掉该限制,遍历所有密钥以确认主密钥信息。
3. 修复指纹显示逻辑
直接打印byte数组的原始值会因byte的有符号特性导致显示异常,需确保byte2Hex方法将byte作为无符号值转换为十六进制:
private static String byte2Hex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b & 0xFF)); } return sb.toString(); }
修改后的完整测试代码
@Test public void test() throws NoSuchProviderException, IOException, PGPException { InputStream in = PGPUtil.getDecoderStream(new FileInputStream("public_EdDsa.asc")); PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new JcaKeyFingerprintCalculator()); List<PGPPublicKey> keys = new ArrayList<>(); Iterator<PGPPublicKeyRing> rIt = pgpPub.getKeyRings(); while (rIt.hasNext()) { PGPPublicKeyRing kRing = rIt.next(); Iterator<PGPPublicKey> kIt = kRing.getPublicKeys(); while (kIt.hasNext()) { PGPPublicKey k = kIt.next(); if (k.isEncryptionKey() || k.isSigningKey()) { keys.add(k); } } } for (PGPPublicKey pgpPublicKey : keys) { System.out.println("密钥ID: " + Long.toHexString(pgpPublicKey.getKeyID()).toUpperCase()); byte[] fingerprint = pgpPublicKey.getFingerprint(); System.out.println("指纹十六进制: " + byte2Hex(fingerprint)); Iterator<String> userIDs = pgpPublicKey.getUserIDs(); while (userIDs.hasNext()){ String next = userIDs.next(); System.out.println("用户ID: " + next); } System.out.println("密钥用途: " + (pgpPublicKey.isSigningKey() ? "签名" : "") + (pgpPublicKey.isEncryptionKey() ? "加密" : "")); System.out.println("============end============"); } } private static String byte2Hex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b & 0xFF)); } return sb.toString(); }
内容的提问来源于stack exchange,提问作者Lei
相关产品推荐
相关产品推荐

