说到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签章这块就说完了,但愿对你们有所帮助。