利用证书给pdf文件添加数字签名

news2024/11/17 23:28:21

文章目录

  • 给pdf文件签名
  • 文件准备
    • 构建印章
    • 获取证书
      • 方法一 阿里云申请证书
      • 方法二 自建证书
    • 利用证书给pdf签名
    • 在指定位置签名
      • 在指定坐标签名
      • 在指定签名域签名

给pdf文件签名

如何给pdf文件签名,这样pdf文件就具有不可修改性,具有鉴权、完整性、不可抵赖。

文件准备

需要一个印章图片和证书文件。

在这里插入图片描述

在这里插入图片描述

构建印章

可以通过ps或者其它方式自由构建一张透明底的图片印章或者用户手写的签名。
这里为了方便,直接使用代码生成一张方形印章。

import sun.font.FontDesignMetrics;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 图片生成
 */
public class ImageCreateUtils {


    /**
     * @param username
     * @param companyName
     * @param date
     * @param width
     * @param height
     * @param picname
     * @return
     */
    public static boolean createSignImage(
            String username, //
            String companyName, //
            String date,
            int width,
            int height,
            String picname) {
        FileOutputStream out = null;
        //背景色
        Color bgcolor = Color.WHITE;
        //字色
        Color fontcolor = Color.RED;
        Font userNameFont = new Font(null, Font.BOLD, 20);
        Font companyNameFont = new Font(null, Font.BOLD, 18);
        try { // 宽度 高度
            BufferedImage bimage = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g = bimage.createGraphics();
            g.setColor(bgcolor); // 背景色
            g.fillRect(0, 0, width, height); // 画一个矩形
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON); // 去除锯齿(当设置的字体过大的时候,会出现锯齿)

            g.setColor(Color.RED);
            g.fillRect(0, 0, 8, height);
            g.fillRect(0, 0, width, 8);
            g.fillRect(0, height - 8, width, height);
            g.fillRect(width - 8, 0, width, height);

            g.setColor(fontcolor); // 字的颜色
            g.setFont(userNameFont); // 字体字形字号
            FontMetrics fm = FontDesignMetrics.getMetrics(userNameFont);
            int font1_Hight = fm.getHeight();
            int strWidth = fm.stringWidth(username);
            int y = 35;
            int x = (width - strWidth) / 2;
            g.drawString(username, x, y); // 在指定坐标除添加文字

            g.setFont(companyNameFont); // 字体字形字号

            fm = FontDesignMetrics.getMetrics(companyNameFont);
            int font2_Hight = fm.getHeight();
            strWidth = fm.stringWidth(companyName);
            x = (width - strWidth) / 2;
            g.drawString(companyName, x, y + font1_Hight); // 在指定坐标除添加文字

            strWidth = fm.stringWidth(date);
            x = (width - strWidth) / 2;
            g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐标除添加文字

            g.dispose();
            ImageIO.write(bimage, picname.substring(picname.lastIndexOf(".") + 1), new File(picname));
            out.flush();
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

编写测试用例生成图片

@Test
    void createSignImage() {
        ImageCreateUtils.createSignImage("王二狗", "海港城纵横科技有限公司", "2050.09.05", 250, 100, "D:\\test3\\wangergou.jpg");
        System.out.println("-------------构建印章结束---------------------");
    }

运行测试用例
在这里插入图片描述

获取证书

方法一 阿里云申请证书

这里的证书是从阿里云下载获取的,你可以通过其它方式获取证书。
在这里插入图片描述

方法二 自建证书

请见keytool工具生成JKS证书

利用证书给pdf签名

需要的依赖

 <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.3</version>
        </dependency>

签名工具

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.*;

import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.Certificate;

public class KeystoreUtils {

    /**
     *
     * @param src 需要签章的pdf文件路径
     * @param dest 签完章的pdf文件路径
     * @param chain 证书链
     * @param img 印章图片
     * @param pk 签名私钥
     * @param digestAlgorithm 摘要算法名称,例如SHA-1
     * @param provider  密钥算法提供者,可以为null
     * @param subfilter 数字签名格式,itext有2种
     * @param reason 签名的原因,显示在pdf签名属性中
     * @param location 签名的地点,显示在pdf签名属性中
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws DocumentException
     */
    public void sign(String src, String dest,String img, Certificate[] chain, PrivateKey pk, String digestAlgorithm, String provider,
                     MakeSignature.CryptoStandard subfilter, String reason, String location) throws GeneralSecurityException, IOException, DocumentException {

        PdfReader pdfReader = new PdfReader(src);
        FileOutputStream fileOutputStream = new FileOutputStream(dest);

        /**
         * 1 参数依次为:文件名、文件输入流、文件版本号、临时文件、是否可以追加签名
         *  1.1 false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
         *  1.2 true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
         */
        PdfStamper stamper = PdfStamper.createSignature(pdfReader, fileOutputStream, '\0', null, false);
        // 获取数字签章属性对象,设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        /**
         * 1 三个参数依次为:设置签名的位置、页码、签名域名称,多次追加签名的时候,签名域名称不能一样
         *  1.1 签名的位置四个参数:印章左下角的X、Y轴坐标,印章右上角的X、Y轴坐标,
         *         这个位置是相对于PDF页面的位置坐标,即该坐标距PDF当前页左下角的坐标
         */
        appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "sign");
//        appearance.setVisibleSignature("sign2");

        /**
         * 用于盖章的印章图片,引包的时候要引入itext包的image
         */
        Image image = Image.getInstance(img);
        appearance.setSignatureGraphic(image);

        /**
         * 设置认证等级,共4种,分别为:
         *  NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、
         *  CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
         *
         * 需要用哪一种根据业务流程自行选择
         */
        appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

        /**
         * 印章的渲染方式,同样有4种:
         *  DESCRIPTION、NAME_AND_DESCRIPTION,
         *  GRAPHIC_AND_DESCRIPTION,GRAPHIC;
         * 这里选择只显示印章
         */
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

        /**
         * 算法主要为:RSA、DSA、ECDSA
         * 摘要算法,这里的itext提供了2个用于签名的接口,可以自己实现
         */
        ExternalDigest digest = new BouncyCastleDigest();
        /**
         * 签名算法,参数依次为:证书秘钥、摘要算法名称,例如MD5 | SHA-1 | SHA-2.... 以及 提供者
         */
        ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, null);
        /**
         * 最重要的来了,调用itext签名方法完成pdf签章
         */
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
    }
}

编写测试用例

@Test
    void addSign() {
        try {
            String KEYSTORE = "D:\\test3\\cert2\\www.xxx.space.jks";//证书文件
            char[] PASSWORD = "0t5uiwai".toCharArray();//证书密码
            String IMG = "D:\\test3\\wangergou.jpg";//用户的签名
            String SRC = "D:\\test3\\test1.pdf"; //待签文件
            String OUTPUT_SRC = "D:\\test3\\test1_sign123.pdf";//签名输出的文件
            //读取keystore ,获得私钥和证书链
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream(KEYSTORE), PASSWORD);
            String alias = (String)keyStore.aliases().nextElement();
            PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, PASSWORD);
            Certificate[] chain = keyStore.getCertificateChain(alias);

            KeystoreUtils keystoreUtils = new KeystoreUtils();
            keystoreUtils.sign(SRC, String.format(OUTPUT_SRC, 3),IMG, chain, PrivateKey, DigestAlgorithms.SHA1, null, MakeSignature.CryptoStandard.CMS, "文件已签名", "Beijing");
            System.out.println("--------------签名完成---------------");
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, e.getMessage());
            e.printStackTrace();
        }
    }

执行测试用例生成签名后文件
在这里插入图片描述

在指定位置签名

在指定坐标签名

/**
         * 1 三个参数依次为:设置签名的位置、页码、签名域名称,多次追加签名的时候,签名域名称不能一样
         *  1.1 签名的位置四个参数:印章左下角的X、Y轴坐标,印章右上角的X、Y轴坐标,
         *         这个位置是相对于PDF页面的位置坐标,即该坐标距PDF当前页左下角的坐标
         */
        appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "sign");

该方法不需要提前在pdf文件设定签名域,直接根据坐标位置签名

在指定签名域签名

编辑pdf模板,使用pdf软件,编辑表单,在需要的位置添加数字签名。
在这里插入图片描述

这里设定的数字签名的签名域为sign2

代码中在此位置签名,由于文件已经设定了数字签名的位置,所以不需要指定坐标了。
在这里插入图片描述

appearance.setVisibleSignature("sign2");

再次执行签名测试用例。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1025428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【操作系统笔记】内存寻址

物理寻址 主存&#xff08;内存&#xff09; 计算机主存也可以称为物理内存&#xff0c;内存可以看成由若干个连续字节大小的单元组成的数组每个字节都有一个唯一的物理地址&#xff08;Physical Address&#xff09;CPU访问内存前&#xff0c;先拿到内存地址&#xff0c;然后…

通讯网关软件005——利用CommGate X2OPC实现OPC客户端访问MS SQL服务器

本文推荐利用CommGate X2OPC实现OPC客户端访问MS SQL服务器获取数据。CommGate X2OPC是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;上位机通过OPC Client软件来获取MS SQL数据库的数据。 …

Qt扩展-KDDockWidgets 简介及配置

Qt扩展-KDDockWidgets 简介及配置] 一、概述二、编译 KDDockWidgets 库1. Cmake Gui 中选择源文件和编译后的路径2. 点击Config&#xff0c;配置好编译器3. 点击Generate4. 在存放编译的文件夹输入如下命令开始编译 三、qmake 配置 一、概述 kdockwidgets是一个由KDAB组织编写…

基于GBDT+Tkinter+穷举法按排队时间预测最优路径的智能导航推荐系统——机器学习算法应用(含Python工程源码)+数据集(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Pycharm 环境Scikit-learnt 模块实现1. 数据预处理2. 客流预测3. 百度地图API调用4. GUI界面设计1&#xff09;手绘地图导入2&#xff09;下拉菜单设计3&#xff09;复选框设计4&#xff09;最短路径结果输出界面…

睿趣科技:抖音开一家网店大概什么时候回本

随着移动互联网的迅猛发展&#xff0c;社交媒体平台如抖音成为了商家推广产品和服务的热门渠道之一。许多人都希望能够通过在抖音上宣传自己的产品&#xff0c;甚至考虑开一家网店&#xff0c;但回本的时间是一个关键问题。本文将探讨抖音开一家网店大概什么时候回本的问题。 首…

calloc

目录 前提须知&#xff1a; 函数介绍&#xff1a; 原型如下&#xff1a; 申请空间&#xff1a; 判断是否申请成功&#xff1a; 释放空间&#xff1a; calloc与malloc的相同之处与不同之处&#xff1a; 相同之处&#xff1a; 不同之处&#xff1a; 举例&#xff1a; ca…

API接口知识小结及电商数据平台商品详情接口请求案例

应用程序接口API&#xff08;Application Programming Interface&#xff09;&#xff0c;是提供特定业务输出能力、连接不同系统的一种约定。这里包括外部系统与提供服务的系统&#xff08;中后台系统&#xff09;或后台不同系统之间的交互点。包括外部接口、内部接口&#xf…

无涯教程-JavaScript - SUMXMY2函数

描述 SUMXMY2函数返回两个数组中相应值的差的平方和。 语法 SUMXMY2 (array_x, array_y)争论 Argument描述Required/OptionalArray_xThe first array or range of values.RequiredArray_yThe second array or range of values.Required Notes 参数应为数字或名称,数组或包…

Vite前端构建工具详解

Vite前端构建工具 Vite 是一款新兴的前端构建工具&#xff0c;它的出现带来了前端开发体验的革命性变化。本文将介绍 Vite 的基本概念和核心特性&#xff0c;并通过代码实例来演示其强大功能。 什么是 Vite&#xff1f; Vite 是由 Evan You&#xff08;Vue.js 的创始人&#x…

掌握进度管理基本指南,保证项目不延期

项目管理中的进度管理是规划、制定、控制和监控项目时间表的过程&#xff0c;确保任务和活动按时完成。 假设你是一名项目经理&#xff0c;带着团队组织一场备受瞩目的音乐节。精确的时间安排是关键。你需要确保演出者准时到达并按计划表演&#xff0c;所有供应商都准备就绪&a…

「干货」洁净室悬浮粒子计数器全部常见型号参数汇总

我们的人体工程学设计轻巧的Lighthouse手持式3016-IAQ是市场上先进的手持式粒子计数器&#xff0c;其质量浓度模式的密度约为μg/ m3。Lighthouse手持式粒子计数器最多可提供6个粒径同时计数的通道&#xff0c;可在快速&#xff0c;易于阅读的彩色触摸屏上显示累积和差分粒子计…

推荐一个可以采集众多国内外电商网站数据的工具

淘宝API 接入地址 item_get 获得淘宝商品详情item_get_pro 获得淘宝商品详情高级版item_review 获得淘宝商品评论item_fee 获得淘宝商品快递费用item_password 获得淘口令真实urlitem_list_updown 批量获得淘宝商品上下架时间seller_info 获得淘宝店铺详情item_search 按关键…

做题杂记222

文章目录 题1题2题3题4 一些较简单的题目。里面有些小点&#xff0c;稍不留意跑起来也挺费时。 题1 leak 题目描述&#xff1a; #!/usr/bin/env python # -*- coding: utf-8 -*- from Crypto.Util.number import * from secret import FLAGm bytes_to_long(FLAG)def getpq(n…

新消费降温,良品铺子还能走多远?

如果时间倒退到多年前&#xff0c;杨红春应该不会料到现在良品铺子的境遇。 从2006年创立至今&#xff0c;前半段良品铺子经历了品牌升级&#xff0c;从线下发展到平台电商、社交电商&#xff0c;做APP客户端进行全渠道的营销&#xff0c;把一家曾入不敷出的小店&#xff0c;养…

echarts 轴文字内容太长导致显示不全解决方案

情况&#xff1a;Y轴内容是文字&#xff0c;内容太长可能会系那是不全。调整grid会导致短数据前的留白过于多。 解决方案&#xff1a; 方法一&#xff1a; 首先可以配置grid自适应 &#xff0c; containLabel 属性设置true &#xff08;这常用于『防止标签溢出』的场景&…

51单片机光照强度检测自动路灯开关仿真( proteus仿真+程序+报告+讲解视频)

51单片机光照强度检测自动路灯开关仿真( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0052 讲解视频 基于51单片机的光照检测自动路灯控制仿真设计( proteus仿…

42V转5V芯片——高效率、宽压输入范围和强大输出能力

42V转5V芯片——高效率、宽压输入范围和强大输出能力 摘要&#xff1a; 42V转5V芯片是一种电源管理解决方案&#xff0c;可以将高达42V的输入电压转换为稳定的5V输出电压。该芯片以其高效率、宽压输入范围和强大的输出能力而备受关注。它具有93%的转换效率&#xff0c;能够在广…

学Python的漫画漫步进阶 -- 第十五步.访问数据库

学Python的漫画漫步进阶 -- 第十五步.访问数据库 十五、访问数据库15.1 SQLite数据库15.1.1 SQLite数据类型15.1.2 Python数据类型与SQLite数据类型的映射15.1.3 使用GUI管理工具管理SQLite数据库 15.2 数据库编程的基本操作过程15.3 sqlite3模块API15.3.1 数据库连接对象Conne…

使用Tensorboard碰到AttributeError: module ‘distutils‘ has no attribute ‘version‘

当出现这个错误时 说明 当前tensorboard的版本和你安装的pytorch版本不匹配&#xff0c;tensorboard版本太 高&#xff0c;pytorch太低。 使用conda list查看你的setuptools版本 conda list我的版本有点高68&#xff0c;所以我需要降低版本&#xff0c;使用以下命令降低版本…

解读|美创深度参与5项电信和互联网行业数据安全标准发布实施

《数据安全法》、《个人信息保护法》等法律法规的颁布实施&#xff0c;坚持安全和发展并重的原则&#xff0c;积极应对复杂严峻的安全风险与挑战&#xff0c;加速构建数据安全保障体系&#xff0c;成为电信和互联网行业重要工作。 “安全发展、标准先行”&#xff0c;标准化工作…