博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java使用Itext5.5.10进行pdf签章
阅读量:6378 次
发布时间:2019-06-23

本文共 9092 字,大约阅读时间需要 30 分钟。

hot3.png

说到PDF数字签名签章,这个其实也是数字证书信息安全的应用范畴,关于数字证书和数字签名,网上有很多解释说明,但讲解都多不够详细准确,这边推荐一篇大神的博文,讲解浅显易懂形象。刚入门CA行业的人,可以入门看看。 

言归正传,正文开始

Itext包 和 BC包

要自己实现PDF数字签章,是一件极其浩大的工程,难度很大(看看市面上多少公司是吃这一行的饭就知道了),好在java是个开源的世界,有很多开源项目。这里,咱们使用itext来实现一下pdf的数字签章(为什么挑itext,很大原因是,自己在做这一块的时候,网上对于itext的使用也有很多博文,不过,大多都是用的比较早期的itext,itext官网目前的版本的已经有了变化,网上普遍的做法都已经不适用了,还有一个原因,itext官网有官方教程,各个模块的样例,很方便)。 

如果不知道在官网怎么下载jar包,这里附上我自己的下载地址方便大家, ,另外还需要密钥算法包bouncycastle.org ,这个官网下载很简单,官网地址如下。

开始

咱们跟随样例,先来一个简单的签章。步骤如下: 

一、准备一个pdf文档(貌似是废话) 
二、准备一个图章图片(貌似也是废话) 
三、准备一个keystore(只要是java keystore支持的格式都可以,例如.p12,如果没有,可以用bouncycastle生成一个,也很简单)。其实,Usbkey数字证书也是可以使用的,后边我再说这一块。 
四、按照官网样例,写个.p12的签章代码。

代码

1、新建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;

准备的资料

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";

2、写个类,声明一个方法用来进行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);        }

3、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();        }     }

4、运行main方法就可以了。效果如下,用adobe reader可以看到图章,可以获取签名信息这里写图片描述

使用特殊签名算法

上边的例子中,使用的是比较常见的签名算法-sha1withRsa,itext支持国际流行的大部分签名算法。 

当然itext也支持特殊的签名算法,例如国密,为什么itext不把国密算法也封装进jar包呢,一个原因是国密并不是国际通用标准,二是即便把国密封装进jar包,进行完签名后,一般的pdf阅读器也是无法验签的,因为adobe 的pdf标准没有国密算法。 
即便如此,我们依然可以自己把国密算法加到itext签章中,只不过阅读器无法验签就对了。主要用的就是上边例子中的2个接口。

// 摘要算法        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签章这块就说完了,希望对大家有所帮助。 

转载于:https://my.oschina.net/u/3787897/blog/1630625

你可能感兴趣的文章
[PHP]怎样在SAE的CodeIgniter项目中隐藏掉index.php
查看>>
AutoLayout具体解释+手把手实战
查看>>
机器学习之基于朴素贝叶斯文本分类算法
查看>>
OkHttp 官方Wiki【设计思想】
查看>>
Spring Security入门(1-13)Spring Security的投票机制和投票器
查看>>
选择问题(线性时间复杂度)
查看>>
JAVA性能优化的五种方式
查看>>
Bootstrap下拉菜单
查看>>
Linux上 ps 命令的用法
查看>>
数据库的case when 使用实例
查看>>
MySQL主从复制和读写分离
查看>>
.NET Core 2.0 Preview2 发布汇总
查看>>
jdk1.8的lambda语法(转)
查看>>
ReactNative开发工具有这一篇足矣
查看>>
扁平数组构建DOM树
查看>>
编程算法 - 两个升序列的同样元素 代码(C)
查看>>
还在为画“类Word文档报表”而发愁吗?
查看>>
GUI for git|SourceTree|入门基础
查看>>
Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案
查看>>
关于HTML中文乱码问题
查看>>