目录
1.先来看效果
2.分析:
3.代码实现
4 项目结构及jar包配置
表单盖章,经常看到有盖骑缝章,项目恰好有这个需求,pdf文档盖数字签名骑缝章,项目效果图
现整理,记录,供需要的参考,pdf数字签名,签骑缝章
签章图片
签章后,效果图:
应该在签章上修改,增加图片,不应改变文档内容
在adobe reader上查看
数字签名签在第一张图上,后面 签空域
有数字签名的图片
后面的签空域:
package signPDF;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import java.awt.Color;
import java.io.*;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampRequestGenerator;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;
import org.bouncycastle.util.encoders.Base64;
/**
* Created by 巩希波 on 2018/06/1.
*
*/
public class PdfTqifengStamp {
//tsa
private static long start;
private SignerKeystore signerKeystore;
private TSAClient tsaClient;
private String signName="xyb"+System.currentTimeMillis();
private PdfTqifengStamp(){}
/**
*
* @param tsa_url tsa服务器地址
* @param tsa_accnt tsa账户号
* @param tsa_passw tsa密码
* @param cert_path 证书路径
* @param cert_passw 证书密码
*/
public PdfTqifengStamp(String tsa_url,String tsa_accnt,String tsa_passw,String cert_path,String cert_passw) {
tsaClient = new TSAClientBouncyCastle(tsa_url, tsa_accnt, tsa_passw);
try {
signerKeystore = new SignerKeystorePKCS12(new FileInputStream(cert_path), cert_passw);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 在已有的PDF文件中添加签名区域
*
* @param nImage 图片
* @param stamper PDF文件编辑对象
* @param sigName 页面
* @param pageNo 那一页
* @param llx x坐标
* @param lly y坐标
*/
public static void addAppearance(Image nImage, PdfStamper stamper, String sigName, int pageNo, float llx, float lly) {
// 创建数字签名域
PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
field.setFieldName(sigName);
// set the widget properties
// field.setPage(pageNo);
float width = nImage.getWidth();
float height = nImage.getHeight();
Rectangle rect = new Rectangle(llx-width, lly, llx, lly+height);;
field.setWidget(rect, PdfAnnotation.HIGHLIGHT_NONE);
field.setFlags(PdfAnnotation.FLAGS_PRINT);//是否可打印
// System.out.println(rect.getWidth() + "*" + rect.getHeight());
// 设置区域宽高和边框厚度,以及边框颜色,填充颜色
// PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(), rect.getWidth(), rect.getHeight());
PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(), width, height);
// tp.setColorStroke(new BaseColor(0, 0, 200));
// tp.setColorFill(new BaseColor(230, 230, 240));
// 绘制并填充
// tp.rectangle(b / 2, b / 2, rect.getWidth() - b, rect.getHeight() - b);
// tp.fillStroke();
try {
tp. addImage(nImage, width, 0, 0, height, 0, 0);
// tp.addImage(image, a, b, c, d, e, f);
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("创建签名区域失败, 区域名称:" + sigName + e);
}
// 支持中文
/* try {
// BaseFont cnBaseFont = loadFont("SIMFANG"); // 加载字体,请自己实现
BaseFont cnBaseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font cnFont = new Font(cnBaseFont, 9, Font.NORMAL, BaseColor.BLACK);
ColumnText.showTextAligned(tp, Element.ALIGN_CENTER, new Phrase(40f, "签名区域", cnFont), rect.getWidth() / 2,
rect.getHeight() / 2, 0);
} catch (Exception e) {
System.out.println("创建签名区域失败, 区域名称:" + sigName + e);
}*/
field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp);
// add it as an annotation
stamper.addAnnotation(field, pageNo);
}
/**
* TSA时间戳签名
* @param infilePath 未签名的文件路径
* @param outfilePath 签名后的文件路径
* @throws Exception
*/
public void signPDF(String infilePath,String outfilePath) throws Exception {
PdfReader reader = new PdfReader(infilePath);
FileOutputStream fout = new FileOutputStream(outfilePath);
//一次签名
// PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');
//多次签名
// PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', null, true);
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', null, true);
PdfSignatureAppearance sap = stp.getSignatureAppearance();
sap.setCrypto(null, this.signerKeystore.getChain(), null, PdfSignatureAppearance.SELF_SIGNED);
// //修改签名时间
// Calendar call=Calendar.getInstance();
//// call.set(2008, 4, 9);//修改签名时间
// call.setTimeInMillis(Long.valueOf("1525256602000"));
// sap.setSignDate(call);
Image image = Image.getInstance("E:\\pdfsign\\meinv.jpg"); //使用png格式透明图片
sap.setSignatureGraphic(image);
sap.setAcro6Layers(true);
sap.setRenderingMode(RenderingMode.GRAPHIC);
// sap.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, "Signature5");
sap.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, signName);
//骑缝章 切割图片
Rectangle pageSize = reader.getPageSize(1);//获得第一页
float height = pageSize.getHeight();
float width = pageSize.getWidth();
int nums = reader.getNumberOfPages();
Image[] nImage = PDFStamperCheckMark.subImages("E:/pdfsign/qifeng.png",nums);//生成骑缝章切割图片
for(int n=0;n 0) {
messageDigest.update(buf, 0, n);
}
byte hash[] = messageDigest.digest();
Calendar cal = Calendar.getInstance();
byte[] ocsp = null;
if ( this.signerKeystore.getChain().length >= 2) {
String url = PdfPKCS7.getOCSPURL((X509Certificate) this.signerKeystore.getChain()[0]);
if (url != null && url.length() > 0)
// ocsp = new OcspClientBouncyCastle((X509Certificate)this.signerKeystore.getChain()[0], (X509Certificate)this.signerKeystore.getChain()[1], url).getEncoded();
ocsp = new OcspClientBouncyCastle().getEncoded((X509Certificate)this.signerKeystore.getChain()[0], (X509Certificate)this.signerKeystore.getChain()[1], url);
}
byte sh[] = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp);
sgn.update(sh, 0, sh.length);
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, this.tsaClient, ocsp);
//获取不到,为null,源码中看,没有初始化
// TimeStampToken token = sgn.getTimeStampToken();
// Calendar call = sgn.getTimeStampDate();
//算法
// String algorithm = tsaClient.getDigestAlgorithm();
// System.out.println("algorithm = " + algorithm);
if (contentEstimated + 2 < encodedSig.length)
throw new Exception("Not enough space");
byte[] paddedSig = new byte[contentEstimated];
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
// //休眠10s
// Thread.sleep(10000);
//
// SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
// System.out.println("签名时间戳 TimeStamp: " + date_format.format(sgn.getTimeStampDate().getTime()));
// System.out.println(sgn.getSignDate().toString());
reader.close();
// stp.close();
sap.close(dic2);
//暂时关掉
// inspectSignatures(outfilePath);
// changeDirectory("20171214104349700104_2.pdf","E:/pdfsign/test","E:/pdfsign");
}
public void inspectSignatures(String path) throws IOException, GeneralSecurityException {
//不加这两句,有的格式读不出
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
System.out.println(path);
PdfReader reader = new PdfReader(path);
AcroFields fields = reader.getAcroFields();
//如果没有签名,names为空
ArrayList names = fields.getSignatureNames();
for (String name : names) {
System.out.println("===== " + name + " =====");
readTimeStamp(fields, name);
}
System.out.println("===== " + signName + " =====");
// readTimeStamp(fields, signName);
System.out.println();
}
void readTimeStamp(AcroFields fields, String name) throws SignatureException{
PdfPKCS7 pkcs7 = fields.verifySignature(name);
//签名破坏,输出false
System.out.println("Integrity check OK? " + pkcs7.verify());
SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
// System.out.println("签名时间 原始Signed on: " + pkcs7.getSignDate().getTime());
System.out.println("签名时间 格式化Signed on: " + date_format.format(pkcs7.getSignDate().getTime()));
if (pkcs7.getTimeStampDate() != null) {
// System.out.println("签名时间戳 原始TimeStamp: " + pkcs7.getTimeStampDate().getTime());
System.out.println("签名时间戳 格式化TimeStamp: " + date_format.format(pkcs7.getTimeStampDate().getTime()));
// TimeStampToken ts = pkcs7.getTimeStampToken();
// System.out.println("TimeStamp service: " + ts.getTimeStampInfo().getTsa());
}
// 结束运行
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "ms.");
}
public static void main(String[] args) {
//开始运行
start = System.currentTimeMillis();
//test
// String TSA_URL = "http://tsa.safelayer.com:8093";
String TSA_URL = "http://timestamp.wosign.com/rfc3161";
String TSA_ACCNT = "";
String TSA_PASSW = "";
// String IN_FILE = "E:\\项目\\paperless\\lipsum.pdf";
// String IN_FILE = "E:\\pdfsign\\rectang_1.pdf";
// String IN_FILE = "E:\\pdfsign\\test接口操作1.pdf";
String IN_FILE = "E:\\pdfsign\\20180615145557407929.pdf";
// String IN_FILE = "E:\\pdfsign\\qifeng6.pdf";
// String OUT_FILE = "E:\\项目\\paperless\\test_signed.pdf";
// String OUT_FILE = "E:\\pdfsign\\test\\20171214104349700104_2.pdf";
String OUT_FILE = "E:\\pdfsign\\qifeng22.pdf";
// String CERT_PATH = "E:\\项目\\paperless\\bfnsh.pfx";
String CERT_PATH = "D:\\phpserver\\upload\\private_key\\user256.p12";
String CERT_PASSW = "123456";
PdfTqifengStamp signer = new PdfTqifengStamp(TSA_URL,TSA_ACCNT,TSA_PASSW,CERT_PATH,CERT_PASSW);
try {
signer.signPDF(IN_FILE,OUT_FILE);
} catch (Exception e) {
e.printStackTrace();
}
/* try {
new PDFTimeSigner2_Modify().inspectSignatures(OUT_FILE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
}
}
图片切割方法;
/**
* 切割图片
* @param imgPath 原始图片路径
* @param n 切割份数
* @return itextPdf的Image[]
* @throws IOException
* @throws BadElementException
*/
public static Image[] subImages(String imgPath,int n) throws IOException, BadElementException {
Image[] nImage = new Image[n];
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedImage img = ImageIO.read(new File(imgPath));
int h = img.getHeight();
int w = img.getWidth();
int sw = w/n;
for(int i=0;i
(很重要,不匹配容易报错)
项目结构:
jar包配置
参考:
PDF时间戳数字签名 - CSDN博客
http://blog.csdn.net/running_snail_/article/details/52995983
PDF盖骑缝章 - CSDN博客
https://blog.csdn.net/running_snail_/article/details/53008578
itext怎么设置空白的签名域
https://bbs.csdn.net/topics/392087132