如何用 Java 对 PDF 文件进行电子签章(五) 如何生成一个高清晰的签章

参考:

  1. https://blog.csdn.net/javasun608/article/details/79307845

  
具体步骤: 由PDF模板生成一个PDF文件、加签章。由itext5 生成的签章 图片更加清晰

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;
        PdfReader reader=null;
        PdfStamper ps =null;
        try {
            reader = new PdfReader(templatePdfPath);
            bos = new ByteArrayOutputStream();
            /* 将要生成的目标PDF文件名称 */
            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();
                }
            }
        }
         if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
             if (ps != null) {
                try {
                    ps.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();
    }
 
    }

可以分析下下面这两个签章的区别,发现左边的签章很模糊,右边的特别清晰。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值