Java 签章操作的设计与实现
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 签章操作的设计与实现相关推荐
- Java图形化界面设计——容器(JFrame)
Java图形化界面设计--容器(JFrame) 程序是为了方便用户使用的,因此实现图形化界面的程序编写是所有编程语言发展的必然趋势,在命令提示符下运行的程序可以让我们了解java程序的基本知识体系结构 ...
- java字符串操作_Java的字符串操作
Java的字符串操作 小型送分题:Java有字符串(String),StringBuffer(字符串缓存),StringBuilder(字符串建造者?)各种实现,究其原因还是历史上的各种坑. 一.不同 ...
- Java反射:框架设计的灵魂
Java反射:框架设计的灵魂 框架:办成品软件,可以在框架的基础上进行开发 反射:将类的各个部分封装成对象,这就是反射机制 反射的好处 在程序运行的过程中,操作这些对象 可以降低程序的耦合性,提高程序 ...
- java技术论坛的毕业设计_基于java的bbs论坛设计,软件毕业设计
基于java的bbs论坛设计,软件毕业设计 XXX毕 业 设 计 任 务 书专业 软件技术 年级 xx 级 班级 二班姓名 xx 学号 20 xx0205xx3威 海 职 业 学 院 教 务 处 编 ...
- Java Spring MVC分层设计
Java Spring MVC分层设计 第一次尝试着用Java做Web开发,使用了Java Spring框架,顺便说一句,如果使用Spring开发,建议使用STS(Spring Tool Suite) ...
- java 解析/操作 xml 几种常用方式 xml的增加/删除/修改
java 解析/操作 xml 几种常用方式 xml的增加/删除/修改 首先,我们先介绍几种常用的xml解析器. 1. 介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方 ...
- JAVA高级应用课程设计(网上书城系统——会员登陆模块的设计与实现)
课程设计报告 课 程 名 称: JAVA高级应用课程设计 设 计 题 目:网上书城系统--会员登陆模块的设计与实现 目 录 一.开发背景. 1 (一)背景概述. 1 (二)发展前景 ...
- Java拼图游戏总结,Java拼图游戏课程设计报告
Java拼图游戏课程设计报告 JavaJava 程序设计与应用开发 课程设计报告程序设计与应用开发 课程设计报告 设计题目 拼图大作战 学生姓名 学生班级 学生学号 指导教师 完成时间2016 年 0 ...
- 基于Java的雷电游戏设计(含源文件)
欢迎添加微信互相交流学习哦! 项目源码:https://gitee.com/oklongmm/biye 基于Java的雷电游戏 摘 要 电脑游戏,是指在计算机上能够运转的游戏软件.这种软件具 ...
最新文章
- 使用池来实现并发服务器
- Typescript中使用Axios
- 适配器设计模式,简单的Java代码模拟
- 给那些被墙困扰着,找不到库的孩子们
- spring整合junit测试
- 欧几里德算法求最大公约数
- 前端学习(561):解决margin重叠第二种情况父子
- apache 官方 Dubbo 文档
- 机器学习实现线性梯度算实现octave
- Django中的ORM进阶操作
- Navicat for MySQL 64位破解版
- LINGO 11.0安装教程
- WAP 1.X 2.0 相关知识
- 微信小程序模仿购物车页面
- IOS8 keyboardWillShow 在UIKeyboardWillShowNotification 调用两次 问题解决
- JUC —— 常用辅助类
- Java继承的特征和优势
- 一本书读懂财报 | 现金流量表剖析
- 01组团队项目-Alpha冲刺-5/6
- 谷歌、互联网股票与以太坊
热门文章
- 【闲聊】写给毕业生们的一些话
- [小题大做] Github + Jenkins 实现自动化部署 hexo 博客静态文件
- 提升代码质量的方法:领域模型、设计原则、设计模式
- 前端vue:节点、树以及虚拟 DOM
- Java 框架,黑马 java 视频教程,面试资料分享
- 遗传基因科普(2):人体DNA有多长?
- 二进制部署K8S多Master+LB负载均衡群集+K8S日志排错
- 6. 欲组成一个64K×16位的存储器,若选用32K×8位的存储芯片,共需________片;若选用16K×1位的存储芯片,则需________片;若选用1 K×4位的存储芯片共需________片。
- 欣向路由全自动一吧多网的实现(转)
- 23英寸显示器DELL U2312HM尺寸