历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案: 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案

您所在的位置:网站首页 盖章签章 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案: 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案

历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案: 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案

2024-06-24 20:48| 来源: 网络整理| 查看: 265

历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案 一、前言

九月中旬到十月底,我和同事参加了某个系统的开发,涉及到对PDF的电子签字+盖章+防伪二维码+水印等,我最开始选择使用pageoffice实现PDF的盖章和签字,并且也写了一篇博客来进行详细的介绍(pageoffice实现签名盖章:http://t.csdn.cn/nNxpe),但是出现一个问题,PageOffice支持JAVA、ASP.NET、PHP多种编程开发语言,使开发集成简单高效,事半功倍。让集成PAGEOFFICE的协同办公系统更具价值,但是他是卓正软件公司的一个项目,需要收费,并且安全系数不一定能得到保证,而这个项目需要上生产,所以经过多方的研究,学习,总结,今天终于将一套PDF集成线上签字+盖章+防伪二维码+水印的一系列解决方法总结出来,今天写这篇博客进行开源

二、使用itextPDF实现PDF电子公章工具类 1、电子公章的制作

我们需要实现电子公章盖章,但是不能使用公司的章,我们这次推荐使用的线上做章工具来模拟电子印章

做章网站:http://seal.biaozhiku.com/

我们选择圆形印章

然后输入公司名,输入章名输入编码然后点击395生成,最后点击保存图片,我们的个人专业章就实现了

电子公章效果如图:

PDF模板图

生成PDF效果图

2、itextPDF的相关依赖 com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 使用的是boot项目,所以完整依赖是 org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 3、相关配置及数字签名的配置 (1)摘要算法 我们项目启动之后报错

需要加这个配置文件就不报错了,这个主要原因是摘要算法没有,需要引入相关依赖 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 涉及到加密算法就需要数字签名了(数字签名格式,CMS,CADE),我们就需要一个文件(我的命名是:server.p12)这个东西需要我们自己电脑生成数字签名

(2)java工具keytool生成p12数字证书文件

Keytool是用于管理**和证书的工具,位于%JAVA_HOME%/bin目录。 使用JDK的keytool工具

keytool在jdk的bin目录下

2. 打开keytool所在的bin目录,然后在上面的路径显示框中输入CMD,然后回车,即可在当前文件夹下打开命令提示符,并且路径是当前文件夹。

生成数字文件,在命令行输入 keytool -genkeypair -alias whj -keypass 111111 -storepass 111111 -dname “C=CN,ST=SD,L=QD,O=haier,OU=dev,CN=haier.com” -keyalg RSA -keysize 2048 -validity 3650 -keystore D:\keystore\server.keystore

参数解释:

storepass keystore 文件存储密码 keypass 私钥加解密密码 alias 实体别名(包括证书私钥) dname 证书个人信息 keyalt 采用公钥算法,默认是DSA keysize **长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA) validity 有效期 keystore 指定keystore文件

转换为p12格式

在命令行输入

keytool -importkeystore -srckeystore D:\keystore\server.keystore -destkeystore D:\keystore\whj.p12 -srcalias whj -destalias serverkey -srcstoretype jks -deststoretype pkcs12 -srcstorepass 111111 -deststorepass 111111 -noprompt

生成的最终文件

4、项目结构及源码

工具类(可以直接复制) package com.whj.pdf; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.security.*; import com.whj.entity.SignatureInfo; import java.io.*; import java.security.GeneralSecurityException; /** * @author 王恒杰 * @date 2022/10/13 22:52 * @Description:盖章功能工具类 */ public class ItextUtil { public static final char[] PASSWORD = "111111".toCharArray();// keystory密码 /** * 单多次签章通用 * * @param src * @param target * @param signatureInfo * @throws GeneralSecurityException * @throws IOException * @throws DocumentException */ @SuppressWarnings("resource") public void sign(String src, String target, SignatureInfo signatureInfo) { InputStream inputStream = null; FileOutputStream outputStream = null; ByteArrayOutputStream result = new ByteArrayOutputStream(); try { inputStream = new FileInputStream(src); ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(inputStream); // 创建签章工具PdfStamper ,最后一个boolean参数是否允许被追加签名 // false的话,pdf文件只允许被签名一次,多次签名,最后一次有效 // true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改 PdfStamper stamper = PdfStamper.createSignature(reader, tempArrayOutputStream, '\0', null, true); // 获取数字签章属性对象 PdfSignatureAppearance appearance = stamper .getSignatureAppearance(); appearance.setReason(signatureInfo.getReason()); appearance.setLocation(signatureInfo.getLocation()); // 设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样 图片大小受表单域大小影响(过小导致压缩) // 签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角 // 四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y //四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y appearance.setVisibleSignature(new Rectangle(280, 220, 140, 600), 1, "sig1"); // 读取图章图片 Image image = Image.getInstance(signatureInfo.getImagePath()); appearance.setSignatureGraphic(image); appearance.setCertificationLevel(signatureInfo .getCertificationLevel()); // 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示) appearance.setRenderingMode(signatureInfo.getRenderingMode()); // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现 // 摘要算法 ExternalDigest digest = new BouncyCastleDigest(); // 签名算法 ExternalSignature signature = new PrivateKeySignature( signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(), null); // 调用itext签名方法完成pdf签章 //数字签名格式,CMS,CADE MakeSignature.signDetached(appearance, digest, signature, signatureInfo.getChain(), null, null, null, 0, MakeSignature.CryptoStandard.CADES); inputStream = new ByteArrayInputStream( tempArrayOutputStream.toByteArray()); // 定义输入流为生成的输出流内容,以完成多次签章的过程 result = tempArrayOutputStream; outputStream = new FileOutputStream(new File(target)); outputStream.write(result.toByteArray()); outputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != outputStream) { outputStream.close(); } if (null != inputStream) { inputStream.close(); } if (null != result) { result.close(); } } catch (IOException e) { e.printStackTrace(); } } } } 实体类 package com.whj.entity; import com.itextpdf.text.pdf.PdfSignatureAppearance; import java.security.PrivateKey; import java.security.cert.Certificate; /** * @author 王恒杰 * @date 2022/10/13 22:52 * @Description: */ public class SignatureInfo { private String reason; //签名的原因,显示在pdf签名属性中 private String location;//签名的地点,显示在pdf签名属性中 private String digestAlgorithm;//摘要算法名称,例如SHA-1 private String imagePath;//图章路径 private String fieldName;//表单域名称 private Certificate[] chain;//证书链 private PrivateKey pk;//签名私钥 private int certificationLevel = 0; //批准签章 private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式:仅描述,仅图片,图片和描述,签章者和描述 //图章属性 private float rectllx;//图章左下角x private float rectlly;//图章左下角y private float recturx;//图章右上角x private float rectury;//图章右上角y public float getRectllx() { return rectllx; } public void setRectllx(float rectllx) { this.rectllx = rectllx; } public float getRectlly() { return rectlly; } public void setRectlly(float rectlly) { this.rectlly = rectlly; } public float getRecturx() { return recturx; } public void setRecturx(float recturx) { this.recturx = recturx; } public float getRectury() { return rectury; } public void setRectury(float rectury) { this.rectury = rectury; } public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getDigestAlgorithm() { return digestAlgorithm; } public void setDigestAlgorithm(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; } public String getImagePath() { return imagePath; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public Certificate[] getChain() { return chain; } public void setChain(Certificate[] chain) { this.chain = chain; } public PrivateKey getPk() { return pk; } public void setPk(PrivateKey pk) { this.pk = pk; } public int getCertificationLevel() { return certificationLevel; } public void setCertificationLevel(int certificationLevel) { this.certificationLevel = certificationLevel; } public PdfSignatureAppearance.RenderingMode getRenderingMode() { return renderingMode; } public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) { this.renderingMode = renderingMode; } } 测试 package com.whj.pdf; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.security.DigestAlgorithms; import com.whj.entity.SignatureInfo; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import static com.whj.pdf.ItextUtil.PASSWORD; /** * @author 王恒杰 * @date 2022/10/24 10:42 * @Description: 盖章功能实现 */ public class PdfStamp { public static void main(String[] args) { try { ItextUtil app = new ItextUtil(); // 将证书文件放入指定路径,并读取keystore ,获得私钥和证书链 String pkPath = "src/main/resources/whj.p12"; KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(new FileInputStream(pkPath), PASSWORD); String alias = ks.aliases().nextElement(); PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD); // 得到证书链 Certificate[] chain = ks.getCertificateChain(alias); //需要进行签章的pdf String path = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\程序员小王.pdf"; // 封装签章信息 SignatureInfo signInfo = new SignatureInfo(); signInfo.setReason("理由"); signInfo.setLocation("位置"); signInfo.setPk(pk); signInfo.setChain(chain); signInfo.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); signInfo.setDigestAlgorithm(DigestAlgorithms.SHA1); signInfo.setFieldName("demo"); // 签章图片 signInfo.setImagePath("D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\chapter.png"); signInfo.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC); //// 值越大,代表向x轴坐标平移 缩小 (反之,值越小,印章会放大) signInfo.setRectllx(100); //// 值越大,代表向y轴坐标向上平移(大小不变) signInfo.setRectlly(200); // 值越大 代表向x轴坐标向右平移 (大小不变) signInfo.setRecturx(150); // 值越大,代表向y轴坐标向上平移(大小不变) signInfo.setRectury(150); //签章后的pdf路径 app.sign(path, "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\out.pdf", signInfo); } catch (Exception e) { e.printStackTrace(); } } } 5、结果展示

三、thymeleaf+itext签字功能+PDF文件加密实现

所以需依赖

需要手工导入一个jar包(jar包我放在了源码里面,需要自行下载)

org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 com.google.guava guava 25.0-jre

因为这个的相关类太多,只展示了核心代码,全部代码请到博客底部下载源码

1、签字图片上传和签字实现的Controller package com.whj.controller; import com.whj.service.SignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /** * @author 王恒杰 * @date 2022/10/24 13:57 * @Description: 实现签字上传和签字功能 */ @RestController @RequestMapping("/sign") public class SignController { @Autowired private SignService signService; @PostMapping(value = "/uploadSign") @ResponseBody public String uploadSign(String img) { return signService.uploadSign(img); } @PostMapping(value = "/sign") @ResponseBody public String sign() { return signService.sign(id); } } 2、签字上传,签字,PDF文件加密业务层实现 /** * @author 王恒杰 * @date 2022/10/24 14:00 * @Description: */ @Service public class SignServiceImpl implements SignService{ @Override public String uploadSign(String img) { //String idCard="3000"; // 生成jpeg图片 idCard.asString() String url = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\img\\sign.png"; try { if (img == null) // 图像数据为空 { return "no"; } int i = img.indexOf("base64,") + 7;//获取前缀data:image/gif;base64,的坐标 String newImage = img.substring(i, img.length());//去除前缀 BASE64Decoder decoder = new BASE64Decoder(); // Base64解码 byte[] bytes = decoder.decodeBuffer(newImage); for (int j = 0; j 0) { point.bx = point.x; point.by = point.y; point.x = clickX.pop(); point.y = clickY.pop(); console.log(point.x); console.log(point.y); /*alert(point.x); alert(point.y);*/ point.drag = clickDrag.pop(); context.beginPath(); if(point.drag && point.notFirst) { context.moveTo(point.bx, point.by); } else { point.notFirst = true; context.moveTo(point.x - 1, point.y); } context.lineTo(point.x, point.y); context.closePath(); context.stroke(); } for(var i=0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3