天天看点

itext7 org.bouncycastle.asn1.ASN1OutputStream.writeObject(Lorg/bouncycastle/asn1/ASN1Primitive;)V

itext7 org.bouncycastle.asn1.ASN1OutputStream.writeObject(Lorg/bouncycastle/asn1/ASN1Primitive;)V

需求是根据关键字定位然后在该位置盖章,项目原先使用的是itext5,由于itext5根据关键字定位有些段落读不到,导致定位差距很大,于是便升级到itext7。运行时出现了Exception in thread “main” java.lang.NoSuchMethodError: org.bouncycastle.asn1.ASN1OutputStream.writeObject(Lorg/bouncycastle/asn1/ASN1Primitive;)V。

<!--itext5 pom-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>5.5.13</version>
</dependency>

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
           
<!--itext7 pom-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>7.2.0</version>
</dependency>

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
           

最开始只修改了itextpdf的pom,运行出现问题,作为Java开发来说,第一眼见到ClassNotFoundException、NoSuchMethodException这些异常来说,第一反应就是排包。通过Maven Helper插件发现7.2.0的依赖里面已经包含了bcprov-jdk15on和bcpkix-jdk15on

itext7 org.bouncycastle.asn1.ASN1OutputStream.writeObject(Lorg/bouncycastle/asn1/ASN1Primitive;)V

最简单的方法就是直接将bcprov-jdk15on和bcpkix-jdk15on这两个依赖删除掉,再次运行就可以了。这需要根据项目的需求来决定是删除低版本依赖还是使这两个版本兼容,具体参考 https://mp.weixin.qq.com/s/ahRDR6JGHFiLYd1efVJMog。

下面是根据关键字定位并盖章代码,代码来源 https://www.cnblogs.com/bumblebee-xy/p/7291265.html,做了一点修改,源代码直接运行有点问题。

package satoken.controller;

/**
 * @author unknown
 * @date 2021/12/22
 */
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.layout.element.Image;
import com.itextpdf.signatures.*;
import com.itextpdf.signatures.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.signatures.PdfSigner.CryptoStandard;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.swing.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;

public class SignPDF {
    //keystore文件路径
    public static final String KEYSTORE = "C:\\Users\\unknown\\Desktop\\test.jks";
    // keystore密码
    public static final char[] PASSWORD = "test".toCharArray();
    //需要盖章的pdf文件路径
    public static final String SRC = "C:\\Users\\unknown\\Desktop\\test.pdf";
    //盖章后生产的pdf文件路径
    public static final String DEST = "C:\\Users\\unknown\\Desktop\\test2.pdf";
    //印章路径
    public static final String stamperSrc = "C:\\Users\\unknown\\Desktop\\test.png";

    public void sign(String src  //需要签章的pdf文件路径
            , String dest  // 签完章的pdf文件路径
            , Certificate[] chain //证书链
            , PrivateKey pk //签名私钥
            , String digestAlgorithm  //摘要算法名称,例如SHA-1
            , String provider  // 密钥算法提供者,可以为null
            , CryptoStandard subfilter //数字签名格式,itext有2种
            , String reason  //签名的原因,显示在pdf签名属性中,随便填
            , String location) //签名的地点,显示在pdf签名属性中,随便填
            throws GeneralSecurityException, IOException {
        //下边的步骤都是固定的,照着写就行了,没啥要解释的
        PdfReader reader = new PdfReader(src);
        //目标文件输出流
        FileOutputStream os = new FileOutputStream(dest);
        //创建签章工具PdfSigner ,最后一个boolean参数
        //false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
        //true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
        PdfSigner stamper = new PdfSigner(reader, os, new StampingProperties());
        // 获取数字签章属性对象,设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        ImageData img = ImageDataFactory.create(stamperSrc);
        //读取图章图片,这个image是itext包的image
        Image image = new Image(img);
        float height = image.getImageHeight();
        float width = image.getImageWidth();
        //设置签名的位置,页码,签名域名称,多次追加签名的时候,签名与名称不能一样
        //签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
        //四个参数的分别是,图章左下角x,图章左下角y,图章宽度,图章高度
        appearance.setPageNumber(1);
        //后面两个参数就是根据关键字定位的,为了方便测试就写死了
        appearance.setPageRect(new Rectangle(113, 113, 127.1367f, 134.673f));
        //插入盖章图片
        appearance.setSignatureGraphic(img);
        //设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
        appearance.setRenderingMode(RenderingMode.GRAPHIC);
        // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
        // 摘要算法
        IExternalDigest digest = new BouncyCastleDigest();
        // 签名算法
        Security.addProvider(new BouncyCastleProvider());
        IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
        // 调用itext签名方法完成pdf签章
        stamper.setCertificationLevel(1);
        stamper.signDetached(digest, signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
        reader.close();
    }

    public static void main(String[] args) {
        try {
            // 读取keystore ,获得私钥和证书链 jks
            KeyStore ks = KeyStore.getInstance("jks");
            ks.load(new FileInputStream(KEYSTORE), PASSWORD);
            String alias = (String) ks.aliases().nextElement();
            PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
            Certificate[] chain = ks.getCertificateChain(alias);
            // new一个上边自定义的方法对象,调用签名方法
            SignPDF app = new SignPDF();
            app.sign(SRC, String.format(DEST, 1), chain, pk, DigestAlgorithms.SHA256, null, CryptoStandard.CADES, "Test 1",
                    "Ghent");
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, e.getMessage());
            e.printStackTrace();
        }
    }
}
           
/**
 * @description:根据关键字定位
 **/
public class KywordLcation {

    public static List<Position> findKeywordPositions(InputStream inputStream, String keyword) throws IOException {
        PdfReader pdfData = new PdfReader(inputStream);
        PdfDocument pdfDocument = new PdfDocument(pdfData);
        List<Position> result = new ArrayList<>();
        for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
            PdfPage page = pdfDocument.getPage(i);
            RegexBasedLocationExtractionStrategy strategy = new RegexBasedLocationExtractionStrategy(keyword);
            PdfCanvasProcessor canvasProcessor = new PdfCanvasProcessor(strategy);
            canvasProcessor.processPageContent(page);
            Collection<IPdfTextLocation> resultantLocations = strategy.getResultantLocations();
            for (IPdfTextLocation location : resultantLocations) {
                Rectangle rectangle = location.getRectangle();
                result.add(Position.builder().pageNum(i).x(rectangle.getX() + rectangle.getWidth() / 2).y(rectangle.getY() + rectangle.getHeight() / 2).build());
            }
        }
        return result;
    }

    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\zhl\\Desktop\\test.pdf");
        findKeywordPositions(fileInputStream, "test");
    }
}
           

参考链接:

jar包冲突解决方案 https://mp.weixin.qq.com/s/ahRDR6JGHFiLYd1efVJMog

使用itext7给pdf盖章 https://www.cnblogs.com/bumblebee-xy/p/7291265.html