使用Java对PDF进行电子签章 您所在的位置:网站首页 怎么用手机在pdf上签名字用什么软件 使用Java对PDF进行电子签章

使用Java对PDF进行电子签章

2024-06-28 23:27| 来源: 网络整理| 查看: 265

使用Java对PDF进行电子签章 开始之前前期准备开始生成keystore证书来张材料全家福编码项目结构签署工具类

开始之前 公司近期做的项目用到了电子签章(给PDF盖章签名),这过程真是曲折。恰逢现在时间比较空闲(有时间摸鱼)。我把签章的过程给记录下来 前期准备 1. 需要签名的PDF (废话,没有PDF签什么名啊~) 2. 给PDF签名的印章图片 (貌似也是废话) 3. keystore 证书文件 (先认识认识,教程接下来会说怎么生成) 开始 生成keystore证书 安装有JDK的机器,并配置系统变量 (安装教程很多,自行百度) 墨染涟漪搜索cmd选择管理员身份打开!(管理员身份运行!管理员身份运行!管理员身份运行!) 墨染涟漪输入以下信息 keytool -genkey -alias lianyi -keyalg RSA -validity 30 -keystore android.keystore

说明:

-alias 证书别名

-keyalg 算法,有两种:RSA 和 CipherSuite RSA

-validity 证书有效期,我这里是30天

-keystore 证书的名称以及路径(方便演示,我这里放在桌面)

输入口令的时候,出于安全考虑是不显示的。直接输入密码再次确认就行。 墨染涟漪

然后输入一些信息,按照提示输入就行。输入密钥口令的时候如果和上面密钥库口令相同可以直接回车,我这里使用相同的,直接回车。 墨染涟漪

回车完之后在哪里找到呢? 打开C盘 -> 用户 -> [用户名] 就可以找到刚才生成的证书了。 墨染涟漪

来张材料全家福

在这里插入图片描述

至此,需要的资料就准备完毕了~

接下来…废话少说,直接上号墨染涟漪

编码

创建项目: 墨染涟漪 输入工程名,组织名,不说了 墨染涟漪 什么都不选,等下自己加 墨染涟漪 瞄一眼pom文件里加的坐标,其实都可以从maven库里找到

4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.5 com.example keystore-demo 0.0.1-SNAPSHOT keystore-demo Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web 2.4.0 com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcpkix-jdk15on 1.60 org.bouncycastle bcprov-jdk15on 1.60 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin 项目结构

-放一张结构图,防止找不到在哪里 墨染涟漪

签署工具类 接下来我们新建一个签章文件KeystoreUtils.java 下面的是引入的包,不要引错了 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 java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.cert.Certificate; /** * * @author 墨 * @create 2021-04-28 17:33 */ public class KeystoreUtils { /** * * @param src 需要签章的pdf文件路径 * @param dest 签完章的pdf文件路径 * @param chain 证书链 * @param img 印章图片 * @param pk 签名私钥 * @param digestAlgorithm 摘要算法名称,例如SHA-1 * @param provider 密钥算法提供者,可以为null * @param subfilter 数字签名格式,itext有2种 * @param reason 签名的原因,显示在pdf签名属性中 * @param location 签名的地点,显示在pdf签名属性中 * @throws GeneralSecurityException * @throws IOException * @throws DocumentException */ public void sign(String src, String dest,String img, Certificate[] chain, PrivateKey pk, String digestAlgorithm, String provider, MakeSignature.CryptoStandard subfilter, String reason, String location) throws GeneralSecurityException, IOException, DocumentException { PdfReader pdfReader = new PdfReader(src); FileOutputStream fileOutputStream = new FileOutputStream(dest); /** * 1 参数依次为:文件名、文件输入流、文件版本号、临时文件、是否可以追加签名 * 1.1 false的话,pdf文件只允许被签名一次,多次签名,最后一次有效 * 1.2 true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改 */ PdfStamper stamper = PdfStamper.createSignature(pdfReader, fileOutputStream, '\0', null, false); // 获取数字签章属性对象,设定数字签章的属性 PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setReason(reason); appearance.setLocation(location); /** * 1 三个参数依次为:设置签名的位置、页码、签名域名称,多次追加签名的时候,签名域名称不能一样 * 1.1 签名的位置四个参数:印章左下角的X、Y轴坐标,印章右上角的X、Y轴坐标, * 这个位置是相对于PDF页面的位置坐标,即该坐标距PDF当前页左下角的坐标 */ appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "sign"); /** * 用于盖章的印章图片,引包的时候要引入itext包的image */ Image image = Image.getInstance(img); appearance.setSignatureGraphic(image); /** * 设置认证等级,共4种,分别为: * NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、 * CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS * * 需要用哪一种根据业务流程自行选择 */ appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED); /** * 印章的渲染方式,同样有4种: * DESCRIPTION、NAME_AND_DESCRIPTION, * GRAPHIC_AND_DESCRIPTION,GRAPHIC; * 这里选择只显示印章 */ appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC); /** * 算法主要为:RSA、DSA、ECDSA * 摘要算法,这里的itext提供了2个用于签名的接口,可以自己实现 */ ExternalDigest digest = new BouncyCastleDigest(); /** * 签名算法,参数依次为:证书秘钥、摘要算法名称,例如MD5 | SHA-1 | SHA-2.... 以及 提供者 */ ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, null); /** * 最重要的来了,调用itext签名方法完成pdf签章 */ MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter); } }

OK 到这里我们的签署就完成了,下面只需要去提供参数调用该工具类就行。那我们直接开搞

在测试类KeystoreDemoApplicationTests给出以下代码 import com.example.utils.KeystoreUtils; import com.itextpdf.text.pdf.security.DigestAlgorithms; import com.itextpdf.text.pdf.security.MakeSignature; import org.springframework.boot.test.context.SpringBootTest; import javax.swing.*; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; @SpringBootTest class KeystoreDemoApplicationTests { public static final String KEYSTORE = "D:\\android.keystore"; // 之前生成的keystory密码 public static final char[] PASSWORD = "123456".toCharArray(); // 需要签名的PDF路径 public static final String SRC = "D:\\abc.pdf"; // 完成签名的PDF路径 public static final String OUTPUT_SRC = "D:\\abc-sign.pdf"; public static final String IMG = "D:\\seal01.jpg"; public static void main(String[] args) { try { //读取keystore ,获得私钥和证书链 KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(KEYSTORE), PASSWORD); String alias = (String)keyStore.aliases().nextElement(); PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, PASSWORD); Certificate[] chain = keyStore.getCertificateChain(alias); KeystoreUtils keystoreUtils = new KeystoreUtils(); keystoreUtils.sign(SRC, String.format(OUTPUT_SRC, 3),IMG, chain, PrivateKey, DigestAlgorithms.SHA1, null, MakeSignature.CryptoStandard.CMS, "Test", "Ghent"); } catch (Exception e) { JOptionPane.showMessageDialog(null, e.getMessage()); e.printStackTrace(); } } }

直接运行看下效果 在这里插入图片描述

这样,我们就完成了电子签名。

我们再修改下一下KeystoreUtils中印章位置

appearance.setVisibleSignature(new Rectangle(200, 200, 300, 300), 1, "sign1");

再运行查看 在这里插入图片描述 看到位置已经改变~ 做是做完了,但是还记得那两行摘要算法和签名算法吗?回顾一下

ExternalDigest digest = new BouncyCastleDigest(); ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, null);

我们去康康摘要算法是怎么实现的

public class BouncyCastleDigest implements ExternalDigest { public BouncyCastleDigest() { } public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException { String oid = DigestAlgorithms.getAllowedDigests(hashAlgorithm); if (oid == null) { throw new NoSuchAlgorithmException(hashAlgorithm); } else if (oid.equals("1.2.840.113549.2.2")) { return new Digest(); } else if (oid.equals("1.2.840.113549.2.5")) { return new org.bouncycastle.jcajce.provider.digest.MD5.Digest(); } else if (oid.equals("1.3.14.3.2.26")) { return new org.bouncycastle.jcajce.provider.digest.SHA1.Digest(); } else if (oid.equals("2.16.840.1.101.3.4.2.4")) { return new org.bouncycastle.jcajce.provider.digest.SHA224.Digest(); } else if (oid.equals("2.16.840.1.101.3.4.2.1")) { return new org.bouncycastle.jcajce.provider.digest.SHA256.Digest(); } else if (oid.equals("2.16.840.1.101.3.4.2.2")) { return new org.bouncycastle.jcajce.provider.digest.SHA384.Digest(); } else if (oid.equals("2.16.840.1.101.3.4.2.3")) { return new org.bouncycastle.jcajce.provider.digest.SHA512.Digest(); } else if (oid.equals("1.3.36.3.2.2")) { return new org.bouncycastle.jcajce.provider.digest.RIPEMD128.Digest(); } else if (oid.equals("1.3.36.3.2.1")) { return new org.bouncycastle.jcajce.provider.digest.RIPEMD160.Digest(); } else if (oid.equals("1.3.36.3.2.3")) { return new org.bouncycastle.jcajce.provider.digest.RIPEMD256.Digest(); } else if (oid.equals("1.2.643.2.2.9")) { return new org.bouncycastle.jcajce.provider.digest.GOST3411.Digest(); } else { throw new NoSuchAlgorithmException(hashAlgorithm); } } }

哦~ 这一顿操作下来原来是要我们给算法名啊。根据不同的算法获取对应的签名摘要。当然我们也可以自己去实现,签名算法同样也可以由我们自己实现(敲黑板,我要留作业了)

你学废了吗?

创作不易,请点个赞嘛~



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有