利用Java对PDF文件进行电子签章的实战过程

目录
  • 一、 概述
  • 二、 技术选型
  • 三、 生成一个图片签章
    • 1. 生成一个如下图的签章图片
    • 2. 相关代码
  • 四、 如何按模板生成PDF文件
    • 1. 制作PDF模板
    • 2. 制作一个如下图的PDF模板,该模板是带有PDF的表单域的
  • 五、 如何生成PKCS12证书
    • 1. PKCS的简单介绍
    • 2. 使用JAVA生成一个PKCS12证书并进行存贮,相关分析见代码注解
  • 六、 如何生成一个高清晰的签章
    • 1. 由PDF模板生成一个PDF文件,见代码注解
    • 2. 对PDF文件进行签章
    • 3. 高清签章
  • 七、 如何进行多次PDF签名
  • 八、 总结

一、 概述

印章是我国特有的历史文化产物,古代主要用作身份凭证和行驶职权的工具。它的起源是由于社会生活的实际需要。早在商周时代,印章就已经产生。如今的印章已成为一种独特的,融实用性和艺术性为一体的艺术瑰宝。传统的印章容易被坏人、小人私刻;从而新闻鲜有报道某某私刻公章,侵吞国家财产。随着计算机技术、加密技术及图像处理技术的发展,出现了电子签章。电子签章是电子签名的一种表现形式,利用图像处理技术、数字加密技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性。

电子签章与数字证书一样是身份验证的一种手段,泛指所有以电子形式存在,依附在电子文件并与其逻辑关联,可用以辨识电子文件签署者身份,保证文件的完整性,并表示签署者同意电子文件所陈述事实的内容。一般来说对电子签章的认定都是从技术角度而言的。主要是指通过特定的技术方案来鉴别当事人的身份及确保电子资料内容不被篡改的安全保障措施。电子签章常于发送安全电子邮件、访问安全站点、网上招标投标、网上签约、安全网上公文传送、公司合同、电子处方笺等。

电子签章是一个很复杂的问题,大到有相关的电子签章系统;今天分享一下如何把电子签章应用到电子处方笺的PDF文件里。

二、 技术选型

目前主流处理PDF文件两个jar包分别是:

  • 开源组织Apache的PDFBox,官网https://pdfbox.apache.org/
  • 大名鼎鼎adobe公司的iText,官网https://itextpdf.com/tags/adobe,其中iText又分为iText5和iText7

如何在PDFBox、iText5和iText7选出合适自己项目的技术呢?

对比PDFBox、iText5和iText7这三者:

  1. PDFBox的功能相对较弱,iText5和iText7的功能非常强悍;
  2. iText5的资料网上相对较多,如果出现问题容易找到解决方案;PDFBox和iText7的网上资料相对较少,如果出现问题不易找到相关解决方案;
  3. 通过阅读PDFBox代码目前PDFBox还没提供自定义签章的相关接口;iText5和iText7提供了处理自定义签章的相关实现;
  4. PDFBox只能实现把签章图片加签到PDF文件;iText5和iText7除了可以把签章图片加签到PDF文件,还可以实现直接对签章进行绘制,把文件绘制到签章上。
  5. PDFBox和iText5/iText7使用的协议不一样。PDFBox使用的是APACHE LICENSE VERSION 2.0(https://www.apache.org/licenses/);iText5/iText7使用的是AGPL(https://itextpdf.com/agpl)。PDFBox免费使用,AGPL商用收费

本分享JAVA对PDF文件进行电子签章需要实现的功能:

  1. 生成证书。与PDFBox、iText5和iText7技术无关
  2. 按模板输出PDF文件:PDFBox、iText5和iText7都可以完成,但是PDFBox会遇到中文乱码比较棘手的问题
  3. 在PDF文件中实现把签章图片加签到PDF文件:PDFBox、iText5和iText7都可以实现,没有很多的区别
  4. 在PDF文件中绘制签章:iText5和iText7都可以实现,PDFBox目前不支持
  5. 在PDF文件中生成高清签章:iText5和iText7都可以实现,PDFBox目前不支持
  6. 在PDF文件中进行多次签名::PDFBox、iText5和iText7都可以完成,没有区别

通过相关技术分析和要实现的功能分析,采用iText5进行开发,唯一遗憾的是iText商用收费;但是这不是做技术需要关心的!!选用iText5的理由:

  • 使用iText5能实现全部的功能
  • 如何在开发中遇到相关问题,容易找到相应解决方案

三、 生成一个图片签章

1. 生成一个如下图的签章图片

2. 相关代码

    import java.awt.Color;
    import java.awt.Font;
    import java.awt.FontMetrics;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.image.BufferedImage;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import sun.font.FontDesignMetrics;

    import com.sun.image.codec.jpeg.JPEGCodec;
    import com.sun.image.codec.jpeg.JPEGEncodeParam;
    import com.sun.image.codec.jpeg.JPEGImageEncoder;

    public class SignImage {

    /**
     * @param doctorName
     *            String 医生名字
     * @param hospitalName
     *            String 医生名称
     * @param date
     *            String 签名日期
     *            图片高度
     * @param jpgname
     *            String jpg图片名
     * @return
     */
    public static boolean createSignTextImg(
            String doctorName, //
            String hospitalName, //
            String date,
            String jpgname) {
        int width = 255;
        int height = 100;
        FileOutputStream out = null;
        //背景色
        Color bgcolor = Color.WHITE;
        //字色
        Color fontcolor = Color.RED;
        Font doctorNameFont = new Font(null, Font.BOLD, 20);
        Font othorTextFont = 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(doctorNameFont); // 字体字形字号
            FontMetrics fm = FontDesignMetrics.getMetrics(doctorNameFont);
            int font1_Hight = fm.getHeight();
            int strWidth = fm.stringWidth(doctorName);
            int y = 35;
            int x = (width - strWidth) / 2;
            g.drawString(doctorName, x, y); // 在指定坐标除添加文字

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

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

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

            g.dispose();
            out = new FileOutputStream(jpgname); // 指定输出文件
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bimage);
            param.setQuality(50f, true);
            encoder.encode(bimage, param); // 存盘
            out.flush();
            return true;
        } catch (Exception e) {
            return false;
        }finally{
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
    }
    public static void main(String[] args) {
        createSignTextImg("华佗", "在线医院", "2018.01.01",   "sign.jpg");
    }
}

四、 如何按模板生成PDF文件

1. 制作PDF模板

目前PDF模板工具别无他物,只能使用伟大的Adobe公司提供的Adobe Acrobatpro DC软件进行制作。如何使用该软件这里就不多说了,如果在使用中遇到什么可以另外咨询。

2. 制作一个如下图的PDF模板,该模板是带有PDF的表单域的

五、 如何生成PKCS12证书

1. PKCS的简单介绍

PKCS:The Public-Key Cryptography Standards (简称PKCS)是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。

到1999年底,PKCS已经公布了以下标准:

  • PKCS#1:定义RSA公开密钥算法加密和签名机制,主要用于组织PKCS#7中所描述的数字签名和数字信封[22]。
  • PKCS#3:定义Diffie-Hellman密钥交换协议[23]。
  • PKCS#5:描述一种利用从口令派生出来的安全密钥加密字符串的方法。使用MD2或MD5 从口令中派生密钥,并采用DES-CBC模式加密。主要用于加密从一个计算机传送到另一个计算机的私人密钥,不能用于加密消息[24]。
  • PKCS#6:描述了公钥证书的标准语法,主要描述X.509证书的扩展格式[25]。
  • PKCS#7:定义一种通用的消息语法,包括数字签名和加密等用于增强的加密机制,PKCS#7与PEM兼容,所以不需其他密码操作,就可以将加密的消息转换成PEM消息[26]。
  • PKCS#8:描述私有密钥信息格式,该信息包括公开密钥算法的私有密钥以及可选的属性集等[27]。
  • PKCS#9:定义一些用于PKCS#6证书扩展、PKCS#7数字签名和PKCS#8私钥加密信息的属性类型[28]。
  • PKCS#10:描述证书请求语法[29]。
  • PKCS#11:称为Cyptoki,定义了一套独立于技术的程序设计接口,用于智能卡和PCMCIA卡之类的加密设备[30]。
  • PKCS#12:描述个人信息交换语法标准。描述了将用户公钥、私钥、证书和其他相关信息打包的语法[31]。
  • PKCS#13:椭圆曲线密码体制标准[32]。
  • PKCS#14:伪随机数生成标准。
  • PKCS#15:密码令牌信息格式标准[33]。

PKCS12也就是以上标准的PKCS#12,主要用来描述个人身份信息;本次分享中要进行签章操作的是医生和药师,他们就是一个个人主体,给他们分配一个PKCS12的证书,就等于给他们分配了一个用于盖章的印章。

2. 使用JAVA生成一个PKCS12证书并进行存贮,相关分析见代码注解

    public class Extension {

        private String oid;

        private boolean critical;

        private byte[] value;

        public String getOid() {
            return oid;
        }

        public byte[] getValue() {
            return value;
        }
        public boolean isCritical() {
            return critical;
        }
    }

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.math.BigInteger;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.KeyStore;
    import java.security.NoSuchAlgorithmException;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.SecureRandom;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Random;

    import org.bouncycastle.asn1.ASN1ObjectIdentifier;
    import org.bouncycastle.asn1.ASN1Primitive;
    import org.bouncycastle.asn1.x500.X500Name;
    import org.bouncycastle.asn1.x509.BasicConstraints;
    import org.bouncycastle.asn1.x509.CRLDistPoint;
    import org.bouncycastle.asn1.x509.DistributionPoint;
    import org.bouncycastle.asn1.x509.DistributionPointName;
    import org.bouncycastle.asn1.x509.GeneralName;
    import org.bouncycastle.asn1.x509.GeneralNames;
    import org.bouncycastle.asn1.x509.KeyUsage;
    import org.bouncycastle.cert.X509CertificateHolder;
    import org.bouncycastle.cert.X509v3CertificateBuilder;
    import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.operator.ContentSigner;
    import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

    public class Pkcs {

    private static KeyPair getKey() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",
                new BouncyCastleProvider());
        generator.initialize(1024);
        // 证书中的密钥 公钥和私钥
        KeyPair keyPair = generator.generateKeyPair();
        return keyPair;
    }

    /**
     * @param password
     *            密码
     * @param issuerStr 颁发机构信息
     *
     * @param subjectStr 使用者信息
     *
    * @param certificateCRL 颁发地址
     *
     * @return
     */
    public static Map<String, byte[]> createCert(String password,
            String issuerStr, String subjectStr, String certificateCRL) {
        Map<String, byte[]> result = new HashMap<String, byte[]>();
        ByteArrayOutputStream out = null;
        try {
            // 生成JKS证书
            // KeyStore keyStore = KeyStore.getInstance("JKS");
            // 标志生成PKCS12证书
            KeyStore keyStore = KeyStore.getInstance("PKCS12",
                    new BouncyCastleProvider());
            keyStore.load(null, null);
            KeyPair keyPair = getKey();
            // issuer与 subject相同的证书就是CA证书
            Certificate cert = generateCertificateV3(issuerStr, subjectStr,
                    keyPair, result, certificateCRL, null);
            // cretkey随便写,标识别名
            keyStore.setKeyEntry("cretkey", keyPair.getPrivate(),
                    password.toCharArray(), new Certificate[] { cert });
            out = new ByteArrayOutputStream();
            cert.verify(keyPair.getPublic());
            keyStore.store(out, password.toCharArray());
            byte[] keyStoreData = out.toByteArray();
            result.put("keyStoreData", keyStoreData);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
        return result;
    }

    /**
     * @param issuerStr
     * @param subjectStr
     * @param keyPair
     * @param result
     * @param certificateCRL
     * @param extensions
     * @return
     */
    public static Certificate generateCertificateV3(String issuerStr,
            String subjectStr, KeyPair keyPair, Map<String, byte[]> result,
            String certificateCRL, List<Extension> extensions) {
        ByteArrayInputStream bout = null;
        X509Certificate cert = null;
        try {
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            Date notBefore = new Date();
            Calendar rightNow = Calendar.getInstance();
            rightNow.setTime(notBefore);
            // 日期加1年
            rightNow.add(Calendar.YEAR, 1);
            Date notAfter = rightNow.getTime();
            // 证书序列号
            BigInteger serial = BigInteger.probablePrime(256, new Random());
            X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                    new X500Name(issuerStr), serial, notBefore, notAfter,
                    new X500Name(subjectStr), publicKey);
            JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder(
                    "SHA1withRSA");
            SecureRandom secureRandom = new SecureRandom();
            jBuilder.setSecureRandom(secureRandom);
            ContentSigner singer = jBuilder.setProvider(
                    new BouncyCastleProvider()).build(privateKey);
            // 分发点
            ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier(
                    "2.5.29.31");
            GeneralName generalName = new GeneralName(
                    GeneralName.uniformResourceIdentifier, certificateCRL);
            GeneralNames seneralNames = new GeneralNames(generalName);
            DistributionPointName distributionPoint = new DistributionPointName(
                    seneralNames);
            DistributionPoint[] points = new DistributionPoint[1];
            points[0] = new DistributionPoint(distributionPoint, null, null);
            CRLDistPoint cRLDistPoint = new CRLDistPoint(points);
            builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);
            // 用途
            ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier(
                    "2.5.29.15");
            // | KeyUsage.nonRepudiation | KeyUsage.keyCertSign
            builder.addExtension(keyUsage, true, new KeyUsage(
                    KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
            // 基本限制 X509Extension.java
            ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier(
                    "2.5.29.19");
            builder.addExtension(basicConstraints, true, new BasicConstraints(
                    true));
            // privKey:使用自己的私钥进行签名,CA证书
            if (extensions != null)
                for (Extension ext : extensions) {
                    builder.addExtension(
                            new ASN1ObjectIdentifier(ext.getOid()),
                            ext.isCritical(),
                            ASN1Primitive.fromByteArray(ext.getValue()));
                }
            X509CertificateHolder holder = builder.build(singer);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            bout = new ByteArrayInputStream(holder.toASN1Structure()
                    .getEncoded());
            cert = (X509Certificate) cf.generateCertificate(bout);
            byte[] certBuf = holder.getEncoded();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            // 证书数据
            result.put("certificateData", certBuf);
            //公钥
            result.put("publicKey", publicKey.getEncoded());
            //私钥
            result.put("privateKey", privateKey.getEncoded());
            //证书有效开始时间
            result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
            //证书有效结束时间
            result.put("notAfter", format.format(notAfter).getBytes("utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bout != null) {
                try {
                    bout.close();
                } catch (IOException e) {
                }
            }
        }
        return cert;
    }

    public static void main(String[] args) throws Exception{
        // CN: 名字与姓氏    OU : 组织单位名称
        // O :组织名称  L : 城市或区域名称  E : 电子邮件
        // ST: 州或省份名称  C: 单位的两字母国家代码
        String issuerStr = "CN=在线医院,OU=gitbook研发部,O=gitbook有限公司,C=CN,E=gitbook@sina.com,L=北京,ST=北京";
        String subjectStr = "CN=huangjinjin,OU=gitbook研发部,O=gitbook有限公司,C=CN,E=huangjinjin@sina.com,L=北京,ST=北京";
        String certificateCRL  = "https://gitbook.cn";
        Map<String, byte[]> result = createCert("123456", issuerStr, subjectStr, certificateCRL);

        FileOutputStream outPutStream = new FileOutputStream("c:/keystore.p12"); // ca.jks
        outPutStream.write(result.get("keyStoreData"));
        outPutStream.close();
        FileOutputStream fos = new FileOutputStream(new File("c:/keystore.cer"));
        fos.write(result.get("certificateData"));
        fos.flush();
        fos.close();
    }
    }

六、 如何生成一个高清晰的签章

1. 由PDF模板生成一个PDF文件,见代码注解

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import com.itextpdf.text.DocumentException;
    import com.itextpdf.text.pdf.AcroFields;
    import com.itextpdf.text.pdf.AcroFields.Item;
    import com.itextpdf.text.pdf.BaseFont;
    import com.itextpdf.text.pdf.PdfReader;
    import com.itextpdf.text.pdf.PdfStamper;

    public class PDFUtils {

    /**
     * @param fields
     * @param data
     * @throws IOException
     * @throws DocumentException
     */
    private static void fillData(AcroFields fields, Map<String, String> data) throws IOException, DocumentException {
        List<String> keys = new ArrayList<String>();
        Map<String, Item> formFields = fields.getFields();
        for (String key : data.keySet()) {
            if(formFields.containsKey(key)){
                String value = data.get(key);
                fields.setField(key, value); // 为字段赋值,注意字段名称是区分大小写的
                keys.add(key);
            }
        }
        Iterator<String> itemsKey = formFields.keySet().iterator();
        while(itemsKey.hasNext()){
            String itemKey = itemsKey.next();
            if(!keys.contains(itemKey)){
                fields.setField(itemKey, " ");
            }
        }
    }

    /**
     * @param templatePdfPath
     *            模板pdf路径
     * @param generatePdfPath
     *            生成pdf路径
     * @param data
     *            数据
     */
    public static String generatePDF(String templatePdfPath, String generatePdfPath, Map<String, String> data) {
        OutputStream fos = null;
        ByteArrayOutputStream bos = null;
        try {
            PdfReader reader = new PdfReader(templatePdfPath);
            bos = new ByteArrayOutputStream();
            /* 将要生成的目标PDF文件名称 */
            PdfStamper ps = new PdfStamper(reader, bos);
            /* 使用中文字体 */
            BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);
            ArrayList<BaseFont> fontList = new ArrayList<BaseFont>();
            fontList.add(bf);
            /* 取出报表模板中的所有字段 */
            AcroFields fields = ps.getAcroFields();
            fields.setSubstitutionFonts(fontList);
            fillData(fields, data);
            /* 必须要调用这个,否则文档不会生成的  如果为false那么生成的PDF文件还能编辑,一定要设为true*/
            ps.setFormFlattening(true);
            ps.close();
            fos = new FileOutputStream(generatePdfPath);
            fos.write(bos.toByteArray());
            fos.flush();
            return generatePdfPath;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static void main(String[] args) {
        Map<String, String> data = new HashMap<String, String>();
        //key为pdf模板的form表单的名字,value为需要填充的值
        data.put("title", "在线医院");
        data.put("case", "123456789");
        data.put("date", "2018.12.07");
        data.put("name", "gitbook");
        data.put("sex", "男");
        data.put("age", "29");
        data.put("phone", "13711645814");
        data.put("office", "内科");
        data.put("cert", "身痒找打");
        data.put("drug", "1、奥美拉唑肠溶胶囊             0.25g10粒×2板 ");
        data.put("dose", "×2盒");
        data.put("cons", "用法用量:口服 一日两次 一次2粒");
        data.put("tips", "温馨提示");
        data.put("desc", "尽量呆在通风较好的地方,保持空气流通,有利于病情康复。尽量呆在通风较好的地方");
        generatePDF("C:\\Users\\zhilin\\Desktop\\chat\\tpl.pdf",
                "C:\\Users\\zhilin\\Desktop\\chat\\filled.pdf", data );
    }
    }

2. 对PDF文件进行签章

经过过上面的代码可以生成一个名为sign.jpg的签章图片,生成一个keystore.p12的证书文件,还有一个已经通过模板填充了表单的名为filled.pdf的pdf文件。下面就可通过以上材料生成一个签名的PDF文件。

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.Security;
    import java.security.cert.Certificate;
    import java.util.UUID;

    import org.bouncycastle.jce.provider.BouncyCastleProvider;

    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.PdfSignatureAppearance.RenderingMode;
    import com.itextpdf.text.pdf.PdfStamper;
    import com.itextpdf.text.pdf.security.BouncyCastleDigest;
    import com.itextpdf.text.pdf.security.DigestAlgorithms;
    import com.itextpdf.text.pdf.security.ExternalDigest;
    import com.itextpdf.text.pdf.security.ExternalSignature;
    import com.itextpdf.text.pdf.security.MakeSignature;
    import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
    import com.itextpdf.text.pdf.security.PrivateKeySignature;

    public class SignPdf {
    /**
     * @param password
     *            秘钥密码
     * @param keyStorePath
     *            秘钥文件路径
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signImage
     *            签名图片文件
     * @param x
     *            x坐标
     * @param y
     *            y坐标
     * @return
     */
    public static byte[] sign(String password, String keyStorePath, String signPdfSrc, String signImage,
            float x, float y) {
        File signPdfSrcFile = new File(signPdfSrc);
        PdfReader reader = null;
        ByteArrayOutputStream signPDFData = null;
        PdfStamper stp = null;
        FileInputStream fos = null;
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();
            Security.addProvider(provider);
            KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
            fos = new FileInputStream(keyStorePath);
            // 私钥密码 为Pkcs生成证书是的私钥密码 123456
            ks.load(fos, password.toCharArray());
            String alias = (String) ks.aliases().nextElement();
            PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
            Certificate[] chain = ks.getCertificateChain(alias);
            reader = new PdfReader(signPdfSrc);
            signPDFData = new ByteArrayOutputStream();
            // 临时pdf文件
            File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);
             stp.setFullCompression();
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("数字签名,不可改变");
            // 使用png格式透明图片
            Image image = Image.getInstance(signImage);
            sap.setImageScale(0);
            sap.setSignatureGraphic(image);
            sap.setRenderingMode(RenderingMode.GRAPHIC);
            // 是对应x轴和y轴坐标
            sap.setVisibleSignature(new Rectangle(x, y, x + 185, y + 68), 1,
                    UUID.randomUUID().toString().replaceAll("-", ""));
            stp.getWriter().setCompressionLevel(5);
            ExternalDigest digest = new BouncyCastleDigest();
            ExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA512, provider.getName());
            MakeSignature.signDetached(sap, digest, signature, chain, null, null, null, 0, CryptoStandard.CADES);
            stp.close();
            reader.close();
            return signPDFData.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if (signPDFData != null) {
                try {
                    signPDFData.close();
                } catch (IOException e) {
                }
            }

            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        byte[] fileData = sign("123456", "C:\\Users\\zhilin\\Desktop\\chat\\keystore.p12", //
                "C:\\Users\\zhilin\\Desktop\\chat\\filled.pdf",//
                "C:\\Users\\zhilin\\Desktop\\chat\\sign.jpg", 100, 290);
        FileOutputStream f = new FileOutputStream(new File("C:\\Users\\zhilin\\Desktop\\chat\\signed.pdf"));
        f.write(fileData);
        f.close();
    }
    }

3. 高清签章

高清签章是通过iText的绘制功能来完成。主要直接在PDF文件中绘制签章,代码实现如下:

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.Security;
    import java.security.cert.Certificate;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;

    import com.itextpdf.awt.AsianFontMapper;
    import com.itextpdf.text.BaseColor;
    import com.itextpdf.text.Element;
    import com.itextpdf.text.Font;
    import com.itextpdf.text.Paragraph;
    import com.itextpdf.text.Rectangle;
    import com.itextpdf.text.pdf.BaseFont;
    import com.itextpdf.text.pdf.ColumnText;
    import com.itextpdf.text.pdf.PdfReader;
    import com.itextpdf.text.pdf.PdfSignatureAppearance;
    import com.itextpdf.text.pdf.PdfStamper;
    import com.itextpdf.text.pdf.PdfStream;
    import com.itextpdf.text.pdf.PdfTemplate;
    import com.itextpdf.text.pdf.security.BouncyCastleDigest;
    import com.itextpdf.text.pdf.security.DigestAlgorithms;
    import com.itextpdf.text.pdf.security.ExternalDigest;
    import com.itextpdf.text.pdf.security.ExternalSignature;
    import com.itextpdf.text.pdf.security.MakeSignature;
    import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
    import com.itextpdf.text.pdf.security.PrivateKeySignature;

    public class SignHighPdf {

    /**
     * @param password
     *            秘钥密码
     * @param keyStorePath
     *            秘钥文件路径
     * @param signPdfSrc
     *            签名的PDF文件
     * @param x
     *
     * @param y
     * @return
     */
    public static byte[] sign(String password, String keyStorePath, String signPdfSrc,
            float x, float y,
            String signText) {
        File signPdfSrcFile = new File(signPdfSrc);
        PdfReader reader = null;
        ByteArrayOutputStream signPDFData = null;
        PdfStamper stp = null;
        FileInputStream fos = null;
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();
            Security.addProvider(provider);
            KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
            fos = new FileInputStream(keyStorePath);
            ks.load(fos, password.toCharArray()); // 私钥密码
            String alias = (String) ks.aliases().nextElement();
            PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
            Certificate[] chain = ks.getCertificateChain(alias);
            reader = new PdfReader(signPdfSrc);
            signPDFData = new ByteArrayOutputStream();
            // 临时pdf文件
            File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("数字签名,不可改变");
            // 是对应x轴和y轴坐标
            sap.setVisibleSignature(new Rectangle(x, y, x + 150, y + 65), 1,
                    "sr"+String.valueOf(System.nanoTime()));
            /layer 0 Creating the appearance for layer 0
            PdfTemplate n0 = sap.getLayer(0);
            n0.reset();
            float lx = n0.getBoundingBox().getLeft();
            float by = n0.getBoundingBox().getBottom();
            float width = n0.getBoundingBox().getWidth();
            float height = n0.getBoundingBox().getHeight();
            n0.setRGBColorFill(255, 0, 0);
            n0.rectangle(lx, by, 5, height);
            n0.rectangle(lx, by, width, 5);
            n0.rectangle(lx, by+height-5, width, 5);
            n0.rectangle(lx+width-5, by, 5, height);
            n0.fill();
            ///layer 2
            PdfTemplate n2 = sap.getLayer(2);
            n2.setCharacterSpacing(0.0f);
            ColumnText ct = new ColumnText(n2);
            ct.setSimpleColumn(n2.getBoundingBox());
            n2.setRGBColorFill(255, 0, 0);
            //做一个占位的动作
            Paragraph p1 = new Paragraph(" ");
            BaseFont bf = BaseFont.createFont(AsianFontMapper.ChineseSimplifiedFont, AsianFontMapper.ChineseSimplifiedEncoding_H,
                    BaseFont.NOT_EMBEDDED);
            Font font1 = new Font(bf, 5, Font.BOLD, BaseColor.RED);
            Font font2 = new Font(bf, 13, Font.BOLD, BaseColor.RED);
            p1.setFont(font1);
            ct.addElement(p1);
            Paragraph p = new Paragraph(signText);
            p.setAlignment(Element.ALIGN_CENTER);
            p.setFont(font2);
            ct.addElement(p);
            ct.go();
            stp.getWriter().setCompressionLevel(PdfStream.BEST_COMPRESSION);
            ExternalDigest digest = new BouncyCastleDigest();
            ExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA512, provider.getName());
            MakeSignature.signDetached(sap, digest, signature, chain, null, null, null, 0, CryptoStandard.CADES);
            stp.close();
            reader.close();
            return signPDFData.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (signPDFData != null) {
                try {
                    signPDFData.close();
                } catch (IOException e) {
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        //对已经签章的signed.pdf文件再次签章,这次是高清签章
        byte[] fileData = sign("123456", "C:\\Users\\zhilin\\Desktop\\chat\\keystore.p12",//
                "C:\\Users\\zhilin\\Desktop\\chat\\signed.pdf", 350, 290, "华佗\n2017-12-20");
        FileOutputStream f = new FileOutputStream(new File("C:\\Users\\zhilin\\Desktop\\chat\\signed2.pdf"));
        f.write(fileData);
        f.close();
    }

    }

可以分析下下面这两个签章的区别,发现左边的签章很模糊,右边的特别清晰。

七、 如何进行多次PDF签名

生成多个签章重点代码,已在SignPdf.java类进行标注说明;如果想进行多次签名,就只需对已经进行过签名的PDF文件再次调用sign方法进行再次签名即可(第六点有张图片就有两个签章,这就是多次签名的结果)。

PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);

八、 总结

分享中sign.jpg文件的白色背景需要做透明化处理才能达到正确电子签章的效果(不覆盖PDF文件中已有的内容,真实的电子签章也是这样做的),大家回去可以思考下怎么把一个jpg文件白色背景透明化(高清签章就已经实现透明化,可以试着把SignPdf.java和SignHighPdf.java签章到有文字的PDF上面看看效果)。

大家见到的公司公章都是圆形的;这个也是可以做到的大家想想怎样生成一个圆形的图片签章;然后进行电子签名。这里主要是讲解代码实现,所有代码非常多。大家回去好好研读代码。真正的电子签名需要通过CA认证公司来完成,我这里只是提供参考方案让大家学习。

到此这篇关于利用Java对PDF文件进行电子签章的文章就介绍到这了,更多相关Java对PDF文件电子签章内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java实现在pdf模板的指定位置插入图片

    本文实例为大家分享了java在pdf模板的指定位置插入图片的具体代码,供大家参考,具体内容如下 java操作pdf有个非常好用的库itextpdf,maven: <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.6</version> </dependency> <!--

  • java原装代码完成pdf在线预览和pdf打印及下载

    前提准备: 1. 项目中至少需要引入的jar包,注意版本: a) core-renderer.jar b) freemarker-2.3.16.jar c) iText-2.0.8.jar d) iTextAsian.jar 上代码: 注释: 此类为自定义的Tag类的基类,在action中怎么放的数据,在ftl中就怎么取数据,简洁明了.  1. 自定义Tag类的基类 /** * 通用的生成pdf预览和生成打印的html文件 * * @author xg君 * */ public abstract

  • Java生成PDF文件的实例代码

    复制代码 代码如下: package com.qhdstar.java.pdf; import java.awt.Color;import java.io.FileOutputStream; import com.lowagie.text.Chapter;import com.lowagie.text.Document;import com.lowagie.text.Font;import com.lowagie.text.FontFactory;import com.lowagie.text.

  • 详解Java生成PDF文档方法

    最近项目需要实现PDF下载的功能,由于没有这方面的经验,从网上花了很长时间才找到相关的资料.整理之后,发现有如下几个框架可以实现这个功能. 1. 开源框架支持 iText,生成PDF文档,还支持将XML.Html文件转化为PDF文件: Apache PDFBox,生成.合并PDF文档: docx4j,生成docx.pptx.xlsx文档,支持转换为PDF格式. 比较: iText开源协议为AGPL,而其他两个框架协议均为Apache License v2.0. 使用PDFBox生成PDF就像画图

  • Java 生成PDF文档的示例代码

    最近项目需要实现PDF下载的功能,由于没有这方面的经验,从网上花了很长时间查找了相关的资料.整理之后,发现有几个框架可以实现这个功能. 1. 开源框架支持 iText,生成PDF文档,还支持将XML.Html文件转化为PDF文件: Apache PDFBox,生成.合并PDF文档: docx4j,生成docx文档,支持转换为PDF格式. 2. 实现方案 比较了一番后,采用了FreeMarker+docx4j+Apache PDFBox的方案: maven依赖 <!-- pdfbox --> &

  • 利用Java对PDF文件进行电子签章的实战过程

    目录 一. 概述 二. 技术选型 三. 生成一个图片签章 1. 生成一个如下图的签章图片 2. 相关代码 四. 如何按模板生成PDF文件 1. 制作PDF模板 2. 制作一个如下图的PDF模板,该模板是带有PDF的表单域的 五. 如何生成PKCS12证书 1. PKCS的简单介绍 2. 使用JAVA生成一个PKCS12证书并进行存贮,相关分析见代码注解 六. 如何生成一个高清晰的签章 1. 由PDF模板生成一个PDF文件,见代码注解 2. 对PDF文件进行签章 3. 高清签章 七. 如何进行多次

  • 用Java验证pdf文件的电子章签名

    pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4

  • JAVA生成pdf文件的实操指南

    目录 一.简介 二.实操 三.原理解析 1.是什么? 1.1.关键技术 2.怎么做?为什么? 3.参考 总结 一.简介 PDF文件格式可以将文字.字型.格式.颜色及独立于设备和分辨率的图形图像等封装在一个文件中.本文实现将html页面转PDF. 二.实操 生成pdf文件成功,但是文字对不上.当修改”GetHtmlContent“部分的编码之后,再次执行生成PDF文件即可完成正确的实现. Edit Configurations 三.原理解析 从这几点深入剖析和总结这个小项目: 1.是什么? 该项目

  • java实现pdf文件截图的方法【附PDFRenderer.jar下载】

    本文实例讲述了java实现pdf文件截图的方法.分享给大家供大家参考,具体如下: 最近做的一个网站中,有个需求是上传pdf文件,显示pdf的封页,点击封页之后进行在线阅读,这里使用的是PDFRender对pdf进行截图. public static boolean createScreenShoot(String source, String target) { File file = new File(source); if (!file.exists()) { System.err.prin

  • Yii2框架实现利用mpdf创建pdf文件功能示例

    本文实例讲述了Yii2框架实现利用mpdf创建pdf文件功能.分享给大家供大家参考,具体如下: 安装mPDF 使用 Composer安装 在yii2的 'composer.json' 加入"mpdf/mpdf":"*" "require": { "php": ">=5.4.0", "yiisoft/yii2": "*", "yiisoft/yii2-b

  • C# 利用PdfSharp生成Pdf文件的示例

    目录 PdfSharp下载 涉及知识点 文档示例图 核心代码 PdfSharp一款开源的用于创建,操作PDF文档的.Net类库,本文以一个简单的小例子,简述如何通过PdfSharp进行创建PDF文档,仅供学习分享使用,如有不足之处,还请指正. PdfSharp下载 在本例中,主要通过NuGet包管理器进行下载安装,目前PdfSharp版本为v1.5.0.5147,如下所示: 涉及知识点 在生成PDF文档过程中,主要知识点如下: PdfDocument : 表示一个PDF文档对象,调用save方法

  • Java实现PDF文件的分割与加密功能

    由于``某些不可抗力原因,公司不允许使用itext系列的jar包,因此系统中使用的相关jar得替换成开源的.经比较和尝试考虑使用org.apache.pdfbox来替换,同时修改系统中原有的方法,发现比itext系列稍显简洁一点,记录如下: 加密文件 /** * 加密文件测试 * @from fhadmin.cn */ @Test public void encryptTest(){ try { String filePath = "D:\\test\\像李开复一样思考人生.pdf";

  • Python利用PyMuPDF实现PDF文件处理

    目录 1.PyMuPDF简介 介绍 功能 2.安装 关于命名fitz的说明 3.使用方法 导入库,查看版本 打开文档 Document的方法和属性 获取元数据 获取目标大纲 页面(Page) PDF操作 1.PyMuPDF简介 介绍 在介绍PyMuPDF之前,先来了解一下MuPDF,从命名形式中就可以看出,PyMuPDF是MuPDF的Python接口形式. MuPDF MuPDF 是一个轻量级的 PDF.XPS和电子书查看器.MuPDF 由软件库.命令行工具和各种平台的查看器组成. MuPDF 

  • 利用Python+Selenium破解春秋航空网滑块验证码的实战过程

    目录 前言 开发工具 环境搭建 实战记录 一. 验证码简介 二.破解滑块验证码 2.1 计算滑块到缺口的距离 2.2 将滑块拖到缺口位置 前言 记录一次利用Python+Selenium破解滑块验证码的实战过程. 让我们愉快地开始吧~ 开发工具 Python版本: 3.6.4 相关模块: pillow模块: selenium模块: numpy模块: 以及一些Python自带的模块. 其他: chromedriver 环境搭建 安装Python并添加到环境变量,pip安装需要的相关模块即可. 实战

  • 利用java操作Excel文件的方法

    很久以来都想研究一下利用java操作Excel的方法,今天没事,就稍微了解了一下,特总结一下.利用java操作Excel,有个开源的东东-jxl.jar,可以到http://sourceforge.net/projects/jexcelapi/files/下载. 一.读取Excel文件内容 复制代码 代码如下: /** *//**读取Excel文件的内容     * @param file  待读取的文件     * @return     */    public static String

随机推荐