OpenPDF的学习文档(第十二章·PDF签名)
什么是数字签名
- 本质是经过加密计算的一串数据。
- 提供以下三个保障:
- 身份真实性: 可验证的,是经过 XXX签署的。
- 不可否认性: XXX签署过的PDF,XXX不可以否认。
- 内容完整性: 签名之后,PDF再次被修改,针对XXX的验签会失败。
- 签名的体现
- 真正的签名数据:隐藏在 PDF 文件底层结构里的二进制数据包,不可见。
- 所有在页面上能看到的签名,都是外观表达,本质上是PDF的一个注释。
- 图片:扫描图,电子印章。
- 一段文字:签署人+签署时间。
- 图文混合:印章图片+签署人+签署时间。
- 完全不可见:可以不在页面上展示。
证书、加密、国密算法
- 需要了解的东西
-
证书链、信任源(Certificate Chain / CA Root)
-
公钥证书(X.509 Digital Certificate)
-
私钥(Private Key)
-
完整链条:
- 生成公私密钥对:常用
OpenSSL- 如果Windows没有安装OpenSSL
-
# 更新索引 winget source update # 搜索openssl,找到对应的ID winget search openssl # 安装 OpenSSL winget install ShiningLight.OpenSSL.Light # 查看是否安装成功 openssl version - 如果不行,手动把环境变量设置到Path中。
- 一般是这个地址:
C:\Program Files\OpenSSL-Win64\bin
-
- 生成 RSA 2048 位的私钥文件:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out C:/TMP/private_key.pem![image]()
- 从私钥中提取对应的公钥文件
openssl pkey -in C:/TMP/private_key.pem -pubout -out C:/TMP/public_key.pem![image]()
- 国密
-
# 1. 生成 SM2 椭圆曲线私钥 openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:SM2 -out sm2_private_key.pem # 2. 从 SM2 私钥中提取 SM2 公钥 openssl pkey -in sm2_private_key.pem -pubout -out sm2_public_key.pem # 3. (可选)使用 SM2 私钥直接生成带 SM3 摘要的 CSR 申请文件 openssl req -new -key sm2_private_key.pem -sm3 -out sm2_request.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=ZhangSan"
-
- 如果Windows没有安装OpenSSL
- 基于私钥生成CSR申请文件:
- 交互式生成
openssl req -new -key C:/TMP/private_key.pem -out C:/TMP/request.csr![image]()
![image]()
- 信息一定要认真填写,后面向CA申请证书是要花钱的。
- 验证一下文件request.csr是否正确
openssl req -in C:/TMP/request.csr -noout -text![image]()
- 千万不要将private_key.pem泄露出去
- 将CSR文件交给CA:
- 如果是面向公网,就必须找到公信 CA,交钱办理证书
- 如果是面向系统内部,可内部生成。
- 使用OpenSSL充当CA
openssl genrsa -out C:/TMP/ca_private.key 2048CA的私钥openssl req -x509 -new -nodes -key C:/TMP/ca_private.key -sha256 -days 3650 -out C:/TMP/ca_cert.crtCA交互式生成crt文件。![image]()
- 为上述生成的
request.csr使用ca_cert.crt + ca_private.key来签发证书。 openssl x509 -req -in C:/TMP/request.csr -CA C:/TMP/ca_cert.crt -CAkey C:/TMP/ca_private.key -CAcreateserial -out C:/TMP/user_cert.crt -days 365 -sha256- 自建CA成功,生成证书
user_cert.crt。 - 将自建的ca_cert.crt根证书导入到系统,否则PDF阅读器不认这个签名。
![image]()
- 如果是面向公网,就必须找到公信 CA,交钱办理证书
- 将私钥+CA颁发的证书合并为PFX/P12:
- 散碎的文件不能直接被Java识别,需要打包为.p12格式。
openssl pkcs12 -export -out C:/TMP/signer.p12 -inkey C:/TMP/private_key.pem -in C:/TMP/user_cert.crt -certfile C:/TMP/ca_cert.crt- 会要求输入密码,需要牢记这个密码。
- 最后得到signer.p12,使用它来进行签名验签。
- 生成公私密钥对:常用
-
添加签名
@Slf4j
public class SignUtils {
private static final String p12Path = "C:/TMP/sign/signer.p12";
private static final char[] p12Password = "liang".toCharArray();
private static final PrivateKey pk;
private static final Certificate[] chain;
static {
try {
// 注册 Bouncy Castle 密码学提供者
Security.addProvider(new BouncyCastleProvider());
// 加载 PKCS12 (.p12) 密钥库,获取私钥和证书链
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(Files.newInputStream(Paths.get(p12Path)), p12Password);
String alias = ks.aliases().nextElement();
pk = (PrivateKey) ks.getKey(alias, p12Password);
chain = ks.getCertificateChain(alias);
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException |
UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
}
public static byte[] sgin(PdfReader reader) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 对文档签名
PdfStamper stamper = PdfStamper.createSignature(reader, os, "\u0000", null, true);
// 添加可见属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
// 设置密码参数
appearance.setCrypto(pk, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
// 签名理由
appearance.setReason("合同签署 / 文件盖章");
// 签名地点
appearance.setLocation("北京");
// 设置可见盖章框的范围
appearance.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, "Signature1");
// 完成签名并保存文件
stamper.close();
return os.toByteArray();
}
}
@Test
public void readPDF() throws IOException {
PdfReader reader = new PdfReader("C:/TMP/sign/abc.pdf");
byte[] bytes = SignUtils.sgin(reader);
reader.close();
Files.write(Paths.get("C:/TMP/sign/abc_sign.pdf"),bytes);
}

public static byte[] sgin(PdfReader reader, Image image) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 对文档签名
PdfStamper stamper = PdfStamper.createSignature(reader, os, "\u0000", null, true);
// 添加可见属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
// 签名理由
appearance.setReason("合同签署 / 文件盖章");
// 签名地点
appearance.setLocation("北京");
// 设置密码参数
appearance.setCrypto(pk, chain, null, PdfSignatureAppearance.SELF_SIGNED);
if (image != null) {
appearance.setSignatureGraphic(image);
// 图片和描述信息
appearance.setRender(PdfSignatureAppearance.SignatureRenderGraphic);
String uniqueFieldName = "Signature_" + System.currentTimeMillis();
// 设置可见盖章框的范围
appearance.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, uniqueFieldName);
}
// 完成签名并保存文件
stamper.close();
return os.toByteArray();
}

验证签名
public static void 验签 () throws IOException, SignatureException{
// 读取PDF
PdfReader reader = new PdfReader(Files.newInputStream(Paths.get("C:/TMP/007.PDF")));
// 获取AcroFields的只读版本
AcroFields fields = reader.getAcroFields();
// 获取已签名的字段名称
List<String> names = fields.getSignedFieldNames();
System.out.println("签名数量: " + names.size());
for (String name : names) {
System.out.println("======== 签名域: " + name + " ========");
// 是否是整个文档的最后一次修改
System.out.println("覆盖整个文档: " +
fields.signatureCoversWholeDocument(name));
// 取出签名对象
PdfPKCS7 pkcs7 = fields.verifySignature(name);
// 验证签名值是否正确(最关键)
boolean integrity = pkcs7.verify();
System.out.println("签名完整性: " + integrity);
// 签名时间
Calendar signDate = pkcs7.getSignDate();
System.out.println("签名时间: " + signDate.getTime());
// 签名证书
X509Certificate cert = pkcs7.getSigningCertificate();
System.out.println("签名证书: " + cert.getSubjectX500Principal());
try {
cert.checkValidity();
} catch (CertificateExpiredException e) {
System.out.println("签名证书已过期: " + cert.getSubjectX500Principal());
} catch (CertificateNotYetValidException e) {
System.out.println("签名证书无效: " + cert.getSubjectX500Principal());
}
// 是否包含时间戳
if (pkcs7.getTimeStampDate() != null) {
System.out.println("时间戳时间: " +
pkcs7.getTimeStampDate().getTime());
}
//
}
reader.close();
}









浙公网安备 33010602011771号