首页 > 其他分享 >基于Apache PDFBox的PDF数字签名

基于Apache PDFBox的PDF数字签名

时间:2024-01-25 17:32:41浏览次数:23  
标签:数字签名 doc PDF signature Apache import new rect pdfbox

在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<PDField> 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;
    }
 
}

标签:数字签名,doc,PDF,signature,Apache,import,new,rect,pdfbox
From: https://blog.51cto.com/u_16230987/9416347

相关文章

  • 基于Apache PDFBox的PDF数字签名
    在Java语言环境中完成数字签名主要基于itext-pdf、PDFBox两种工具,itext-pdf受商业限制,应用于商业服务中需要购买授权。PDFBox是apache基金会开源项目,基于apache2.0开源协议,不受商业限制,开发者可放心使用。以下是基于PDFBox的数字签名源码,使用该源码可使用PDFBox对PDF格式的文件进......
  • 数据结构与算法 pdf下载
    《数据结构与算法》涉及计算机中数据的组织、重组、移动、使用和提取等操作方法,及相关的数学分析。《数据结构与算法》所选的主题基于以下几个朴素的原则。第一,本书只讲解实用的技术,而忽略一些理论上非常虽然出色、但不太实用的算法。第二,本书既包含经典的方法,也包括最近发现的......
  • 除了Adobe之外,还有什么方法可以将Excel转为PDF?
    前言Java是一种广泛使用的编程语言,它在企业级应用开发中发挥着重要作用。而在实际的开发过程中,我们常常需要处理各种数据格式转换的需求。今天小编为大家介绍下如何使用葡萄城公司的的JavaAPI组件GrapeCityDocumentsforExcel(以下简称为GcExcel)将ExcelXLSX文件转换为PDF。......
  • 【专题】2023年中国工业机器人行业研究报告PDF合集分享(附原数据表)
    原文链接:https://tecdat.cn/?p=34144原文出处:拓端数据部落公众号仿生机器人作为一类结合了仿生学原理的机器人,具备自主决策和规划行动的能力,正逐渐进入大众视野。它们的核心技术要素包括感知与认知技术、运动与控制技术、人机交互技术和自主决策技术。阅读原文,获取专题报告合集......
  • 《设计模式:可复用面向对象软件的基础》PDF
    内容简介本书结合设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中*有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好、表达清楚的软件设计模式,这些模式在实用环境下特别有用。本书适合大学计算机专业的学生、研究生及相关人......
  • 《设计模式之美》PDF
    内容简介本书结合真实项目案例,从面向对象编程范式、设计原则、代码规范、重构技巧和设计模式5个方面详细介绍如何编写高质量代码。第1章为概述,简单介绍了本书涉及的各个模块,以及各个模块之间的联系;第2章介绍面向对象编程范式;第3章介绍设计原则;第4章介绍代码规范;第5章介绍重构技巧;......
  • itext的PdfPCell中设置行间距失败的问题
     直接在构造方法添加的时候设置行间距是失效的PdfPCellpdfPCell=newPdfPCell(elements);//这个时候就不能直接把段落放构造方法中了,因为newPdfPCell(elements)底层使用的是this.column.setLeading(0.0F,1.0F);不会延用elements的setLeading,因此在单元格中添加段落建......
  • SQL必知必会(第5版)PDF下载
    SQL是使用最为广泛的数据库语言,几乎所有重要的DBMS都支持SQL。本书是麻省理工学院、伊利诺伊大学等众多大学的参考教材,由浅入深地讲解了SQL的基本概念和语法,涉及数据的排序、过滤和分组,以及表、视图、联结、子查询、游标、存储过程和触发器等内容,实例丰富,便于查阅。与其他同类图书......
  • 第一行代码 Android(第3版)PDF下载
    《第一行代码Android第3版》被Android开发者誉为“Android学习第一书”。全书系统全面、循序渐进地介绍了Android软件开发的必备知识、经验和技巧。《第一行代码Android第3版》基于Android10.0对第2版进行了全面更新,不仅将所有知识点都在Android10.0系统上进行了重新适配,同......
  • 《Java核心编程》PDF
    内容简介本书主要基于Java13来介绍Java核心编程相关的知识点,以及从Java8至Java13以来的新特性,主要内容包括:Java语言基础、面向对象编程、集合框架、异常处理、I/O处理、网络编程、并发编程、基本编程结构的改进、垃圾回收器的增强、使用脚本语言、Lambda表达式与函数式编程、St......