传入临时文件参数后无法获取PdfSignatureAppearance的rangeStream
问题:使用临时文件处理大PDF签名时,
appearance.getRangeStream()抛出流已关闭异常 我在使用iText进行PDF签名开发时,遇到以下问题:
- 当调用
PdfStamper.createSignature时指定了临时文件参数(第四个参数,用于处理大文件签名),执行MakeSignature.signExternalContainer后调用appearance.getRangeStream()会抛出IOException,提示流已关闭。 - 如果将临时文件参数设为null,就能正常获取该流,但临时文件是处理大PDF签名必需的,不能移除。
以下是我的代码片段:
os = new FileOutputStream(rutaDestinoArchivo); PdfStamper stamper; if (claveEncriptar != null && !claveEncriptar.isEmpty()) { stamper = PdfStamper.createSignature(reader, os, '\0', new File(rutaDestinoArchivo + ".tmp"), false); stamper.setEncryption(claveEncriptar.getBytes(), claveEncriptar.getBytes(), PdfWriter.ALLOW_PRINTING, PdfWriter.ENCRYPTION_AES_128 | PdfWriter.DO_NOT_ENCRYPT_METADATA); } else { stamper = PdfStamper.createSignature(reader, os, '\0', new File(rutaDestinoArchivo + ".tmp"), true); } PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); if (!seguirFirmando) { appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED); } motivo = " " + motivo + " "; if (motivo.length() > 0) { String[] parts = motivo.split("@motloc@"); String part11 = parts[0].trim(); String part21 = parts[1].trim(); motivo = ""; localidad = ""; if (part11.length() > 0) { motivo = part11; } if (part21.length() > 0) { localidad = part21; } } else { motivo = ""; localidad = ""; } appearance.setReason(motivo); appearance.setLocation(localidad); String ttitulo = tituloFirma; if (ttitulo.length() == 0) { ttitulo = "FIRMA DIGITAL"; } Integer ttamletra = tamanoLetra; if (ttamletra > 0) { appearance.setLayer2Font(new Font(Font.FontFamily.UNDEFINED, ttamletra)); } if (descripcion != null && !descripcion.trim().isEmpty()) { appearance.setLayer2Text(descripcion); } appearance.setLayer4Text(ttitulo); appearance.setAcro6Layers(true); appearance.setImageScale(0.5f); appearance.setRunDirection(PdfWriter.RUN_DIRECTION_LTR); if (firmaVisible) { DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(); this.nombreFirma = ((nombreFirma != null && !nombreFirma.trim().isEmpty()) ? nombreFirma : "sig" + dateFormat.format(date)); if (rutaArchivoImagen == null || rutaArchivoImagen.isEmpty()) { appearance.setRenderingMode(RenderingMode.DESCRIPTION); } else { appearance.setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION); Image watermark = Image.getInstance(rutaArchivoImagen); appearance.setSignatureGraphic(watermark); } appearance.setVisibleSignature(new Rectangle(x, y, x + ancho, y + alto), pagina, this.nombreFirma); } ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); MakeSignature.signExternalContainer(appearance, external, tamanoFirma); InputStream inp = appearance.getRangeStream(); // 此处抛出IOException:流已关闭
解决方案
问题根源
当使用临时文件参数时,MakeSignature.signExternalContainer执行完成后会自动关闭所有相关流(包括rangeStream),导致后续无法从appearance中获取该流。而不指定临时文件时,流会保留在内存中,因此可以正常获取。
解决步骤
核心思路是在签名容器的sign方法执行过程中获取并缓存rangeStream,而不是在方法执行完毕后从appearance中获取。
- 自定义
ExternalSignatureContainer子类,用于缓存rangeStream
class CachedRangeSignatureContainer implements ExternalSignatureContainer { private final ExternalSignatureContainer delegate; private InputStream rangeStream; public CachedRangeSignatureContainer(ExternalSignatureContainer delegate) { this.delegate = delegate; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { // 缓存要签名的数据流(即rangeStream) this.rangeStream = data; // 委托给原容器处理签名逻辑 return delegate.sign(data); } // 提供获取缓存流的方法 public InputStream getRangeStream() { return rangeStream; } }
- 修改原代码中的签名容器调用逻辑
将原来的ExternalBlankSignatureContainer替换为自定义的缓存容器,并从自定义容器中获取rangeStream:
// 替换原签名容器创建逻辑 CachedRangeSignatureContainer cachedContainer = new CachedRangeSignatureContainer( new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED) ); MakeSignature.signExternalContainer(appearance, cachedContainer, tamanoFirma); // 从自定义容器中获取rangeStream,而非appearance InputStream inp = cachedContainer.getRangeStream();
- 优化建议
如果你的需求是计算PDF的哈希值,可以直接在sign方法中对data流进行哈希计算,无需缓存流,这样能避免额外的资源占用:
class HashCalculatingSignatureContainer implements ExternalSignatureContainer { private final ExternalSignatureContainer delegate; private byte[] documentHash; public HashCalculatingSignatureContainer(ExternalSignatureContainer delegate) { this.delegate = delegate; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { try { // 计算SHA-256哈希(可根据需求替换算法) MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[8192]; int read; while ((read = data.read(buffer)) != -1) { digest.update(buffer, 0, read); } documentHash = digest.digest(); // 重置流,确保原签名容器能正常读取 data.reset(); } catch (IOException e) { throw new GeneralSecurityException("Failed to calculate document hash", e); } return delegate.sign(data); } public byte[] getDocumentHash() { return documentHash; } }
使用时直接获取哈希值即可:
HashCalculatingSignatureContainer hashContainer = new HashCalculatingSignatureContainer( new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED) ); MakeSignature.signExternalContainer(appearance, hashContainer, tamanoFirma); byte[] pdfHash = hashContainer.getDocumentHash();
内容的提问来源于stack exchange,提问作者Cesar Palomino Salas
相关产品推荐
相关产品推荐

