You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

分布式系统中基于ESP的两阶段PDF数字签名实现问题排查

基于ESP(电子签名服务提供商)的两阶段PDF数字签名实现问题

签名流程

  • 第一阶段:
    1. 生成文档哈希值;
    2. 使用ESP的私钥加密该哈希值;
    3. 创建签名请求并跳转至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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 02:57:34