基于Apache PDFBox的PDF数字签名
在Java语言环境中完成数字签名主要基于itext-pdf、PDFBox两种工具,itext-pdf受商业限制,应用于商业服务中需要购买授权。PDFBox是apache基金会开源项目,基于apache2.0开源协议,不受商业限制,开发者可放心使用。以下是基于PDFBox的数字签名源码,使用该源码可使用PDFBox对PDF格式的文件进行数字。此外,完成数字签名还生成数字证书,全流程的数字签名示例可下载开源项目模拟数字签名全流程。开源数字签名访问地址:https://gitee.com/kaifangqian
`package org.resrun.sdk.pdfbox;
import org.resrun.sdk.pdfbox.vo.AssinaturaModel;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.util.Matrix;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Calendar;
import java.util.List;
public class AssinaturaPDF extends SignatureBase {
private SignatureOptions signatureOptions;
private AssinaturaModel assinatura;
public AssinaturaPDF(AssinaturaModel assinaturaModel) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException {
super(assinaturaModel.getCertInfo());
this.assinatura = assinaturaModel;
if (assinaturaModel.getTsa() != null && !assinaturaModel.getTsa().equals("")) {
setTsaUrl(assinaturaModel.getTsa());
}
}
public byte [] assina() throws Exception {
if (assinatura.getPdf() == null || assinatura.getPdf().length == 0) {
throw new Exception("pdf file null");
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
PDDocument doc = Loader.loadPDF(assinatura.getPdf())) {
if (doc.isEncrypted()) {
try {
doc.setAllSecurityToBeRemoved(true);
} catch (Exception e) {
throw new Exception("pdf passwd error", e);
}
}
int accessPermissions = SigUtils.getMDPPermission(doc);
if (accessPermissions == 1)
{
throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
PDSignature signature = null;
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
PDRectangle rect = null;
// TODO 签名域的位置 可能需要再计算
Rectangle2D humanRect = new Rectangle2D.Float(assinatura.getPosition().getParseX(), assinatura.getPosition().getParseY(),
assinatura.getPosition().getSignWidthFloat(), assinatura.getPosition().getSignHeightFloat());
// sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
if (acroForm != null)
{
signature = findExistingSignature(acroForm, assinatura.getSignatureKey());
if (signature != null)
{
rect = acroForm.getField(assinatura.getSignatureKey()).getWidgets().get(0).getRectangle();
}
}
if (signature == null)
{
// create signature dictionary
signature = new PDSignature();
}
if (rect == null)
{
rect = createSignatureRectangle(doc, humanRect);
}
if (doc.getVersion() >= 1.5f && accessPermissions == 0)
{
SigUtils.setMDPPermission(doc, signature, 2);
}
if (acroForm != null && acroForm.getNeedAppearances())
{
// PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible
// with Adobe Reader
if (acroForm.getFields().isEmpty())
{
// we can safely delete it if there are no fields
acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
// note that if you've set MDP permissions, the removal of this item
// may result in Adobe Reader claiming that the document has been changed.
// and/or that field content won't be displayed properly.
// ==> decide what you prefer and adjust your code accordingly.
}
else
{
System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
}
}
// default filter
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
// subfilter for basic and PAdES Part 2 signatures
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Name");
signature.setLocation("Location");
signature.setReason("Reason");
// the signing date, needed for valid signature
signature.setSignDate(Calendar.getInstance());
signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect, signature));
signatureOptions.setPage(assinatura.getPosition().getPage());
// signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
doc.addSignature(signature, this, signatureOptions);
// doc.saveIncremental(bos);
doc.saveIncremental(bos);
IOUtils.closeQuietly(signatureOptions);
doc.close();
return bos.toByteArray();
// return null;
}
}
private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum,
PDRectangle rect, PDSignature signature) throws IOException
{
try (PDDocument doc = new PDDocument())
{
PDPage page = new PDPage(srcDoc.getPage(pageNum).getMediaBox());
doc.addPage(page);
PDAcroForm acroForm = new PDAcroForm(doc);
doc.getDocumentCatalog().setAcroForm(acroForm);
PDSignatureField signatureField = new PDSignatureField(acroForm);
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
List acroFormFields = acroForm.getFields();
acroForm.setSignaturesExist(true);
acroForm.setAppendOnly(true);
acroForm.getCOSObject().setDirect(true);
acroFormFields.add(signatureField);
widget.setRectangle(rect);
// from PDVisualSigBuilder.createHolderForm()
PDStream stream = new PDStream(doc);
PDFormXObject form = new PDFormXObject(stream);
PDResources res = new PDResources();
form.setResources(res);
form.setFormType(1);
PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight());
float height = bbox.getHeight();
Matrix initialScale = null;
switch (srcDoc.getPage(pageNum).getRotation())
{
case 90:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(1));
initialScale = Matrix.getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
height = bbox.getWidth();
break;
case 180:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(2));
break;
case 270:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(3));
initialScale = Matrix.getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
height = bbox.getWidth();
break;
case 0:
default:
break;
}
form.setBBox(bbox);
// from PDVisualSigBuilder.createAppearanceDictionary()
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject().setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
appearance.setNormalAppearance(appearanceStream);
widget.setAppearance(appearance);
try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream))
{
// for 90° and 270° scale ratio of width / height
// not really sure about this
// why does scale have no effect when done in the form matrix???
if (initialScale != null)
{
cs.transform(initialScale);
}
// show background (just for debugging, to see the rect size + position)
// cs.setNonStrokingColor(Color.yellow);
// cs.addRect(-5000, -5000, 10000, 10000);
cs.fill();
if (assinatura.getSignatureImage() != null)
{
PDImageXObject img = PDImageXObject.createFromByteArray(doc,assinatura.getSignatureImage(),"test");
int imgHeight = img.getHeight();
int imgWidth = img.getWidth();
cs.saveGraphicsState();
cs.transform(Matrix.getScaleInstance(rect.getWidth()/imgWidth*1.0f,rect.getHeight()/imgHeight*1.0f));
cs.drawImage(img, 0,0);
cs.restoreGraphicsState();
}
}
// no need to set annotations and /P entry
ByteArrayOutputStream baos = new ByteArrayOutputStream();
doc.save(baos);
// FileUtils.writeByteArrayToFile(new File("C:\Users\Administrator\Desktop\tem\pdfbox\sign-t.pdf"), baos.toByteArray());
return new ByteArrayInputStream(baos.toByteArray());
}
}
private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRect)
{
float x = (float) humanRect.getX();
float y = (float) humanRect.getY();
float width = (float) humanRect.getWidth();
float height = (float) humanRect.getHeight();
PDPage page = doc.getPage(0);
PDRectangle pageRect = page.getCropBox();
PDRectangle rect = new PDRectangle();
// signing should be at the same position regardless of page rotation.
switch (page.getRotation())
{
case 90:
rect.setLowerLeftY(x);
rect.setUpperRightY(x + width);
rect.setLowerLeftX(y);
rect.setUpperRightX(y + height);
break;
case 180:
rect.setUpperRightX(pageRect.getWidth() - x);
rect.setLowerLeftX(pageRect.getWidth() - x - width);
rect.setLowerLeftY(y);
rect.setUpperRightY(y + height);
break;
case 270:
rect.setLowerLeftY(pageRect.getHeight() - x - width);
rect.setUpperRightY(pageRect.getHeight() - x);
rect.setLowerLeftX(pageRect.getWidth() - y - height);
rect.setUpperRightX(pageRect.getWidth() - y);
break;
case 0:
default:
rect.setLowerLeftX(x);
rect.setUpperRightX(x + width);
rect.setLowerLeftY(pageRect.getHeight() - y - height);
rect.setUpperRightY(pageRect.getHeight() - y);
break;
}
return rect;
}
private PDSignature findExistingSignature(PDAcroForm acroForm, String sigFieldName)
{
PDSignature signature = null;
PDSignatureField signatureField;
if (acroForm != null)
{
signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
if (signatureField != null)
{
// retrieve signature dictionary
signature = signatureField.getSignature();
if (signature == null)
{
signature = new PDSignature();
// after solving PDFBOX-3524
// signatureField.setValue(signature)
// until then:
signatureField.getCOSObject().setItem(COSName.V, signature);
}
else
{
throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
}
}
}
return signature;
}
}
`
基于Apache PDFBox的PDF数字签名的更多相关文章
- apache pdfbox
转 http://www.blogjava.net/sxyx2008/archive/2010/07/23/326890.html 轻松使用apache pdfbox将pdf文件生成图 近期在项目中使 ...
- Apache PDFbox开发指南之PDF文档读取
转载请注明来源:http://blog.csdn.net/loongshawn/article/details/51542309 相关文章: <Apache PDFbox开发指南之PDF文本内容 ...
- 使用Apache PDFBox实现拆分、合并PDF
目录 使用Apache PDFBox实现拆分.合并PDF 问题背景 Apache PDFBox介绍 拆分PDF 合并PDF 拆分 + 合并 完整代码 参考: 使用Apache PDFBox实现拆分.合 ...
- Java 使用PDFBox提取PDF文件中的图片
今天做PDF文件解析,遇到一个需求:提取文件中的图片并保存.使用的是流行的apache开源jar包pdfbox, 但还是遇到坑了,比如pdfbox版本太高或太低都不能用!!这个包竟然没有很好地做好兼容 ...
- java 用PDFBox 删除 PDF文件中的某一页
依赖: <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox-app ...
- 使用pdfBox实现pdf转图片,解决中文方块乱码等问题
一.引入依赖 <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>fontbox ...
- APache PDFbox API使用(1)----简单介绍
因为项目的须要.近期在学习APache PDFbox API,Apache PDFbox API是Apache Java 开源社区中个一个项目,其受Apache 版权 V2的保护,其提供了以下的功能 ...
- pdfBox 读取pdf文件
1.引入maven依赖 <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pd ...
- pdfBox 解析 pdf文件
Spting boot 项目 1.添加依赖 <dependency> <groupId>org.apache.pdfbox</groupId> <artifa ...
- java 库 pdfbox 将 pdf 文件转换成高清图片方法
近期需要将 pdf 文件转成高清图片,使用库是 pdfbox.fontbox.可以使用 renderImageWithDPI 方法指定转换的清晰度,当然清晰度越高,转换需要的时间越长,转换出来的图片越 ...
随机推荐
- 十八般武艺玩转GaussDB(DWS)性能调优:Plan hint运用
摘要:本文介绍GaussDB(DWS)另一种可以人工干预计划生成的功能--plan hint. 前言 数据库的使用者在书写SQL语句时,会根据自己已知的情况尽力写出性能很高的SQL语句.但是当需要写大 ...
- 简化业务代码开发:看Lambda表达式如何将代码封装为数据
摘要:在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下lambad表达式及函数式接口特性. 1.Lambda 表达式 Lambda表达式也被称为箭头函数.匿名函数.闭包 ...
- 案例解读华为隐私计算产品TICS如何实现城市跨部门数据隐私计算
摘要:本文介绍华为可信智能计算服务TICs是如何助力城市跨部门数据实现隐私计算的. 本文分享自华为云社区<基于华为隐私计算产品TICS实现城市跨部门数据隐私计算,助力实现普惠金融>,作者: ...
- What?构造的查询语句会导致堆栈溢出
摘要:本文归纳了Neo4j和Nebula两个开源图数据库的两个pull request修复的堆栈溢出问题,并试着写写通过阅读pr中的问题而得到的一些启发 本文分享自华为云社区<巧妙构造的查询语句 ...
- 再谈BOM和DOM(3):DOM节点操作-元素样式修改及DOM内容增删改查
操作节点,先得选择节点,就得知道节点选择器与DOM节点查找 DOM节点选择器 W3C提供了比较方便的定位节点的方法和属性 getElementById() 一个参数:元素标签的ID getElemen ...
- 【Redis】面试题 GEO地理位置信息
目录 面试 1 http协议详情,http协议版本,http一些请求头 2 GET请求和POST请求的区别 3 如何实现服务器给客户端发送消息,websocket是什么? 4 悲观锁和乐观锁,如何实现 ...
- Linux day2:文件和文件夹相关命令 文件内容编辑命令 Linux常用目录 Linux重要文件
目录 问题说明 前期必备知识 系统运行命令 shutdown -c 快捷方式命令 ctrl+e 目录结构相关命令 mkdir -p 文件和文件夹相关命令 创建文件 touch 查看文件和目录 ls - ...
- Tomcat--安装&&配置文件
配置信息 centos:7.8 tomcat:7.0.3 jdk:1.8 1 部署java环境 [root@localhost ~]# tar xvf jdk-8u181-linux-x64.tar. ...
- CO41创建生产订单维护增强字段
一.CO41计划订单中新增增强字段 报表中新增字段,并可维护,当点击转换创建生产订单时,将四个字段的值,维护到生产订单对应的字段中 二.增强结构 在SFC_POCO中新增对应的字段 三.屏幕增强 找到 ...
- UVA - 10391:Compound Words (字符串水题)
题目大意 给定若干单词,按字典序输出由两个单词拼接而成的单词 思路分析 用set存储所有单词,枚举每个单词word,遍历word的所有左右子串组合情况,若左右子串均在set中,说明符合题意.时间复杂度 ...