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

给pdf文件签名

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

一、文件准备

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

在这里插入图片描述

在这里插入图片描述

1. 构建印章

可以通过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("-------------构建印章结束---------------------");
    }

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

2. 获取证书

方法一 阿里云申请证书

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

方法二 自建证书

请见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");

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

传送门

Pdf文件签名检查

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值