說到PDF數字簽名簽章,這個其實也是數字證書信息安全的應用範疇,關於數字證書和數字簽名,網上有不少解釋說明,但講解都多不夠詳細準確,這邊推薦一篇大神的博文,講解淺顯易懂形象數字證書 數字簽名 數據加密。剛入門CA行業的人,能夠入門看看。
言歸正傳,正文開始html
要本身實現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數字證書纔是最正規最安全的方案,上邊說的都是基於軟證書的,那麼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簽章這塊就說完了,但願對你們有所幫助。