欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用Java對PDF文件進(jìn)行電子簽章的實(shí)戰(zhàn)過程

 更新時(shí)間:2021年07月12日 15:30:23   作者:世界沉浮腳下  
隨著電子賬單、回單、通知、合同的流行,電子文檔的可信度變得非常重要,為防止非法篡改,確保文檔的權(quán)威性,我們可以對PDF進(jìn)行電子簽章,這篇文章主要給大家介紹了關(guān)于如何利用Java對PDF文件進(jìn)行電子簽章的相關(guān)資料,需要的朋友可以參考下

一、 概述

印章是我國特有的歷史文化產(chǎn)物,古代主要用作身份憑證和行駛職權(quán)的工具。它的起源是由于社會(huì)生活的實(shí)際需要。早在商周時(shí)代,印章就已經(jīng)產(chǎn)生。如今的印章已成為一種獨(dú)特的,融實(shí)用性和藝術(shù)性為一體的藝術(shù)瑰寶。傳統(tǒng)的印章容易被壞人、小人私刻;從而新聞鮮有報(bào)道某某私刻公章,侵吞國家財(cái)產(chǎn)。隨著計(jì)算機(jī)技術(shù)、加密技術(shù)及圖像處理技術(shù)的發(fā)展,出現(xiàn)了電子簽章。電子簽章是電子簽名的一種表現(xiàn)形式,利用圖像處理技術(shù)、數(shù)字加密技術(shù)將電子簽名操作轉(zhuǎn)化為與紙質(zhì)文件蓋章操作相同的可視效果,同時(shí)利用電子簽名技術(shù)保障電子信息的真實(shí)性和完整性以及簽名人的不可否認(rèn)性。

電子簽章與數(shù)字證書一樣是身份驗(yàn)證的一種手段,泛指所有以電子形式存在,依附在電子文件并與其邏輯關(guān)聯(lián),可用以辨識(shí)電子文件簽署者身份,保證文件的完整性,并表示簽署者同意電子文件所陳述事實(shí)的內(nèi)容。一般來說對電子簽章的認(rèn)定都是從技術(shù)角度而言的。主要是指通過特定的技術(shù)方案來鑒別當(dāng)事人的身份及確保電子資料內(nèi)容不被篡改的安全保障措施。電子簽章常于發(fā)送安全電子郵件、訪問安全站點(diǎn)、網(wǎng)上招標(biāo)投標(biāo)、網(wǎng)上簽約、安全網(wǎng)上公文傳送、公司合同、電子處方箋等。

電子簽章是一個(gè)很復(fù)雜的問題,大到有相關(guān)的電子簽章系統(tǒng);今天分享一下如何把電子簽章應(yīng)用到電子處方箋的PDF文件里。

二、 技術(shù)選型

目前主流處理PDF文件兩個(gè)jar包分別是:

  • 開源組織Apache的PDFBox,官網(wǎng)https://pdfbox.apache.org/
  • 大名鼎鼎adobe公司的iText,官網(wǎng)https://itextpdf.com/tags/adobe,其中iText又分為iText5和iText7

如何在PDFBox、iText5和iText7選出合適自己項(xiàng)目的技術(shù)呢?

對比PDFBox、iText5和iText7這三者:

  1. PDFBox的功能相對較弱,iText5和iText7的功能非常強(qiáng)悍;
  2. iText5的資料網(wǎng)上相對較多,如果出現(xiàn)問題容易找到解決方案;PDFBox和iText7的網(wǎng)上資料相對較少,如果出現(xiàn)問題不易找到相關(guān)解決方案;
  3. 通過閱讀PDFBox代碼目前PDFBox還沒提供自定義簽章的相關(guān)接口;iText5和iText7提供了處理自定義簽章的相關(guān)實(shí)現(xiàn);
  4. PDFBox只能實(shí)現(xiàn)把簽章圖片加簽到PDF文件;iText5和iText7除了可以把簽章圖片加簽到PDF文件,還可以實(shí)現(xiàn)直接對簽章進(jìn)行繪制,把文件繪制到簽章上。
  5. PDFBox和iText5/iText7使用的協(xié)議不一樣。PDFBox使用的是APACHE LICENSE VERSION 2.0(https://www.apache.org/licenses/);iText5/iText7使用的是AGPL(https://itextpdf.com/agpl)。PDFBox免費(fèi)使用,AGPL商用收費(fèi)

本分享JAVA對PDF文件進(jìn)行電子簽章需要實(shí)現(xiàn)的功能:

  1. 生成證書。與PDFBox、iText5和iText7技術(shù)無關(guān)
  2. 按模板輸出PDF文件:PDFBox、iText5和iText7都可以完成,但是PDFBox會(huì)遇到中文亂碼比較棘手的問題
  3. 在PDF文件中實(shí)現(xiàn)把簽章圖片加簽到PDF文件:PDFBox、iText5和iText7都可以實(shí)現(xiàn),沒有很多的區(qū)別
  4. 在PDF文件中繪制簽章:iText5和iText7都可以實(shí)現(xiàn),PDFBox目前不支持
  5. 在PDF文件中生成高清簽章:iText5和iText7都可以實(shí)現(xiàn),PDFBox目前不支持
  6. 在PDF文件中進(jìn)行多次簽名::PDFBox、iText5和iText7都可以完成,沒有區(qū)別

通過相關(guān)技術(shù)分析和要實(shí)現(xiàn)的功能分析,采用iText5進(jìn)行開發(fā),唯一遺憾的是iText商用收費(fèi);但是這不是做技術(shù)需要關(guān)心的??!選用iText5的理由:

  • 使用iText5能實(shí)現(xiàn)全部的功能
  • 如何在開發(fā)中遇到相關(guān)問題,容易找到相應(yīng)解決方案

三、 生成一個(gè)圖片簽章

1. 生成一個(gè)如下圖的簽章圖片

2. 相關(guān)代碼

    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 醫(yī)生名字
     * @param hospitalName
     *            String 醫(yī)生名稱
     * @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è)矩形
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON); // 去除鋸齒(當(dāng)設(shè)置的字體過大的時(shí)候,會(huì)出現(xiàn)鋸齒)
 
            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); // 字體字形字號(hào)
            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); // 在指定坐標(biāo)除添加文字
 
            g.setFont(othorTextFont); // 字體字形字號(hào)
 
            fm = FontDesignMetrics.getMetrics(othorTextFont);
            int font2_Hight = fm.getHeight();
            strWidth = fm.stringWidth(hospitalName);
            x = (width - strWidth) / 2;
            g.drawString(hospitalName, x, y + font1_Hight); // 在指定坐標(biāo)除添加文字
 
            strWidth = fm.stringWidth(date);
            x = (width - strWidth) / 2;
            g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐標(biāo)除添加文字
 
            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("華佗", "在線醫(yī)院", "2018.01.01",   "sign.jpg");
    }
}

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

1. 制作PDF模板

目前PDF模板工具別無他物,只能使用偉大的Adobe公司提供的Adobe Acrobatpro DC軟件進(jìn)行制作。如何使用該軟件這里就不多說了,如果在使用中遇到什么可以另外咨詢。

2. 制作一個(gè)如下圖的PDF模板,該模板是帶有PDF的表單域的

五、 如何生成PKCS12證書

1. PKCS的簡單介紹

PKCS:The Public-Key Cryptography Standards (簡稱PKCS)是由美國RSA數(shù)據(jù)安全公司及其合作伙伴制定的一組公鑰密碼學(xué)標(biāo)準(zhǔn),其中包括證書申請、證書更新、證書作廢表發(fā)布、擴(kuò)展證書內(nèi)容以及數(shù)字簽名、數(shù)字信封的格式等方面的一系列相關(guān)協(xié)議。

到1999年底,PKCS已經(jīng)公布了以下標(biāo)準(zhǔn):

  • PKCS#1:定義RSA公開密鑰算法加密和簽名機(jī)制,主要用于組織PKCS#7中所描述的數(shù)字簽名和數(shù)字信封[22]。
  • PKCS#3:定義Diffie-Hellman密鑰交換協(xié)議[23]。
  • PKCS#5:描述一種利用從口令派生出來的安全密鑰加密字符串的方法。使用MD2或MD5 從口令中派生密鑰,并采用DES-CBC模式加密。主要用于加密從一個(gè)計(jì)算機(jī)傳送到另一個(gè)計(jì)算機(jī)的私人密鑰,不能用于加密消息[24]。
  • PKCS#6:描述了公鑰證書的標(biāo)準(zhǔn)語法,主要描述X.509證書的擴(kuò)展格式[25]。
  • PKCS#7:定義一種通用的消息語法,包括數(shù)字簽名和加密等用于增強(qiáng)的加密機(jī)制,PKCS#7與PEM兼容,所以不需其他密碼操作,就可以將加密的消息轉(zhuǎn)換成PEM消息[26]。
  • PKCS#8:描述私有密鑰信息格式,該信息包括公開密鑰算法的私有密鑰以及可選的屬性集等[27]。
  • PKCS#9:定義一些用于PKCS#6證書擴(kuò)展、PKCS#7數(shù)字簽名和PKCS#8私鑰加密信息的屬性類型[28]。
  • PKCS#10:描述證書請求語法[29]。
  • PKCS#11:稱為Cyptoki,定義了一套獨(dú)立于技術(shù)的程序設(shè)計(jì)接口,用于智能卡和PCMCIA卡之類的加密設(shè)備[30]。
  • PKCS#12:描述個(gè)人信息交換語法標(biāo)準(zhǔn)。描述了將用戶公鑰、私鑰、證書和其他相關(guān)信息打包的語法[31]。
  • PKCS#13:橢圓曲線密碼體制標(biāo)準(zhǔn)[32]。
  • PKCS#14:偽隨機(jī)數(shù)生成標(biāo)準(zhǔn)。
  • PKCS#15:密碼令牌信息格式標(biāo)準(zhǔn)[33]。

PKCS12也就是以上標(biāo)準(zhǔn)的PKCS#12,主要用來描述個(gè)人身份信息;本次分享中要進(jìn)行簽章操作的是醫(yī)生和藥師,他們就是一個(gè)個(gè)人主體,給他們分配一個(gè)PKCS12的證書,就等于給他們分配了一個(gè)用于蓋章的印章。

2. 使用JAVA生成一個(gè)PKCS12證書并進(jìn)行存貯,相關(guān)分析見代碼注解

    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 頒發(fā)機(jī)構(gòu)信息
     * 
     * @param subjectStr 使用者信息
     * 
    * @param certificateCRL 頒發(fā)地址
     * 
     * @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");
            // 標(biāo)志生成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隨便寫,標(biāo)識(shí)別名
            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();
            // 證書序列號(hào)
            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);
            // 分發(fā)點(diǎn)
            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:使用自己的私鑰進(jìn)行簽名,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");
            // 證書數(shù)據(jù)
            result.put("certificateData", certBuf);
            //公鑰
            result.put("publicKey", publicKey.getEncoded());
            //私鑰
            result.put("privateKey", privateKey.getEncoded());
            //證書有效開始時(shí)間
            result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
            //證書有效結(jié)束時(shí)間
            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 : 城市或區(qū)域名稱  E : 電子郵件
        // ST: 州或省份名稱  C: 單位的兩字母國家代碼 
        String issuerStr = "CN=在線醫(yī)院,OU=gitbook研發(fā)部,O=gitbook有限公司,C=CN,E=gitbook@sina.com,L=北京,ST=北京";
        String subjectStr = "CN=huangjinjin,OU=gitbook研發(fā)部,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();
    }
    }

六、 如何生成一個(gè)高清晰的簽章

1. 由PDF模板生成一個(gè)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); // 為字段賦值,注意字段名稱是區(qū)分大小寫的
                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
     *            數(shù)據(jù)
     */
    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();
            /* 將要生成的目標(biāo)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);
            /* 取出報(bào)表模板中的所有字段 */
            AcroFields fields = ps.getAcroFields();
            fields.setSubstitutionFonts(fontList);
            fillData(fields, data);
            /* 必須要調(diào)用這個(gè),否則文檔不會(huì)生成的  如果為false那么生成的PDF文件還能編輯,一定要設(shè)為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", "在線醫(yī)院");
        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", "內(nèi)科");
        data.put("cert", "身癢找打");
        data.put("drug", "1、奧美拉唑腸溶膠囊             0.25g10?!?板 ");
        data.put("dose", "×2盒");
        data.put("cons", "用法用量:口服 一日兩次 一次2粒");
        data.put("tips", "溫馨提示");
        data.put("desc", "盡量呆在通風(fēng)較好的地方,保持空氣流通,有利于病情康復(fù)。盡量呆在通風(fēng)較好的地方");
        generatePDF("C:\\Users\\zhilin\\Desktop\\chat\\tpl.pdf", 
                "C:\\Users\\zhilin\\Desktop\\chat\\filled.pdf", data );
    }
    }

2. 對PDF文件進(jìn)行簽章

經(jīng)過過上面的代碼可以生成一個(gè)名為sign.jpg的簽章圖片,生成一個(gè)keystore.p12的證書文件,還有一個(gè)已經(jīng)通過模板填充了表單的名為filled.pdf的pdf文件。下面就可通過以上材料生成一個(gè)簽名的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坐標(biāo)
     * @param y
     *            y坐標(biāo)
     * @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();
            // 臨時(shí)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("數(shù)字簽名,不可改變");
            // 使用png格式透明圖片
            Image image = Image.getInstance(signImage);
            sap.setImageScale(0);
            sap.setSignatureGraphic(image);
            sap.setRenderingMode(RenderingMode.GRAPHIC);
            // 是對應(yīng)x軸和y軸坐標(biāo)
            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文件中繪制簽章,代碼實(shí)現(xiàn)如下:

    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();
            // 臨時(shí)pdf文件
            File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("數(shù)字簽名,不可改變");
            // 是對應(yīng)x軸和y軸坐標(biāo)
            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);
            //做一個(gè)占位的動(dòng)作
            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 {
        //對已經(jīng)簽章的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();
    }
 
    }

可以分析下下面這兩個(gè)簽章的區(qū)別,發(fā)現(xiàn)左邊的簽章很模糊,右邊的特別清晰。

七、 如何進(jìn)行多次PDF簽名

生成多個(gè)簽章重點(diǎn)代碼,已在SignPdf.java類進(jìn)行標(biāo)注說明;如果想進(jìn)行多次簽名,就只需對已經(jīng)進(jìn)行過簽名的PDF文件再次調(diào)用sign方法進(jìn)行再次簽名即可(第六點(diǎn)有張圖片就有兩個(gè)簽章,這就是多次簽名的結(jié)果)。

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

八、 總結(jié)

分享中sign.jpg文件的白色背景需要做透明化處理才能達(dá)到正確電子簽章的效果(不覆蓋PDF文件中已有的內(nèi)容,真實(shí)的電子簽章也是這樣做的),大家回去可以思考下怎么把一個(gè)jpg文件白色背景透明化(高清簽章就已經(jīng)實(shí)現(xiàn)透明化,可以試著把SignPdf.java和SignHighPdf.java簽章到有文字的PDF上面看看效果)。

大家見到的公司公章都是圓形的;這個(gè)也是可以做到的大家想想怎樣生成一個(gè)圓形的圖片簽章;然后進(jìn)行電子簽名。這里主要是講解代碼實(shí)現(xiàn),所有代碼非常多。大家回去好好研讀代碼。真正的電子簽名需要通過CA認(rèn)證公司來完成,我這里只是提供參考方案讓大家學(xué)習(xí)。

到此這篇關(guān)于利用Java對PDF文件進(jìn)行電子簽章的文章就介紹到這了,更多相關(guān)Java對PDF文件電子簽章內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java selenium使用瀏覽器調(diào)試工具實(shí)現(xiàn)方法

    java selenium使用瀏覽器調(diào)試工具實(shí)現(xiàn)方法

    本文主要介紹java selenium使用瀏覽器調(diào)試工具,這里整理了幾種瀏覽器的調(diào)試方法,有需要的小伙伴可以參考下
    2016-08-08
  • java中Properties文件加載和使用方法

    java中Properties文件加載和使用方法

    這篇文章主要為大家詳細(xì)介紹了java中Properties文件加載和使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Spring AOP與代理類的執(zhí)行順序級(jí)別淺析

    Spring AOP與代理類的執(zhí)行順序級(jí)別淺析

    這篇文章主要介紹了Spring AOP與代理類的執(zhí)行順序級(jí)別,關(guān)于 Spring AOP和Aspectj的關(guān)系,兩個(gè)都實(shí)現(xiàn)了切面編程,Spring AOP更多地是為了Spring框架本身服務(wù)的,而Aspectj具有更強(qiáng)大、更完善的切面功能
    2023-03-03
  • 解決IDEA報(bào)錯(cuò)war?exploded?is?not?valid問題

    解決IDEA報(bào)錯(cuò)war?exploded?is?not?valid問題

    在使用IntelliJ?IDEA時(shí)遇到'[projectname]warexploded'無效的問題,可以通過清除項(xiàng)目列表、重新導(dǎo)入項(xiàng)目和配置新的Tomcat來解決,確保在Tomcat配置中,將ApplicationContext修改為僅包含一個(gè)'/',這一方法或許能幫助遇到相似問題的開發(fā)者
    2024-09-09
  • SpringBoot?屬性配置中獲取值的方式

    SpringBoot?屬性配置中獲取值的方式

    這篇文章主要介紹了SpringBoot?屬性配置中獲取值的方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Idea中指定xml文件失效的解決過程

    Idea中指定xml文件失效的解決過程

    最近在開發(fā)的過程中遇到了一個(gè)奇怪的問題,下面這篇文章主要給大家介紹了關(guān)于Idea中指定xml文件失效的解決過程,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例

    SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例

    本篇文章主要介紹了SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-02-02
  • 使用Java注解模擬spring ioc容器過程解析

    使用Java注解模擬spring ioc容器過程解析

    這篇文章主要介紹了使用Java注解模擬spring ioc容器過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java中的TreeSet源碼解讀

    Java中的TreeSet源碼解讀

    這篇文章主要介紹了Java中的TreeSet源碼解讀,TreeSet 是一個(gè) 有序集合,它擴(kuò)展了 AbstractSet 類并實(shí)現(xiàn)了 NavigableSet 接口,對象根據(jù)其自然順序以升序排序和存儲(chǔ),該 TreeSet 中使用 平衡樹,更具體的一個(gè) 紅黑樹,需要的朋友可以參考下
    2023-09-09
  • Java%(取模運(yùn)算)全面講解

    Java%(取模運(yùn)算)全面講解

    這篇文章主要介紹了Java%(取模運(yùn)算)全面講解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01

最新評(píng)論