Java使用Itext5.5.10進行pdf簽章

說到PDF數字簽名簽章,這個其實也是數字證書信息安全的應用範疇,關於數字證書和數字簽名,網上有不少解釋說明,但講解都多不夠詳細準確,這邊推薦一篇大神的博文,講解淺顯易懂形象數字證書 數字簽名 數據加密。剛入門CA行業的人,能夠入門看看。 
言歸正傳,正文開始html

Itext包 和 BC包

要本身實現PDF數字簽章,是一件極其浩大的工程,難度很大(看看市面上多少公司是吃這一行的飯就知道了),好在java是個開源的世界,有不少開源項目。這裏,我們使用itext來實現一下pdf的數字簽章(爲何挑itext,很大緣由是,本身在作這一塊的時候,網上對於itext的使用也有不少博文,不過,大多都是用的比較早期的itext,itext官網目前的版本的已經有了變化,網上廣泛的作法都已經不適用了,還有一個緣由,itext官網有官方教程,各個模塊的樣例,很方便)。 
若是不知道在官網怎麼下載jar包,這裏附上我本身的下載地址方便你們, itextpdf-5.5.10 源碼、jar包、doc文檔 ,另外還須要密鑰算法包bouncycastle.org ,這個官網下載很簡單,官網地址以下bouncycastle.org官網java

開始

我們跟隨樣例,先來一個簡單的簽章。步驟以下: 
1、準備一個pdf文檔(貌似是廢話) 
2、準備一個圖章圖片(貌似也是廢話) 
3、準備一個keystore(只要是java keystore支持的格式均可以,例如.p12,若是沒有,能夠用bouncycastle生成一個,也很簡單)。其實,Usbkey數字證書也是可使用的,後邊我再說這一塊。 
4、按照官網樣例,寫個.p12的簽章代碼。c++

代碼

一、新建Java項目,導入itext包和 bc包 
準備須要的資料 
導入的包,應該有多餘的包,我直接從項目中複製出來的算法

import java.io.File;
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;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.JOptionPane;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.CrlClient;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;

準備的資料windows

public static final String KEYSTORE = "F:\\ZzCert\\test.p12";
    public static final char[] PASSWORD = "111111".toCharArray();//keystory密碼
    public static final String SRC = "F:\\test\\src.pdf";
    public static final String DEST = "F:\\test\\signed_dest.pdf";

二、寫個類,聲明一個方法用來進行pdf簽章安全

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, DocumentException {
        //下邊的步驟都是固定的,照着寫就好了,沒啥要解釋的
        // Creating the reader and the stamper,開始pdfreader
        PdfReader reader = new PdfReader(src);
        //目標文件輸出流
        FileOutputStream os = new FileOutputStream(dest);
        //建立簽章工具PdfStamper ,最後一個boolean參數 
        //false的話,pdf文件只容許被簽名一次,屢次簽名,最後一次有效
        //true的話,pdf能夠被追加簽名,驗籤工具能夠識別出每次簽名以後文檔是否被修改
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
        // 獲取數字簽章屬性對象,設定數字簽章的屬性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        //設置簽名的位置,頁碼,簽名域名稱,屢次追加簽名的時候,簽名預名稱不能同樣
        //簽名的位置,是圖章相對於pdf頁面的位置座標,原點爲pdf頁面左下角
        //四個參數的分別是,圖章左下角x,圖章左下角y,圖章右上角x,圖章右上角y
        appearance.setVisibleSignature(new Rectangle(200, 200, 300, 300), 1, "sig1");
        //讀取圖章圖片,這個image是itext包的image
        Image image = Image.getInstance("F:\\test\\Dummy1.png"); 
        appearance.setSignatureGraphic(image); 
      appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
        //設置圖章的顯示方式,以下選擇的是隻顯示圖章(還有其餘的模式,能夠圖章和簽名描述一同顯示)
        appearance.setRenderingMode(RenderingMode.GRAPHIC);

        // 這裏的itext提供了2個用於簽名的接口,能夠本身實現,後邊着重說這個實現
        // 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();
        // 簽名算法
        ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, null);
        // 調用itext簽名方法完成pdf簽章
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
        }

三、main方法中調用簽章 
調用代碼很簡單,以下服務器

public static void main(String[] args)  {
        try {
        //讀取keystore ,得到私鑰和證書鏈
            KeyStore ks = KeyStore.getInstance("PKCS12");
            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一個上邊自定義的方法對象,調用簽名方法
            MainWindow app = new MainWindow();
//      app.sign(SRC, String.format(DEST, 1), chain, pk, DigestAlgorithms.SHA1, provider.getName(), CryptoStandard.CMS, "Test 1", "Ghent");
//      app.sign(SRC, String.format(DEST, 2), chain, pk, "SM3", provider.getName(), CryptoStandard.CADES, "Test 2", "Ghent");
            app.sign(SRC, String.format(DEST, 3), chain, pk, DigestAlgorithms.SHA1, null, CryptoStandard.CMS, "Test 3", "Ghent");
//      app.sign(SRC, String.format(DEST, 4), chain, pk, DigestAlgorithms.RIPEMD160, provider.getName(), CryptoStandard.CADES, "Test 4", "Ghent");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            JOptionPane.showMessageDialog(null, e.getMessage());
            e.printStackTrace();
        } 
    }

四、運行main方法就能夠了。效果以下,用adobe reader能夠看到圖章,能夠獲取簽名信息這裏寫圖片描述app

使用特殊簽名算法

上邊的例子中,使用的是比較常見的簽名算法-sha1withRsa,itext支持國際流行的大部分簽名算法。 
固然itext也支持特殊的簽名算法,例如國密,爲何itext不把國密算法也封裝進jar包呢,一個緣由是國密並非國際通用標準,二是即使把國密封裝進jar包,進行完簽名後,通常的pdf閱讀器也是沒法驗籤的,由於adobe 的pdf標準沒有國密算法。 
即使如此,咱們依然能夠本身把國密算法加到itext簽章中,只不過閱讀器沒法驗籤就對了。主要用的就是上邊例子中的2個接口。ide

// 摘要算法
        ExternalDigest digest ;
        // 簽名算法
        ExternalSignature signature ;

咱們能夠經過本身實現這2個接口,來添加國密算法。 
看看這連個接口的源碼,都很簡單,digest接口返回MessageDigest,實現的時候,直接new 一個MessageDigest,而後實現MessageDigest的抽象方法,把本身實現的SM3算法加進去就能夠了(SM3withSM2按照國密的標準,sm3要加預處理,具體怎麼作,百度不少,這裏很少說) 
signature 接口3個抽象方法,分別返回摘要算法名稱(例如SM3 或者SHA1等),簽名算法中使用的加密算法名稱(例如SM2 或者RSA等), 第三個抽象方法sign就是具體的簽名算法,傳入的參數message是簽名原文,返回值是簽名結果,針對國密算法來講,就能夠把本身實現好的 sm3withsm2簽名算法 寫進去。 
另外,須要注意,實現接口後,運行main會提示錯誤,緣由是 本身實現的國密接口的OID並無加入到itext源碼中,能夠根據錯誤提示,找到須要加入oid的地方,直接把算法oid寫進去後 itext就能夠認到咱們本身實現的算法了。大體有2個地方要加,一個是摘要算法的oid,一個是簽名算法的oid函數

package com.itextpdf.text.pdf.security;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;

/**
 *
 * @author psoares
 */
public interface ExternalDigest {
    public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException;
}
package com.itextpdf.text.pdf.security;

import java.security.GeneralSecurityException;

/**
 * Interface that needs to be implemented to do the actual signing.
 * For instance: you'll have to implement this interface if you want
 * to sign a PDF using a smart card.
 * @author Paulo Soares
 */
public interface ExternalSignature {

    /**
     * Returns the hash algorithm.
     * @return  the hash algorithm (e.g. "SHA-1", "SHA-256,...")
     */
    public String getHashAlgorithm();

    /**
     * Returns the encryption algorithm used for signing.
     * @return the encryption algorithm ("RSA" or "DSA")
     */
    public String getEncryptionAlgorithm();

    /**
     * Signs it using the encryption algorithm in combination with
     * the digest algorithm.
     * @param message   the message you want to be hashed and signed
     * @return  a signed message digest
     * @throws GeneralSecurityException
     */
    public byte[] sign(byte[] message) throws GeneralSecurityException;
}

UsbKey 數字證書籤章

你們必定有 用UsbKey簽章的需求,由於 軟證書是不安全的,私鑰容易被竊取,UsbKey數字證書纔是最正規最安全的方案,上邊說的都是基於軟證書的,那麼Ukey硬證書要怎麼簽章呢? 
一樣的,咱們仍是利用以下這2個接口。不過,此次不用實現digest了,由於itext本身包含的digest算法都是能夠知足的,直接用例子中的代碼就能夠。 
你說個人ukey 證書也是國密SM3withSM2的,那怎麼辦,個人回答是,國密算法的ukey最好不要用,由於仍是那句話,即使實現了接口,簽出來pdf後, 市面上主流pdf閱讀器都不能驗籤,那就失去了簽章的意義。除非,本身寫一個閱讀器。。。 
(能夠點開源碼看看itext oid中都包含那些算法,目前我們用獲得的算法,除了國密算法,其餘的算法基本均可以支持)

// 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();;
        // 簽名算法
        ExternalSignature signature ;

OK,Ukey怎麼調用,很簡單,一樣是 實現signature接口,把你的ukey的摘要算法和加密算法名稱返回, sign函數中, 調用你的Ukey的簽名算法就好了。 
好比,你的Ukey是windows平臺的,Ukey廠家確定給你有Ukey驅動和 算法dll,咱們須要作的就是,用java寫個jni接口,添加native方法,而後javah生成.h頭文件,而後在用c或者c++調用廠家的dll實現jni接口, 而後在 ExternalSignature的sign方法中調用native方法就能夠了。 
固然,本身封裝的 簽名函數傳入的參數就要變一變了,例如私鑰 就直接傳入null

import java.io.File;


public class NativeMethods {
    static {
        String parentPath="D:\\dll\\";
        String dllName="NativeCode.dll";
        File dll=new File(parentPath+dllName);
        if (dll.exists()) {
//      System.loadLibrary(dllName);//dll必須方法系統環境變量下
            System.load(parentPath+dllName); //能夠指定任意位置
        }
    }
    //進行簽名,傳入簽名原文,返回簽名結果
    public native String signByUKEY(String message);
    //獲取簽名公鑰證書
    public native String getSignCer();
}

使用遠程服務器簽名的方式簽章

看了上邊內容,估計你也會觸類旁通的 實現 服務器形式的 簽名了,沒錯,就是實現 簽名接口ExternalSignature signature ;,在sign方法中訪問 服務器簽名接口 傳送簽名原文,返回簽名結果就能夠了。

結語

OK了,itext 進行pdf簽章這塊就說完了,但願對你們有所幫助。 

相關文章
相關標籤/搜索