说到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包 
准备需要的资料 
导入的包,应该有多余的包,我直接从项目中复制出来的

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.security.GeneralSecurityException;
  6. import java.security.KeyStore;
  7. import java.security.PrivateKey;
  8. import java.security.Security;
  9. import java.security.cert.Certificate;
  10. import java.security.cert.X509Certificate;
  11. import java.util.ArrayList;
  12. import java.util.Collection;
  13. import javax.swing.JOptionPane;
  14. import com.itextpdf.text.DocumentException;
  15. import com.itextpdf.text.Image;
  16. import com.itextpdf.text.Rectangle;
  17. import com.itextpdf.text.log.Logger;
  18. import com.itextpdf.text.log.LoggerFactory;
  19. import com.itextpdf.text.pdf.PdfReader;
  20. import com.itextpdf.text.pdf.PdfSignatureAppearance;
  21. import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
  22. import com.itextpdf.text.pdf.PdfStamper;
  23. import com.itextpdf.text.pdf.security.BouncyCastleDigest;
  24. import com.itextpdf.text.pdf.security.CrlClient;
  25. import com.itextpdf.text.pdf.security.DigestAlgorithms;
  26. import com.itextpdf.text.pdf.security.ExternalDigest;
  27. import com.itextpdf.text.pdf.security.ExternalSignature;
  28. import com.itextpdf.text.pdf.security.MakeSignature;
  29. import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
  30. 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

准备的资料

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

2、写个类,声明一个方法用来进行pdf签章

  1. public void sign(String src //需要签章的pdf文件路径
  2. , String dest // 签完章的pdf文件路径
  3. , Certificate[] chain //证书链
  4. , PrivateKey pk //签名私钥
  5. , String digestAlgorithm //摘要算法名称,例如SHA-1
  6. , String provider // 密钥算法提供者,可以为null
  7. , CryptoStandard subfilter //数字签名格式,itext有2种
  8. , String reason //签名的原因,显示在pdf签名属性中,随便填
  9. , String location) //签名的地点,显示在pdf签名属性中,随便填
  10. throws GeneralSecurityException, IOException, DocumentException {
  11. //下边的步骤都是固定的,照着写就行了,没啥要解释的
  12. // Creating the reader and the stamper,开始pdfreader
  13. PdfReader reader = new PdfReader(src);
  14. //目标文件输出流
  15. FileOutputStream os = new FileOutputStream(dest);
  16. //创建签章工具PdfStamper ,最后一个boolean参数
  17. //false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
  18. //true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
  19. PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
  20. // 获取数字签章属性对象,设定数字签章的属性
  21. PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
  22. appearance.setReason(reason);
  23. appearance.setLocation(location);
  24. //设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
  25. //签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
  26. //四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
  27. appearance.setVisibleSignature(new Rectangle(200, 200, 300, 300), 1, "sig1");
  28. //读取图章图片,这个image是itext包的image
  29. Image image = Image.getInstance("F:\\test\\Dummy1.png");
  30. appearance.setSignatureGraphic(image);
  31. appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
  32. //设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
  33. appearance.setRenderingMode(RenderingMode.GRAPHIC);
  34. // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
  35. // 摘要算法
  36. ExternalDigest digest = new BouncyCastleDigest();
  37. // 签名算法
  38. ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, null);
  39. // 调用itext签名方法完成pdf签章
  40. 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方法中调用签章 
调用代码很简单,如下

  1. public static void main(String[] args) {
  2. try {
  3. //读取keystore ,获得私钥和证书链
  4. KeyStore ks = KeyStore.getInstance("PKCS12");
  5. ks.load(new FileInputStream(KEYSTORE), PASSWORD);
  6. String alias = (String)ks.aliases().nextElement();
  7. PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
  8. Certificate[] chain = ks.getCertificateChain(alias);
  9. //new一个上边自定义的方法对象,调用签名方法
  10. MainWindow app = new MainWindow();
  11. // app.sign(SRC, String.format(DEST, 1), chain, pk, DigestAlgorithms.SHA1, provider.getName(), CryptoStandard.CMS, "Test 1", "Ghent");
  12. // app.sign(SRC, String.format(DEST, 2), chain, pk, "SM3", provider.getName(), CryptoStandard.CADES, "Test 2", "Ghent");
  13. app.sign(SRC, String.format(DEST, 3), chain, pk, DigestAlgorithms.SHA1, null, CryptoStandard.CMS, "Test 3", "Ghent");
  14. // app.sign(SRC, String.format(DEST, 4), chain, pk, DigestAlgorithms.RIPEMD160, provider.getName(), CryptoStandard.CADES, "Test 4", "Ghent");
  15. } catch (Exception e) {
  16. // TODO Auto-generated catch block
  17. JOptionPane.showMessageDialog(null, e.getMessage());
  18. e.printStackTrace();
  19. }
  20. }
  • 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个接口。

  1. // 摘要算法
  2. ExternalDigest digest ;
  3. // 签名算法
  4. 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

  1. package com.itextpdf.text.pdf.security;
  2. import java.security.GeneralSecurityException;
  3. import java.security.MessageDigest;
  4. /**
  5. *
  6. * @author psoares
  7. */
  8. public interface ExternalDigest {
  9. public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException;
  10. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. package com.itextpdf.text.pdf.security;
  2. import java.security.GeneralSecurityException;
  3. /**
  4. * Interface that needs to be implemented to do the actual signing.
  5. * For instance: you'll have to implement this interface if you want
  6. * to sign a PDF using a smart card.
  7. * @author Paulo Soares
  8. */
  9. public interface ExternalSignature {
  10. /**
  11. * Returns the hash algorithm.
  12. * @return the hash algorithm (e.g. "SHA-1", "SHA-256,...")
  13. */
  14. public String getHashAlgorithm();
  15. /**
  16. * Returns the encryption algorithm used for signing.
  17. * @return the encryption algorithm ("RSA" or "DSA")
  18. */
  19. public String getEncryptionAlgorithm();
  20. /**
  21. * Signs it using the encryption algorithm in combination with
  22. * the digest algorithm.
  23. * @param message the message you want to be hashed and signed
  24. * @return a signed message digest
  25. * @throws GeneralSecurityException
  26. */
  27. public byte[] sign(byte[] message) throws GeneralSecurityException;
  28. }
  • 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中都包含那些算法,目前咱们用得到的算法,除了国密算法,其他的算法基本都可以支持)

  1. // 摘要算法
  2. ExternalDigest digest = new BouncyCastleDigest();;
  3. // 签名算法
  4. 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

  1. import java.io.File;
  2. public class NativeMethods {
  3. static {
  4. String parentPath="D:\\dll\\";
  5. String dllName="NativeCode.dll";
  6. File dll=new File(parentPath+dllName);
  7. if (dll.exists()) {
  8. // System.loadLibrary(dllName);//dll必须方法系统环境变量下
  9. System.load(parentPath+dllName); //可以指定任意位置
  10. }
  11. }
  12. //进行签名,传入签名原文,返回签名结果
  13. public native String signByUKEY(String message);
  14. //获取签名公钥证书
  15. public native String getSignCer();
  16. }
  • 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. javascript中addEventListener(attachEvent)具体解释

    addEventListener 有三个參数:第一个參数表示事件名称(不含 on,如 "click").第二个參数表示要接收事件处理的函数:第三个參数为 useCapture.样例 ...

  2. 让人郁闷的.net

    一个旧项目,.net 2.0的,因为一个小改动,mongo数据库加了密码,结果折腾两天却无法解决,让人郁闷的地方太多: .net版本多,用的原来的驱动是1.7的,在.net 2.0就可以,mongo服 ...

  3. 关于 redis 报错 :JsonParseException: Unrecognized token 'xxx': was expecting ('true', 'false' or 'null')

    在使用java  读取redis存储的数据时出现 JsonParseException: Unrecognized token 'xiaoqiang': was expecting ('true', ...

  4. Solidworks如何打开swb文件

    把swb文件拖放到Solidworks里面,会弹出窗口选择一个文件夹   随后会自动生成对应的文件,装配体  

  5. javascript链式语法

    因为 jQuery 库的缘故,链式语法在前端界变得非常流行.实际上这是一种非常容易实现的模式.基本上,你只需要让每个函数返回 'this',这样其他函数就可以立即被调用.看看下面的例子. var bi ...

  6. RPi Desktop盒子安装与服务配置

    批量安装配置盒子时候,可以先安装一个,其余的从这台copy过去. 之前的部分shell记录在本地,记录如下,以免忘记.下次可直接cp执行即可: Step1, 创建用户/组 sudo groupadd ...

  7. Spring 开发环境搭建(二)

    为了方面,直接使用eclipse,创建maven工程,创建成功之后 一.修改pom.xml,为了方面我就把Spring相关的jar包都引用了 <project xmlns="http: ...

  8. 关于CBC for ios 加密要记

    倒腾了接近半天,资料找了无数,最后是通过查看Android项目中的加密工具类,才弄明白,在这过程中掌握了一些知识点.比如: 问题1:关于PKCS7Padding和PKCS5Padding iOS中AE ...

  9. Flash 加密和破解

    关于Flash(swf),我们需要明确一点: ***Flash字节码的意义都是公开的 所以如果cracker真的有足够的耐心他最终还是可以破解掉你的Flash.我们能做的只是尽量提高Flash被破解的 ...

  10. oracle中extract()函数----用于截取年、月、日、时、分、秒

    oracle中extract()函数从oracle 9i中引入,用于从一个date或者interval类型中截取到特定的部分 语法如下: extract ( { year | month | day ...