1 说明

本文主要讲解使用Java和SpringBoot框架设计实现对PDF的签章操作。实现对PDF的签章操作不是简单的找个图片贴到PDF上即可,而是需要申请数字证书才能对PDF签章,否则无法验证签章的身份。具体实现分两步进行,第一步生成PKCS12证书,第二步添加签章。

2 生成PKCS12证书

生成PKCS12证书是通过使用bouncycastle包实现的,具体实现如下所示,首先引入依赖。

     <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.49</version></dependency>

步骤1: 编写生成PKCS12证书工具类

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
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;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;import com.css.modules.file.entity.Extension;/*** 证书生成工具类** @author 一朝风月* @date 2021/8/6 14:20*/
public class GenerateCertificateUtil {private static KeyPair getKey() throws NoSuchAlgorithmException {// 密钥对 生成器,RSA算法 生成的  提供者是 BouncyCastleKeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());// 密钥长度 1024generator.initialize(1024);// 证书中的密钥 公钥和私钥return generator.generateKeyPair();}/*** 创建证书** @param password       密码* @param issuerStr      颁发机构信息* @param subjectStr     使用者信息* @param certificateCrl 颁发地址* @return 证书*/public static Map<String, byte[]> createCert(String password, String issuerStr, String subjectStr,String certificateCrl, List<Extension> extensions) {Map<String, byte[]> result = new HashMap<>();ByteArrayOutputStream out = null;try {// 生成证书// KeyStore keyStore = KeyStore.getInstance("JKS");KeyStore keyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());keyStore.load(null, null);KeyPair keyPair = getKey();//  issuer与 subject相同的证书就是CA证书Certificate cert = generateCertificate(issuerStr, subjectStr, keyPair, result, certificateCrl, extensions);// 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 {try {if (out != null) {out.close();}} catch (Exception e) {e.printStackTrace();}}return result;}/*** 生成证书** @param issuerStr      事项字符串* @param subjectStr     主题字符串* @param keyPair        密钥对(公钥,私钥)* @param result         结果* @param certificateCrl CRL分发点* @param extensions     扩展字段* @return 生成证书*/private static Certificate generateCertificate(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 identifier = 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(identifier, true, crlDistPoint);// 用途ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier("2.5.29.15");// | KeyUsage.nonRepudiation | KeyUsage.keyCertSignbuilder.addExtension(keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));// 基本限制 X509Extension.javaASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier("2.5.29.19");builder.addExtension(basicConstraints, true, new BasicConstraints(true));// privateKey:使用自己的私钥进行签名,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(StandardCharsets.UTF_8));//证书有效结束时间result.put("notAfter", format.format(notAfter).getBytes(StandardCharsets.UTF_8));} catch (Exception e) {e.printStackTrace();} finally {if (bout != null) {try {bout.close();} catch (IOException e) {e.printStackTrace();}}}return cert;}
}

步骤2: 证书参数类

import lombok.Data;/*** 证书参数** @author 一朝风月* @date 2021/8/6 16:06*/
@Data
public class CertificateParams {/*** 事项字符串*/private String issuerStr;/*** 主题字符串*/private String subjectStr;/*** CRL分发点*/private String certificateCrl;/*** 证书密码*/private String password;/*** 额外的信息*/private String extensionsJson;
}

步骤3: 编写获取证书接口

 /*** 获取PKCS12证书** @param params   证书参数* @param response HTTP响应* @return PKCS12证书* @throws IOException 异常*/@PostMapping("getCertificate")public Result mGetCertificate(CertificateParams params, HttpServletResponse response) throws IOException {/** 填写说明** CN: 名字与姓氏 OU : 组织单位名称* O :组织名称 L : 城市或区域名称 E : 电子邮件* ST: 州或省份名称 C: 单位的两字母国-家代码** 例如:* String issuerStr = "CN=jcb凭证,OU=研发部,O=jcb有限公司,C=CN,E=jcb@sina.com,L=北京,ST=北京";* String subjectStr = "CN=jcb有限公司,OU=用户,O=test,C=CN,E=jcb@sina.com,L=北京,ST=北京";* String certificateCrl = "https://jcb.cn";*/Map<String, byte[]> result = GenerateCertificateUtil.createCert(params.getPassword(), params.getIssuerStr(),params.getSubjectStr(), params.getCertificateCrl(),JSONArray.parseArray(params.getExtensionsJson(), Extension.class));// 将证书放置在response中response.setContentType("application/*");response.setHeader("Content-Disposition", "attachment;filename=\"" + "PKCS12证书" + "\"");IOUtils.write(result.get("keyStoreData"), response.getOutputStream());return null;}

3 使用证书添加签章

使用刚刚生成的证书添加签章,依赖如下所示:

     <dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version></dependency>

3.1 签章参数类

import lombok.Data;/*** 签章参数** @author 一朝风月* @date 2021/8/6 10:12*/
@Data
public class SignSealParams {/*** 原因*/private String reason;/*** 位置*/private String location;/*** 页码*/private int pageNum;/*** 签名域名称,多次追加签名的时候,签名域名称不能一样*/private String fieldName;/*** 证书密码*/private String password;/*** 图章左下角x*/private int llx;/*** 图章左下角y*/private int lly;/*** 图章右上角x*/private int urx;/*** 图章右上角y*/private int ury;SignSealParams() {this.pageNum = 1;this.fieldName = "sig1";this.llx = 200;this.lly = 200;this.urx = 300;this.ury = 300;}
}

3.2 添加签章

 /*** 添加签章** @param file        PDF* @param seal        印章或者签名* @param certificate PKCS12证书* @param params      参数* @param response    HTTP响应* @return 返回增加签章的PDF* @throws Exception 异常*/@PostMapping("addSeal")public Result mAddSeal(MultipartFile file, MultipartFile seal, MultipartFile certificate,SignSealParams params, HttpServletResponse response) throws Exception {FileProps fileProps = new FileProps(file);if (!"pdf".equalsIgnoreCase(fileProps.getFileType())) {return Result.error("请传入PDF文件!");}PdfReader reader = new PdfReader(file.getInputStream());response.setContentType("application/*");response.setHeader("Content-Disposition", "attachment;filename=\"" + fileProps.getFileName() + "\"");/** 创建签章工具PdfStamper 最后一个boolean参数,* false:pdf文件只允许被签名一次,多次签名,最后一次有效* true:pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改*/PdfStamper stamper = PdfStamper.createSignature(reader, response.getOutputStream(),'\0', null, true);// 获取数字签章属性对象,设定数字签章的属性PdfSignatureAppearance appearance = stamper.getSignatureAppearance();appearance.setReason(params.getReason());appearance.setLocation(params.getLocation());appearance.setVisibleSignature(new Rectangle(params.getLlx(), params.getLly(), params.getUrx(), params.getUry()),params.getPageNum(), params.getFieldName());Image image = Image.getInstance(seal.getBytes());appearance.setSignatureGraphic(image);appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);ExternalDigest digest = new BouncyCastleDigest();KeyStore ks = KeyStore.getInstance("PKCS12");ks.load(certificate.getInputStream(), params.getPassword().toCharArray());String alias = ks.aliases().nextElement();PrivateKey pk = (PrivateKey) ks.getKey(alias, params.getPassword().toCharArray());Certificate[] chain = ks.getCertificateChain(alias);ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA1, null);MakeSignature.signDetached(appearance, digest, signature, chain, null,null, null, 0, CryptoStandard.CMS);return null;}

4 结果

测试一下上述代码的执行结果

Java 签章操作的设计与实现相关推荐

  1. Java图形化界面设计——容器(JFrame)

    Java图形化界面设计--容器(JFrame) 程序是为了方便用户使用的,因此实现图形化界面的程序编写是所有编程语言发展的必然趋势,在命令提示符下运行的程序可以让我们了解java程序的基本知识体系结构 ...

  2. java字符串操作_Java的字符串操作

    Java的字符串操作 小型送分题:Java有字符串(String),StringBuffer(字符串缓存),StringBuilder(字符串建造者?)各种实现,究其原因还是历史上的各种坑. 一.不同 ...

  3. Java反射:框架设计的灵魂

    Java反射:框架设计的灵魂 框架:办成品软件,可以在框架的基础上进行开发 反射:将类的各个部分封装成对象,这就是反射机制 反射的好处 在程序运行的过程中,操作这些对象 可以降低程序的耦合性,提高程序 ...

  4. java技术论坛的毕业设计_基于java的bbs论坛设计,软件毕业设计

    基于java的bbs论坛设计,软件毕业设计 XXX毕 业 设 计 任 务 书专业 软件技术 年级 xx 级 班级 二班姓名 xx 学号 20 xx0205xx3威 海 职 业 学 院 教 务 处 编 ...

  5. Java Spring MVC分层设计

    Java Spring MVC分层设计 第一次尝试着用Java做Web开发,使用了Java Spring框架,顺便说一句,如果使用Spring开发,建议使用STS(Spring Tool Suite) ...

  6. java 解析/操作 xml 几种常用方式 xml的增加/删除/修改

    java 解析/操作 xml 几种常用方式 xml的增加/删除/修改 首先,我们先介绍几种常用的xml解析器. 1. 介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方 ...

  7. JAVA高级应用课程设计(网上书城系统——会员登陆模块的设计与实现)

    课程设计报告 课   程  名   称: JAVA高级应用课程设计 设   计  题   目:网上书城系统--会员登陆模块的设计与实现 目 录 一.开发背景. 1 (一)背景概述. 1 (二)发展前景 ...

  8. Java拼图游戏总结,Java拼图游戏课程设计报告

    Java拼图游戏课程设计报告 JavaJava 程序设计与应用开发 课程设计报告程序设计与应用开发 课程设计报告 设计题目 拼图大作战 学生姓名 学生班级 学生学号 指导教师 完成时间2016 年 0 ...

  9. 基于Java的雷电游戏设计(含源文件)

    欢迎添加微信互相交流学习哦! 项目源码:https://gitee.com/oklongmm/biye 基于Java的雷电游戏 摘   要    电脑游戏,是指在计算机上能够运转的游戏软件.这种软件具 ...

最新文章

  1. 使用池来实现并发服务器
  2. Typescript中使用Axios
  3. 适配器设计模式,简单的Java代码模拟
  4. 给那些被墙困扰着,找不到库的孩子们
  5. spring整合junit测试
  6. 欧几里德算法求最大公约数
  7. 前端学习(561):解决margin重叠第二种情况父子
  8. apache 官方 Dubbo 文档
  9. 机器学习实现线性梯度算实现octave
  10. Django中的ORM进阶操作
  11. Navicat for MySQL 64位破解版
  12. LINGO 11.0安装教程
  13. WAP 1.X 2.0 相关知识
  14. 微信小程序模仿购物车页面
  15. IOS8 keyboardWillShow 在UIKeyboardWillShowNotification 调用两次 问题解决
  16. JUC —— 常用辅助类
  17. Java继承的特征和优势
  18. 一本书读懂财报 | 现金流量表剖析
  19. 01组团队项目-Alpha冲刺-5/6
  20. 谷歌、互联网股票与以太坊

热门文章

  1. 【闲聊】写给毕业生们的一些话
  2. [小题大做] Github + Jenkins 实现自动化部署 hexo 博客静态文件
  3. 提升代码质量的方法:领域模型、设计原则、设计模式
  4. 前端vue:节点、树以及虚拟 DOM
  5. Java 框架,黑马 java 视频教程,面试资料分享
  6. 遗传基因科普(2):人体DNA有多长?
  7. 二进制部署K8S多Master+LB负载均衡群集+K8S日志排错
  8. 6. 欲组成一个64K×16位的存储器,若选用32K×8位的存储芯片,共需________片;若选用16K×1位的存储芯片,则需________片;若选用1 K×4位的存储芯片共需________片。
  9. 欣向路由全自动一吧多网的实现(转)
  10. 23英寸显示器DELL U2312HM尺寸