PGP签名验证遭遇invalid header异常,寻求解决方案
PGP签名验证触发"invalid header encountered"异常解决方案
需求
- 签名哈希算法:SHA256
- 发送方使用自身私钥对消息签名
- 接收方使用发送方公钥验证签名
问题现象
验证签名时抛出如下异常:
Exception in thread "main" java.io.IOException: invalid header encountered at org.bouncycastle.bcpg.BCPGInputStream.readPacket(Unknown Source) at org.bouncycastle.openpgp.PGPLiteralData.(Unknown Source) at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source) at com.groupId.auth.PGPExample.verify(PGPExample.java:253) at com.groupId.auth.PGPExample.main(PGPExample.java:52)
异常出现在代码行while ((obj = pgpObjectFactory.nextObject()) != null) {...}的pgpObjectFactory.nextObject()调用处。
问题根源分析
- 签名编码与解码不匹配:签名方法使用
ArmoredOutputStream生成ASCII装甲格式的签名,但验证方法直接将装甲格式的字节流传入PGPObjectFactory,未进行装甲解码,导致PGP解析器无法识别格式头。 - 公钥使用错误:验证时误用了接收方公钥,实际应该使用发送方的公钥来验证签名。
- 消息处理冗余:签名时对普通字符串消息调用
PGPUtil.getDecoderStream,这会错误地将消息当作PGP装甲格式解码,破坏原始消息内容。
修改后的代码实现
修正后的签名方法
private static byte[] sign(String message) throws IOException, PGPException { ByteArrayOutputStream out = new ByteArrayOutputStream(); // 生成ASCII装甲格式的签名输出流 ArmoredOutputStream signedOut = new ArmoredOutputStream(out); PGPSecretKey senderPrivateKey = PGPUtils.getSenderPrivateKey(); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( new JcaPGPContentSignerBuilder(senderPrivateKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256) .setProvider("BC") ); String password = "password"; // 初始化签名生成器,使用BINARY_DOCUMENT类型 signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, senderPrivateKey.extractPrivateKey( new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(password.toCharArray()))); // 添加签名者用户ID(可选) Iterator<?> it = senderPrivateKey.getPublicKey().getUserIDs(); if (it.hasNext()) { PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.addSignerUserID(false, (String) it.next()); signatureGenerator.setHashedSubpackets(spGen.generate()); } // 直接使用原始消息字节流,无需装甲解码 InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); byte[] buf = new byte[8192]; int len; while ((len = in.read(buf)) > 0) { signatureGenerator.update(buf, 0, len); } in.close(); // 生成并编码签名 signatureGenerator.generate().encode(new BCPGOutputStream(signedOut)); // 关闭流 signedOut.close(); out.close(); return out.toByteArray(); }
修正后的验证方法
private static boolean verify(String message, byte[] signature) throws IOException, PGPException { // 先对ASCII装甲格式的签名进行解码 InputStream decodedIn = PGPUtil.getDecoderStream(new ByteArrayInputStream(signature)); PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(decodedIn, new JcaKeyFingerprintCalculator()); Object obj; while ((obj = pgpObjectFactory.nextObject()) != null) { if (obj instanceof PGPSignatureList) { PGPSignatureList signatureList = (PGPSignatureList) obj; if (signatureList.isEmpty()) { throw new IllegalArgumentException("No signature found in the data"); } PGPSignature pgpSignature = signatureList.get(0); // 使用发送方公钥初始化验证器,而非接收方公钥 pgpSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), PGPUtils.getSenderPublicKey()); // 更新原始消息内容 pgpSignature.update(message.getBytes(StandardCharsets.UTF_8)); return pgpSignature.verify(); } } throw new IllegalArgumentException("No signature found in the data"); }
主方法(补充message定义)
public static void main(String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); String message = "待签名的测试消息"; // 补充定义message变量 byte[] signature = sign(message); System.out.println("Signature: "); System.out.println(new String(signature)); boolean verified = verify(message, signature); System.out.println("Signature Verified: " + verified); }
关键修改点说明
- 签名解码:验证时通过
PGPUtil.getDecoderStream处理签名输入流,将ASCII装甲格式转换为PGP可解析的二进制格式。 - 公钥修正:将验证时的公钥替换为发送方公钥,这是签名验证的核心逻辑——必须用签名者的公钥验证其私钥生成的签名。
- 消息处理优化:签名时直接使用原始消息字节流,避免不必要的装甲解码操作,保证消息内容的完整性。
内容的提问来源于stack exchange,提问作者MM Ary
相关产品推荐
相关产品推荐

