首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用iText (签名延迟)创建PDF数字签名,验证签名时出现无效签名问题。

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

Stack Overflow用户
提问于 2021-05-06 04:08:08
回答 1查看 345关注 0票数 3

我是一个中国软件开发人员,我现在正在实现这样一个功能,使用Android客户端数字签名PDF,我的实现是这样的。

  1. 在服务器
  2. 上创建一个空白签名,将带有空白签名的PDF散列发送给安卓客户端,而Android客户端签名哈希
  3. 使用make签名。为了合并服务器中的签名内容,我现在遇到了这样一个问题,即签名后的PDF无法由PDF阅读器进行验证。它显示PDF文件已经被篡改,应该注意的是,我使用了sm3withsm2算法。Adobe阅读器无法验证它。我们有自己的读者

https://drive.google.com/file/d/127nVvJ0qtSdG53jM0_GUP-WORYrQ5TBo/view?usp=sharing现在我添加了PDF文件地址,谁能帮我分析问题

代码语言:javascript
运行
复制
 /**
 * @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());

}
代码语言:javascript
运行
复制
/**
     * @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();
    }
代码语言:javascript
运行
复制
/**
     * @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");
    }
代码语言:javascript
运行
复制
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) {
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-05-06 12:34:50

在计算要签名属性的散列时,可以使用当时的当前时间作为签名时间属性的值:

代码语言:javascript
运行
复制
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);

稍后,当您收到该散列的裸露签名时,可以使用当时的当前时间重新构建已签名的属性:

代码语言:javascript
运行
复制
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);

因此,最终CMS容器的签名属性中的签名时间与用于散列的签名时间不同,因此签名标识错误的散列。

若要修复此问题,请在两个位置使用相同的签名时间值!

顺便说一句,如果您使用了当前的iText 5版本,您就不会遇到这个问题:因为签名时间属性是可选的(只要在签名字典中设置签名时间),甚至禁止PAdES签名,当前的iText 5版本不再设置该属性,getAuthenticatedAttributeBytesgetEncodedPKCS7不再有Calendar参数.

由于我还没有处理中文算法,所以我无法判断它们在您的代码或示例文件中的应用是否正确。

不过,有一个警告:PdfPKCS7的实现考虑到了DSA和RSA;X9.62 ECDSA支持是后来添加的,而Acrobat忽略了一个错误( iText 7中仍然存在这个错误),但不一定是其他验证器;因此,在将PdfPKCS7用于任何其他算法之前,您必须验证该类是否正确地处理其他算法,如果不正确,则必须改进该类。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67411720

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档