开始之前
公司近期做的项目用到了电子签章(给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库里找到
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>keystore-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>keystore-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.60</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目结构
-放一张结构图,防止找不到在哪里

签署工具类
- 接下来我们新建一个签章文件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);
}
}
}
哦~ 这一顿操作下来原来是要我们给算法名啊。根据不同的算法获取对应的签名摘要。当然我们也可以自己去实现,签名算法同样也可以由我们自己实现(敲黑板,我要留作业了)
你学废了吗?
创作不易,请点个赞嘛~
本文详细介绍使用Java和itext库实现PDF文件的电子签名过程,包括生成keystore证书、编码项目结构及签署工具类的具体实现。

1519

被折叠的 条评论
为什么被折叠?



