分布式系统中基于ESP的两阶段PDF数字签名实现问题排查
基于ESP(电子签名服务提供商)的两阶段PDF数字签名实现问题
签名流程
- 第一阶段:
- 生成文档哈希值;
- 使用ESP的私钥加密该哈希值;
- 创建签名请求并跳转至ESP的UIDAI认证页面完成认证。
- 第二阶段:从ESP提供的另一接口接收签名字节。
遇到的挑战
- PdfSignatureAppearance对象持久化问题:由于签名分为两阶段,需保持该对象活跃直至签名完成;
- 证书插入问题:从ESP响应中插入证书后,Adobe验证签名时提示「无效签名:文档已被修改。」;
- 希望该服务适配分布式系统架构。
生成哈希的代码
public String pdfSigner(File file, HttpServletRequest request, HttpSession session) { String hashDocument = null; PdfReader reader; try { String sourcefile = file.getAbsolutePath(); System.out.println("Path--->" + sourcefile); destFile = sourcefile.replace(file.getName(), "Signed_Pdf.pdf"); request.getSession().setAttribute("fileName", "Signed_Pdf.pdf"); reader = new PdfReader(sourcefile); Rectangle cropBox = reader.getCropBox(1); Rectangle rectangle = null; String user = null; rectangle = new Rectangle(cropBox.getLeft(), cropBox.getBottom(), cropBox.getLeft(100), cropBox.getBottom(90)); fout = new FileOutputStream(destFile); PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0', null, true); appearance = stamper.getSignatureAppearance(); appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); appearance.setAcro6Layers(false); Font font = new Font(); font.setSize(6); font.setFamily("Helvetica"); font.setStyle("italic"); appearance.setLayer2Font(font); Calendar currentDat = Calendar.getInstance(); System.out.println("remove 5 min"); currentDat.add(currentDat.MINUTE, 5); //Adding Delta Time of 5 Minutes.... appearance.setSignDate(currentDat); if (user == null || user == "null" || user.equals(null) || user.equals("null")) { appearance.setLayer2Text("Signed"); } else { appearance.setLayer2Text("Signed by " + user); } appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); appearance.setImage(null); // appearance.setSignDate(currentDat); appearance.setVisibleSignature(rectangle, reader.getNumberOfPages(), null); int contentEstimated = 8192; HashMap<PdfName, Integer> exc = new HashMap(); exc.put(PdfName.CONTENTS, contentEstimated * 2 + 2); PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); dic.setReason(appearance.getReason()); dic.setLocation(appearance.getLocation()); dic.setDate(new PdfDate(appearance.getSignDate())); appearance.setCryptoDictionary(dic); // request.getSession().setAttribute("pdfHash",appearance); appearance.preClose(exc); // fout.close(); request.getSession().setAttribute("appearance", appearance); // System.gc(); // getting bytes of file InputStream is = appearance.getRangeStream(); hashDocument = DigestUtils.sha256Hex(is); //session=request.getSession(); //session.setAttribute("appearance1",appearance); System.out.println("hex: " + is.toString()); } catch (Exception e) { System.out.println("Error in signing doc."); } return hashDocument; }
嵌入签名的代码
public String signPdfwithDS(String response, HttpServletRequest request, HttpSession session) { int contentEstimated = 8192; try { String errorCode = response.substring(response.indexOf("errCode"), response.indexOf("errMsg")); errorCode = errorCode.trim(); if (errorCode.contains("NA")) { String pkcsResponse = xmlSigning.parseXml(response.trim()); byte[] sigbytes = Base64.decodeBase64(pkcsResponse); byte[] paddedSig = new byte[contentEstimated]; System.arraycopy(sigbytes, 0, paddedSig, 0, sigbytes.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true)); //fout.close(); appearance.close(dic2); } else { destFile = "Error"; } } catch (Exception e) { e.printStackTrace(); } System.out.println("Hash after signing :" + calculateSha256Hash(destFile)); return destFile; }
插入证书的代码
String certString = response.substring(response.indexOf(User_X509_START_TAG), response.indexOf(User_X509_END_TAG)) .replaceAll(User_X509_START_TAG, "").replaceAll(User_X509_END_TAG, ""); byte[] certBytes = Base64.decodeBase64(certString); ByteArrayInputStream stream = new ByteArrayInputStream(certBytes); CertificateFactory factory = CertificateFactory.getInstance(X_509); Certificate cert = factory.generateCertificate(stream); List<Certificate> certificates = List.of(cert); appearance.setCrypto(null, certificates.toArray(new Certificate[0]), null, null);
更新:第二种方案的问题
采用第二种方案后,Adobe提示「此签名的格式或包含的信息存在错误」,相关代码如下:
生成哈希的V2代码
public String pdfSignerV2(Resource resource, ESignParameter eSignParameter) { String hashDocument = null; try { PdfReader reader = new PdfReader(resource.getInputStream()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); String signPlaceHolder = eSignParameter.getSignPlaceHolder(); PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0', null, true); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); appearance.setAcro6Layers(false);// deprecated Coordinate locationToSign = findLocationToSign(reader, signPlaceHolder); Rectangle rectangle = new Rectangle(locationToSign.getX(), locationToSign.getY(), locationToSign.getX() + (100), locationToSign.getY() + (50)); Font font = new Font(); font.setSize(6); font.setFamily(FONT_FAMILY); font.setStyle(FONT_STYLE); appearance.setLayer2Font(font); Calendar currentDat = Calendar.getInstance(); appearance.setSignDate(currentDat); appearance.setLayer2Text("Digitally Signed"); appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); appearance.setImage(null); appearance.setVisibleSignature(rectangle, locationToSign.getPageNumber(), signPlaceHolder); int contentEstimated = 8192; MyExternalSignatureContainer container = new MyExternalSignatureContainer(new byte[]{0}); MakeSignature.signExternalContainer(appearance, container, contentEstimated); InputStream is = appearance.getRangeStream(); hashDocument = DigestUtils.sha256Hex(is); MultipartFile dummySignedPdf = new ByteArrayMultipartFile(FILE_NAME, baos.toByteArray()); String dummyFileStoreId = fileStoreUtil.storeFileInFileStore(dummySignedPdf, "kl"); eSignParameter.setFileStoreId(dummyFileStoreId); stamper.close(); } catch (Exception e) { throw new RuntimeException(e); } return hashDocument; }
完成签名的V2代码
public MultipartFile signPdfWithDSAndReturnMultipartFileV2(Resource resource, String response, ESignParameter eSignParameter) { try { int contentEstimated = 8192; PdfReader reader = new PdfReader(resource.getInputStream()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); String pkcsResponse = new XmlSigning().parseXml(response.trim()); byte[] sigbytes = Base64.decodeBase64(pkcsResponse); byte[] paddedSig = new byte[contentEstimated]; System.arraycopy(sigbytes, 0, paddedSig, 0, sigbytes.length); MyExternalSignatureContainer container = new MyExternalSignatureContainer(paddedSig); MakeSignature.signDeferred(reader, eSignParameter.getSignPlaceHolder(), baos, container); return new ByteArrayMultipartFile(FILE_NAME, baos.toByteArray()); } catch (Exception e) { throw new RuntimeException(e); } }
内容的提问来源于stack exchange,提问作者Prasanna
相关产品推荐
相关产品推荐

