简体   繁体   English

使用iText(signdeferred)创建PDF数字签名,验证签名时出现无效签名问题

[英]Using iText (signdeferred) to create PDF digital signature, invalid signature problem appears when verifying signature

I am a Chinese software developer, I am now implementing such a function, using Android client to digitally sign PDF, my implementation is like this我是中国软件开发人员,我现在正在实现这样一个function,使用Android客户端数字签名PDF,我的实现是这样的

  1. Create a blank signature on the server在服务器上创建一个空白签名
  2. Send PDF hash with blank signature to Android client, and Android client signs hash Send PDF hash with blank signature to Android client, and Android client signs hash
  3. Use makesignature.使用 makesignature。 Signdeferred () to merge the signature content in the server Now I encounter such a problem that the PDF after signing cannot be verified by the PDF reader. Signdeferred() 合并服务器中的签名内容现在遇到这样一个问题,签名后的PDF无法被PDF阅读器验证。 It shows that the PDF file has been tampered, It should be noted that I use sm3withsm2 algorithm.显示PDF文件被篡改,需要注意的是我使用的是sm3withsm2算法。 Adobe reader can't verify it. Adobe reader 无法验证。 We have our own reader我们有自己的读者

https://drive.google.com/file/d/127nVvJ0qtSdG53jM0_GUP-WORYrQ5TBo/view?usp=sharing Now I add the PDF file address, who can help me analyze the problem https://drive.google.com/file/d/127nVvJ0qtSdG53jM0_GUP-WORYrQ5TBo/view?usp=sharing现在我添加PDF文件地址,谁能帮我分析问题

 /**
 * @return name.ldd.electSign.webservice.pdf.presign.PresignResponseDetail
 * @Author 焦康
 * @Description 预签章
 * @Date 2021/4/25 14:35
 * @Param [pdfReader, tempFile, temp, chain, positions, fieldName, calendar, hashName]
 **/
public PreSignResponseDetail preSign(PreSignDAO preSignDAO) throws IOException, DocumentException, GeneralSecurityException {
    log.debug("执行预签章");
    FileOutputStream fileOutputStream = new FileOutputStream(preSignDAO.getTempPath());
    // 设置印章信息
    preSignDAO.getPdfReader().setAppendable(true);
    PdfStamper pdfStamper = PdfStamper.createSignature(preSignDAO.getPdfReader(), fileOutputStream, '\0', null, true);
    pdfStamper.getWriter().setCompressionLevel(PdfStream.BEST_COMPRESSION);
    PdfSignatureAppearance pdfSignatureAppearance = pdfStamper.getSignatureAppearance();
    pdfSignatureAppearance.setReason("");
    pdfSignatureAppearance.setLocation("");
    pdfSignatureAppearance.setContact("");
    pdfSignatureAppearance.setLayer2Text("");
    pdfSignatureAppearance.setLayer4Text("");
    pdfSignatureAppearance.setSignatureCreator("");
    pdfSignatureAppearance.setOpacity(preSignDAO.getSealOpacity());
    pdfSignatureAppearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
    pdfSignatureAppearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
    pdfSignatureAppearance.setCertificate(preSignDAO.getCertificateChain()[0]);
    pdfSignatureAppearance.setVisibleSignature(new Rectangle(200, 200, 300, 300), preSignDAO.getPage(), preSignDAO.getFieldName());
    // 设置签名图片
    //读取图章图片,这个image是itext包的image
    Image image = Image.getInstance(preSignDAO.getSealImagePath());
    pdfSignatureAppearance.setImage(image);
    // 开始预签章
    ExternalSignatureContainer externalBlankSignatureContainer = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(pdfSignatureAppearance, externalBlankSignatureContainer, 8192);
    // 计算hash
    InputStream inputStream = pdfSignatureAppearance.getRangeStream();
    BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
    byte[] hash = DigestAlgorithms.digest(inputStream, bouncyCastleDigest.getMessageDigest(preSignDAO.getHashAlgorithm()));
    PdfPKCS7 pkcs7 = new PdfPKCS7(null, preSignDAO.getCertificateChain(), preSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
    byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash, Calendar.getInstance(), null, null, MakeSignature.CryptoStandard.CMS);
    String stringSh = new sun.misc.BASE64Encoder().encode(sh);
    return new PreSignResponseDetail(preSignDAO.getSourcePath(), preSignDAO.getTempPath(), hash, stringSh.replace("\r\n",""), preSignDAO.getFieldName());

}
/**
     * @Author 焦康
     * @Description 数据签名
     * @Date 2021/4/30 14:57
     * @Param [Pin, CN, signData, listener]
     * @return void
     **/
    public void ZAYK_SignData(final String Pin,final String CN,final String signData,HttpsCallbackListener listener){
        httpsCallbackListener = listener;
        new Thread(new Runnable() {
            @Override
            public void run() {
                String result = null;
                try {
                    byte[] bytes = Base64.decode(signData,Base64.DEFAULT);
                    result = service.SDZM_SignData(bytes,CN,Pin);
                    JSONObject jsonObject = JSONObject.parseObject(result);
                    result = jsonObject.getString("signValue");
                    Log.e("SDZM_SignData",result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sendMessage(result);
            }
        }).start();
    }
/**
     * @return
     * @Author 焦康
     * @Description 延迟签章
     * @Date 2021/4/25 17:01
     * @Param
     **/
    public DeferredSignResponseDetail DeferredSign(DeferredSignDAO deferredSignDAO) {
        log.debug("执行延迟签章");
        BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        PdfReader pdfReader = null;
        FileOutputStream os = null;
        try {
            PdfPKCS7 sgn = new PdfPKCS7(null, deferredSignDAO.getCertificateChain(), deferredSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
            sgn.setExternalDigest(deferredSignDAO.getSignData(), null, deferredSignDAO.getDigestEncryptionAlgorithm());
            byte[] sig = sgn.getEncodedPKCS7(deferredSignDAO.getHashData(), calendar, null, null, null, MakeSignature.CryptoStandard.CMS);
            pdfReader = new PdfReader(deferredSignDAO.getTempPath());
            os = new FileOutputStream("D:\\ruoyi\\uploadPath\\pdf\\Dest\\输出");
            ExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
            MakeSignature.signDeferred(pdfReader, deferredSignDAO.getFieldName(), os, external);
        } catch (Exception e) {
            log.error("延迟签章出错:", e);
        } finally {
            if (pdfReader != null) {
                pdfReader.close();
            }
            try {
                if (os != null) {
                    os.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return new DeferredSignResponseDetail(deferredSignDAO.getTargetPath() + ".pdf");
    }
public class MyExternalSignatureContainer implements ExternalSignatureContainer {
    byte[] sig = null;
    
    public MyExternalSignatureContainer(byte[] sig) {
        this.sig = sig;
    }

    @Override
    public byte[] sign(InputStream paramInputStream) throws GeneralSecurityException {
        return this.sig;
    }

    @Override
    public void modifySigningDictionary(PdfDictionary paramPdfDictionary) {
    }
}

When calculating the hash of the to-be-signed attributes, you use the then current time as value of the signing time attribute:在计算待签名属性的 hash 时,使用当时的当前时间作为签名时间属性的值:

PdfPKCS7 pkcs7 = new PdfPKCS7(null, preSignDAO.getCertificateChain(), preSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash, Calendar.getInstance(), null, null, MakeSignature.CryptoStandard.CMS);
String stringSh = new sun.misc.BASE64Encoder().encode(sh);

Later, when you have received a naked signature for that hash, you re-build the signed attributes using the then current time:稍后,当您收到该 hash 的裸签名时,您使用当时的当前时间重新构建签名属性:

Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
...
PdfPKCS7 sgn = new PdfPKCS7(null, deferredSignDAO.getCertificateChain(), deferredSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
sgn.setExternalDigest(deferredSignDAO.getSignData(), null, deferredSignDAO.getDigestEncryptionAlgorithm());
byte[] sig = sgn.getEncodedPKCS7(deferredSignDAO.getHashData(), calendar, null, null, null, MakeSignature.CryptoStandard.CMS);

Thus, the signing time in the signed attributes of the final CMS container differs from the signing time used for hashing, so the signature signs the wrong hash.因此,最终 CMS 容器的已签名属性中的签名时间与用于散列的签名时间不同,因此签名签署了错误的 hash。

To fix this use the identical signing time value in both places!要解决此问题,请在两个地方使用相同的签名时间值!


By the way, if you had used the current iText 5 version, you would not have had that problem: Because that signing time attribute is optional (as long as one sets the signing time in the signature dictionary) and even forbidden for PAdES signatures, the current iText 5 version does not set that attribute anymore and getAuthenticatedAttributeBytes and getEncodedPKCS7 don't have a Calendar parameter anymore...顺便说一句,如果您使用的是当前的 iText 5 版本,您就不会遇到这个问题:因为该签名时间属性是可选的(只要在签名字典中设置签名时间)甚至禁止 PAdES 签名,当前的 iText 5 版本不再设置该属性,并且getAuthenticatedAttributeBytesgetEncodedPKCS7不再具有Calendar参数...


As I have not yet dealt with the Chinese algorithms, I cannot tell whether they are applied correctly in your code or in your example file.由于我还没有处理中文算法,我无法判断它们是否在您的代码或示例文件中正确应用。

One warning, though: PdfPKCS7 has been implemented with DSA and RSA in mind;不过,有一个警告: PdfPKCS7已在考虑 DSA 和 RSA 的情况下实施; X9.62 ECDSA support has been added later, and that with an error (which still is present in iText 7) which Adobe Acrobat ignores but not necessarily other validators;稍后添加了 X9.62 ECDSA 支持,并且带有错误(在 iText 7 中仍然存在),Adobe Acrobat 忽略但不一定是其他验证器; before using PdfPKCS7 for any other algorithms, therefore, you have to verify that that class handles those other algorithms properly, and improve the class if it does not.因此,在将PdfPKCS7用于任何其他算法之前,您必须验证 class 是否正确处理这些其他算法,如果没有,则改进 class。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM