• 利用证书给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) {
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    编写测试用例生成图片

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

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

    2. 获取证书

    方法一 阿里云申请证书

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

    方法二 自建证书

    请见keytool工具生成JKS证书

    二、利用证书给pdf签名

    需要的依赖

     
            <dependency>
                <groupId>com.itextpdfgroupId>
                <artifactId>itextpdfartifactId>
                <version>5.5.13.3version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    签名工具

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    编写测试用例

    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    三、设定签名位置

    在指定坐标签名

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

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

    在指定签名域签名

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

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

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

    appearance.setVisibleSignature("sign2");
    
    • 1

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

    传送门

    Pdf文件签名检查

  • 相关阅读:
    查询两张表的信息 可能用到两张实体类 所以可以创建一个新的实体类来装 新的实体类可以有两个表的属性,或者继承复用父类。
    python scanpy spatial空转全流程
    vue前端实现分页功能
    windows安装wsl2以及ubuntu
    构建全面预算体系,加强企业风险管理
    【附源码】计算机毕业设计JAVA车辆调度管理系统
    折腾Chrome 插件,怎么给右键菜单增加“选项”?
    C++ 同构字符串/ 单词规律
    HTTP协议详解
    H3C S7000/S7500E/10500系列交换机Console密码忘记处理方法
  • 原文地址:https://blog.csdn.net/u011628753/article/details/133071477