天天看點

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