如何确定PDF的PreferredSignatureSize并最小化签名后文件体积
问题1:正确设置
SignatureOptions.PreferredSignatureSize 核心原理
这个参数的作用是在PDF中预留给签名数据的字节空间,设置过小会导致签名数据无法写入(抛出空间不足异常),设置过大则会凭空增加PDF体积。注意:签名数据的大小和原PDF文档体积无关,主要取决于你的证书链长度、签名算法(如RSA密钥长度)以及签名属性的多少,你之前观察到的“大文档适用、小文档不适用”本质是小文档预留过大空间的浪费更明显。
正确设置方法
方法1:动态计算实际所需空间
先模拟生成签名数据,获取其真实长度,再预留10%-20%的缓冲空间,避免因签名属性微小变化导致空间不足:
// 在签名接口外先模拟生成签名数据,计算实际大小 byte[] dummyContent = new byte[100]; // 用任意内容模拟,不影响计算结果 CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); X509Certificate cert = (X509Certificate) certificateChain[0]; gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain))); JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256withRSA"); contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME); gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder() .setProvider(BouncyCastleProvider.PROVIDER_NAME) .build()) .build(contentSignerBuilder.build(privateKey), cert)); CMSProcessableByteArray msg = new CMSProcessableByteArray(dummyContent); CMSSignedData signedData = gen.generate(msg, true); int actualSignatureSize = signedData.getEncoded().length; int preferredSize = actualSignatureSize + (actualSignatureSize / 5); // 预留20%缓冲
然后将代码中signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 1000);替换为signatureOptions.setPreferredSignatureSize(preferredSize);。
方法2:保守固定值设置
如果不想提前计算,可以先设置DEFAULT_SIGNATURE_SIZE * 2的固定值,若出现空间不足异常,再逐步增大。但推荐方法1,能精准控制预留空间,减少体积浪费。
问题2:最小化签名后的PDF体积
1. 优化可见签名图片
你代码中生成的PNG图片是体积膨胀的主要原因之一,可做以下优化:
- 改用JPEG格式(无需完全透明时,设置白色背景):
// 生成不透明背景的图片,改用JPEG保存 BufferedImage image = new BufferedImage(150, 50, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = image.createGraphics(); g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, 150, 50); // 后续文字绘制逻辑不变,适当缩小字体适配尺寸 g2d.setFont(new Font("Arial", Font.PLAIN, 8)); g2d.drawString("Signed by: " + signatureName, 10, 20); g2d.drawString("Location: " + signatureLocation, 10, 35); g2d.dispose();
- 压缩JPEG质量:保存时设置压缩参数,在清晰度和体积间平衡:
tempImageFile = File.createTempFile("signature", ".jpg"); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(0.7f); // 0.7的质量足够清晰,体积可减少60%以上 try (FileOutputStream fos = new FileOutputStream(tempImageFile)) { writer.setOutput(ImageIO.createImageOutputStream(fos)); writer.write(null, new IIOImage(image, null, null), param); } writer.dispose();
2. 精准设置PreferredSignatureSize
如问题1所述,避免预留过大的空白空间,这能直接减少PDF中无效的占位字节。
3. 优化签名数据
- 精简证书链:如果证书链中包含普遍信任的根证书,可以只添加用户证书和中间证书,减少签名数据体积;
- 精简签名属性:默认的
messageDigest和signingTime属性已满足需求,不要添加多余的自定义属性。
4. 保留增量保存
你的代码已经使用document.saveIncremental(baos),这是正确的——增量保存只添加签名相关数据,不会重写整个PDF文档,比全量保存(save())体积小很多,务必保留。
整合优化后的关键代码片段
// 生成压缩后的JPEG签名图片 BufferedImage image = new BufferedImage(150, 50, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = image.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, 150, 50); g2d.setColor(Color.BLACK); g2d.setFont(new Font("Arial", Font.PLAIN, 8)); g2d.drawString("Signed by: " + signatureName, 10, 20); g2d.drawString("Location: " + signatureLocation, 10, 35); g2d.dispose(); // 保存带压缩的JPEG图片 tempImageFile = File.createTempFile("signature", ".jpg"); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(0.7f); try (FileOutputStream fos = new FileOutputStream(tempImageFile)) { writer.setOutput(ImageIO.createImageOutputStream(fos)); writer.write(null, new IIOImage(image, null, null), param); } writer.dispose(); // 动态计算签名所需空间 byte[] dummyContent = new byte[100]; CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); X509Certificate cert = (X509Certificate) certificateChain[0]; gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain))); JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256withRSA"); contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME); gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder() .setProvider(BouncyCastleProvider.PROVIDER_NAME) .build()) .build(contentSignerBuilder.build(privateKey), cert)); CMSProcessableByteArray msg = new CMSProcessableByteArray(dummyContent); CMSSignedData signedData = gen.generate(msg, true); int actualSize = signedData.getEncoded().length; int preferredSize = actualSize + (actualSize / 5); // 设置签名选项 signatureOptions = new SignatureOptions(); signatureOptions.setVisualSignature(visibleSignProperties.getVisibleSignature()); signatureOptions.setPreferredSignatureSize(preferredSize);
内容的提问来源于stack exchange,提问作者Vikash Yadav
相关产品推荐
相关产品推荐

