如何用 Java 对 PDF 文件进行电子签章

摘要:
PDFBox可免费使用。AGPL共享JAVA的商业收费簿,以实现PDF文件的电子签名功能:生成证书。

一、概述

二、技术选型

三、生成一个图片签章

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

五、如何生成PKSC12证书

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

七、如何进行多次PDF签名

八、总结

一、 概述

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

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

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

二、 技术选型

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

  1. 开源组织Apache的PDFBox,官网https://pdfbox.apache.org/
  2. 大名鼎鼎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的理由:

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

三、 生成一个图片签章

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

如何用 Java 对 PDF 文件进行电子签章第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的表单域的

如何用 Java 对 PDF 文件进行电子签章第2张

五、 如何生成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 );
    }
    }

如何用 Java 对 PDF 文件进行电子签章第3张

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, '

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇matlab函数——shading函数bat批处理文件夹内文件名的提取【转载-改编】下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

JSON 之GSON 解析

一、 谷歌GSON这个Java类库可以把Java对象转换成JSON,也可以把JSON字符串转换成一个相等的Java对象。Gson支持任意复杂Java对象包括没有源代码的对象。 二、Gson解析Json步骤  A、服务器端将数据转换成json字符串    首先、服务器端项目要导入Gson的jar包到BuiltPath中。( Gson的jar:http://...

高效Redis工具类

一、引言 本篇博客以redis缓存为主。至于什么是redis缓存?还有没有其它的缓存?哪个缓存的性能会更好?这里就不一一做介绍了!(有兴趣的可以自己去百度一下) 在日常的开发中,我们或多或少(必须)的会用到缓存。为了提高系统性能、提升用户体验度,用户体验是多么的重要;这就要求在软件设计时,不但要注重可靠性、安全性、可扩展性以及可维护性等等的一些指标,更要注...

Java中将对象转换为Map的方法

将对象转换为Map的方法,代码如下: /** * 将对象转成TreeMap,属性名为key,属性值为value * @paramobject 对象 * @return * @throwsIllegalAccessException */ private static TreeM...

spring + redis 实现数据的缓存

1、实现目标   通过redis缓存数据。(目的不是加快查询的速度,而是减少数据库的负担)   2、所需jar包      注意:jdies和commons-pool两个jar的版本是有对应关系的,注意引入jar包是要配对使用,否则将会报错。因为commons-pooljar的目录根据版本的变化,目录结构会变。前面的版本是org.apache.pool,而...

PHP类和对象函数实例详解

1. interface_exists、class_exists、method_exists和property_exists:       顾名思义,从以上几个函数的命名便可以猜出几分他们的功能。我想这也是我随着对PHP的深入学习而越来越喜欢这门编程语言的原因了吧。下面先给出他们的原型声明和简短说明,更多的还是直接看例子代码吧。bool interface...

Quartz+Spring 实现定时任务的 管理和监控

 0,监控的意义 (1)能够查看有多少定时任务,用的什么执行策略,便于管理 (2)能够通过界面操作停止或启动某个定时任务,便于管理 (3)能够通过界面操作改变某个定时任务的执行策略,便于管理 1,pom.xml 文件添加jar 1 <!-- quartz监控 --> 2 <dependency> 3 <gro...