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

Itext包 和 BC包

要自己实现PDF数字签章,是一件极其浩大的工程,难度很大(看看市面上多少公司是吃这一行的饭就知道了),好在java是个开源的世界,有很多开源项目。这里,咱们使用itext来实现一下pdf的数字签章(为什么挑itext,很大原因是,自己在做这一块的时候,网上对于itext的使用也有很多博文,不过,大多都是用的比较早期的itext,itext官网目前的版本的已经有了变化,网上普遍的做法都已经不适用了,还有一个原因,itext官网有官方教程,各个模块的样例,很方便)。 
如果不知道在官网怎么下载jar包,这里附上我自己的下载地址方便大家, itextpdf-5.5.10 源码、jar包、doc文档,另外还需要密钥算法包bouncycastle.org ,这个官网下载很简单,官网地址如下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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

准备的资料

    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";
  • 1
  • 2
  • 3
  • 4

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

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();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

使用特殊签名算法

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

        // 摘要算法
ExternalDigest digest ;
// 签名算法
ExternalSignature signature ;
  • 1
  • 2
  • 3
  • 4

我们可以通过自己实现这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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

UsbKey 数字证书签章

大家一定有 用UsbKey签章的需求,因为 软证书是不安全的,私钥容易被窃取,UsbKey数字证书才是最正规最安全的方案,上边说的都是基于软证书的,那么Ukey硬证书要怎么签章呢? 
同样的,我们还是利用如下这2个接口。不过,这次不用实现digest了,因为itext自己包含的digest算法都是可以满足的,直接用例子中的代码就可以。 
你说我的ukey 证书也是国密SM3withSM2的,那怎么办,我的回答是,国密算法的ukey最好不要用,因为还是那句话,即便实现了接口,签出来pdf后, 市面上主流pdf阅读器都不能验签,那就失去了签章的意义。除非,自己写一个阅读器。。。 
(可以点开源码看看itext oid中都包含那些算法,目前咱们用得到的算法,除了国密算法,其他的算法基本都可以支持)

        // 摘要算法
ExternalDigest digest = new BouncyCastleDigest();;
// 签名算法
ExternalSignature signature ;
  • 1
  • 2
  • 3
  • 4

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

使用远程服务器签名的方式签章

看了上边内容,估计你也会举一反三的 实现 服务器形式的 签名了,没错,就是实现 签名接口ExternalSignature signature ;,在sign方法中访问 服务器签名接口 传送签名原文,返回签名结果就可以了。

结语

OK了,itext 进行pdf签章这块就说完了,希望对大家有所帮助。

Java使用Itext5.5.10进行pdf签章的更多相关文章

  1. Java实现office文档与pdf文档的在线预览功能

    最近项目有个需求要java实现office文档与pdf文档的在线预览功能,刚刚接到的时候就觉得有点难,以自己的水平难以在三四天做完.压力略大.后面查找百度资料.以及在同事与网友的帮助下,四天多把它做完 ...

  2. 20145225《Java程序设计》 第10周学习总结

    20145225<Java程序设计> 第10周学习总结 教材学习内容总结 一.网络编程 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据: 程序员所作的事情就是把数据发送到指定 ...

  3. 《Effective Java(中文第二版)》【PDF】下载

    <Effective Java(中文第二版)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382186 Java(中文第二版)& ...

  4. 20155228 2016-2017-2 《Java程序设计》第10周学习总结

    20155228 2016-2017-2 <Java程序设计>第10周学习总结 教材学习内容总结 网络 网络是能够波此通信的计算机的集合根据范}到的宽度,网络可以分为局域网和广域网.LAN ...

  5. 使用java的 htpUrlConnection post请求 下载pdf文件,然后输出到页面进行预览和下载

    使用java的 htpUrlConnection post请求 下载pdf文件,然后输出到页面进行预览和下载 2018年06月07日 10:42:26 守望dfdfdf 阅读数:235 标签: jav ...

  6. 《疯狂Java讲义第4版》PDF+代码+课件 电子书pdf 分享

    <疯狂Java讲义(第4版)>是<疯狂Java讲义>的第4版,第4版保持了前3版系统.全面.讲解浅显.细致的特性,全面新增介绍了Java 9的新特性. <疯狂Java讲义 ...

  7. 20145212 《Java程序设计》第10周学习总结

    20145212 <Java程序设计>第10周学习总结 学习内容总结 一.Java的网络编程 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来. java.net ...

  8. 20145206《Java程序设计》第10周学习总结

    20145206 <Java程序设计>第10周学习总结 博客学习内容总结 什么是网络编程 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.程序员所作的事情就是把数据发送到指定 ...

  9. 【异常】java.lang.NoClassDefFoundError: com/lowagie/text/pdf/PdfContentByte

    异常信息:   java.lang.NoClassDefFoundError: com/lowagie/text/pdf/PdfContentByte  at com.star.sms.busines ...

随机推荐

  1. Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十八):kafka0.10.1 内置性能测试API用法示例

    消费者测试: ./kafka-consumer-perf-test..com.cn:,vm10..com.cn:,vm10..com.cn: --group test-teg1 --messages ...

  2. [React] Validate React Forms with Formik and Yup

    Validating forms in React can take several lines of code to build. However, Formik's ErrorMessage co ...

  3. 【Eclipse】如何在Eclipse中使用命令行?

    如何在Eclipse中使用命令行? 虽然我们已经有了像 Eclipse 这样高级的 IDE,但是我们有时候也是需要在开发的时候使用 Windows 的命令行,来运行一些独立的程序.在两个程序中切换来切 ...

  4. powerdesigner 不显示表字段只显示表名

    在空白的地方右键选择 Display Preferences然后在左边的General Settings里选Table然后把Columns 的All Columns勾上 如果能帮上您,请选为满意答案, ...

  5. hdu 1728 逃离迷宫 bfs记转向

    题链:http://acm.hdu.edu.cn/showproblem.php?pid=1728 逃离迷宫 Time Limit: 1000/1000 MS (Java/Others)    Mem ...

  6. ASP.NET Hashtable输出JSON格式数据

    最近在开发Windows8 Metro App,使用JavaScript和HTML开发环境.所以操作数据绑定都是使用JSON格式数据.后台使用的是ASP.NET,因为项目相对较小,所有后台没有使用数据 ...

  7. 深度学习-Caffe编译测试的小总结

    1. 搭建的环境和代码:win7 64bit + vs2013+CUDA7.5 http://blog.csdn.net/thesby/article/details/50880802 2. 编译,制 ...

  8. JERSEY中文翻译(第一章、Getting Started、2.2)

    前言 这是jersey2.2的用户向导,我们会尽力维护它的更新并且也会增加新的章节.当阅读本用户指南的时候,也要参阅Jersey API 文档,额外的信息补充JERSEY的新特性和API 如果你想要为 ...

  9. Web服务器讲解与JavaWeb应用部署(本机,以Tomcat为例)

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6042290.html 在讨论Web系统发布之前,我们先来辨析两个概念:服务器.Web服务器. 通常,我们说的服 ...

  10. 批处理/命令行合并js,递归合并子目录js文件

    for /r %%i in (*.js) do type "%%i">>xxx-all.js java -jar yuicompressor.jar --type js ...