1.电子签章简介
电子签章,与我们所使用的数字证书一样,是用来做为身份验证的一种手段,泛指所有以电子形式存在,依附在电子文件并与其逻辑关联,可用以辨识电子文件签署者身份,保证文件的完整性,并表示签署者同意电子文件所陈述事实的内容。一般来说,对电子签章的认定,都是从技术角度而言的。主要是指通过特定的技术方案来鉴别当事人的身份及确保交易资料内容不被篡改的安全保障措施。从广义上讲,电子签章不仅包括我们通常意义上讲的"非对称性密钥加密",也包括计算机口令、生物笔迹辨别、指纹识别,以及新近出现的眼虹膜透视辨别法、面纹识别等。而电子签章技术作为目前最成熟的"数字签章",是以公钥及密钥的"非对称型"密码技术制作的。电子签章是电子签名的一种表现形式,利用图像处理技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性。-百度百科
2.技术选型
目前主流处理PDF文件两个jar包分别是:
①开源组织Apache的PDFBox,官网https://pdfbox.apache.org/
②大名鼎鼎adobe公司的iText,官网https://itextpdf.com/tags/adobe,其中iText又分为iText5和iText7
如何在PDFBox、iText5和iText7选出合适自己项目的技术?
对比PDFBox、iText5和iText7这三者:
①PDFBox的功能相对较弱,iText5和iText7的功能非常强悍;
②iText5的资料网上相对较多,如果出现问题容易找到解决方案;PDFBox和iText7的网上资料相对较少,如果出现问题不易找到相关解决方案;
③通过阅读PDFBox代码目前PDFBox还没提供自定义签章的相关接口;iText5和iText7提供了处理自定义签章的相关实现;
④PDFBox只能实现把签章图片加签到PDF文件;iText5和iText7除了可以把签章图片加签到PDF文件,还可以实现直接对签章进行绘制,把文件绘制到签章上。
⑤PDFBox和iText5/iText7使用的协议不一样。PDFBox使用的是APACHE LICENSE VERSION 2.0(https://www.apache.org/licenses/);iText5/iText7使用的是AGPL(https://itextpdf.com/agpl)。PDFBox免费使用,AGPL商用收费。
3.java实现PDF盖电子签章
准备数字证书,如果不清楚怎么制作自签名的数字证书,可以参考文章1和文章2。
需要的jar包:
<dependencies>
<!--bouncycastle加密包-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.64</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15</artifactId>
<version>1.46</version>
</dependency>
<!--itext-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.11</version>
</dependency>
</dependencies>
java代码:
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
public class LocationSignature {
public void sign(InputStream p12Stream, //p12 路径
char[] password,
InputStream src,//需要签章的pdf文件路径
OutputStream dest,// 签完章的pdf文件路径
String reason,//签名的原因,显示在pdf签名属性中,随便填
String location,//签名的地点,显示在pdf签名属性中,随便填
String chapterPath//电子签章的图片
) throws GeneralSecurityException, IOException, DocumentException {
//读取keystone,获得私钥和证书链
KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
pkcs12.load(p12Stream, password);
String alias = pkcs12.aliases().nextElement();
PrivateKey key = (PrivateKey) pkcs12.getKey(alias, password);
Certificate[] chain = pkcs12.getCertificateChain(alias);
//下边的步骤都是固定的,照着写就行了,没啥要解释的
// Creating the reader and the stamper,开始pdfreader
PdfReader reader = new PdfReader(src);
//目标文件输出流
//创建签章工具PdfStamper ,最后一个boolean参数
//false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
//true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, false);
// 获取数字签章属性对象,设定数字签章的属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
//设置签名的位置,页码,签名域名称,多次追加签名的时候,签名域名称不能一样
//签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
//四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
appearance.setVisibleSignature(new Rectangle(0, 800, 100, 700), 1, "sig1");
//读取图章图片,这个image是itext包的image
Image image = Image.getInstance(chapterPath);
appearance.setSignatureGraphic(image);
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
//设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
// 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
// 摘要算法
BouncyCastleDigest digest = new BouncyCastleDigest();
// 签名算法
PrivateKeySignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA1, null);
// 调用itext签名方法完成pdf签章CryptoStandard.CMS 签名方式,建议采用这种
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
}
public static void main(String[] args) throws IOException, DocumentException, GeneralSecurityException {
LocationSignature locationSignature = new LocationSignature();
String KEYSTORE = "E:\\iotest\\itextpdf\\test2.p12";
char[] PASSWORD = "123456".toCharArray();//keystory密码
String SRC = "E:\\iotest\\itextpdf\\javase.pdf";//原始pdf
String DEST = "E:\\iotest\\itextpdf\\signjavase.pdf";//签名完成的pdf
String chapterPath = "E:\\iotest\\itextpdf\\stamp.png";//签章图片
String reason = "数据不可更改";
String location = "beijing";
locationSignature.sign(new FileInputStream(KEYSTORE), PASSWORD, new FileInputStream(SRC), new FileOutputStream(DEST), reason, location, chapterPath);
System.out.println("签章完成");
}
}
结果展示:
原文链接:https://blog.csdn.net/do_bset_yourself/article/details/78171897?locationNum=8&fps=1
参考:https://blog.csdn.net/javasun608/article/details/79307845