在数字化办公场景中,PDF 电子签章是实现文档安全签署的重要手段。本文将深入探讨 PDF 签章的核心技术,包括证书生成、定位方法、外观定制及常见问题解决方案,并提供基于 Spire.PDF 库的完整 Java 代码示例。
一、PKCS#12 证书生成与应用
1. OpenSSL 生成自签名证书(Windows 环境)
# 生成私钥
openssl genrsa -out private.key 2048
# 生成证书请求(CSR)
openssl req -new -key private.key -out csr.csr -subj "/CN=name/OU=itdev/O=comp/L=63/ST=63/C=CN"
# 生成自签名证书
openssl x509 -req -in csr.csr -signkey private.key -out certificate.crt -days 365
# 转换为PKCS#12格式
openssl pkcs12 -export -in certificate.crt -inkey private.key -out certificate.p12 -password pass:yourpassword
2. Java 代码加载 PKCS#12 证书
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
public class CertificateLoader {
public static void main(String[] args) throws Exception {
// 加载PKCS#12证书
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("certificate.p12"), "yourpassword".toCharArray());
// 获取私钥和证书
String alias = keyStore.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, "yourpassword".toCharArray());
Certificate certificate = keyStore.getCertificate(alias);
}
}
二、PDF 签章核心技术实现
1. 基于关键字定位的签章实现
import com.spire.pdf.*;
import com.spire.pdf.general.find.PdfTextFind;
import com.spire.pdf.security.PdfCertificate;
import com.spire.pdf.security.PdfSignature;
public class PdfSigner {
public static void main(String[] args) {
String keyword = "签章";
String pdfPath = "input.pdf";
String p12Path = "certificate.p12";
String outputPath = "signed.pdf";
try (PdfDocument pdf = new PdfDocument()) {
pdf.loadFromFile(pdfPath);
// 关键字定位
List<float[]> locations = locateKeyword(pdf, keyword);
if (locations.isEmpty()) throw new RuntimeException("未找到关键字");
float[] firstLocation = locations.get(0);
int pageIndex = (int) firstLocation[0];
float keywordX = firstLocation[1];
float keywordY = firstLocation[2];
// 创建签名
PdfPageBase page = pdf.getPages().get(pageIndex);
PdfSignature signature = new PdfSignature(
pdf,
page,
new PdfCertificate(p12Path, "yourpassword"),
"电子签章"
);
// 设置签名位置(关键字右侧)
float signX = keywordX + 10;
signature.setBounds(new Rectangle2D.Float(signX, keywordY, 200, 100));
pdf.saveToFile(outputPath);
} catch (Exception e) {
e.printStackTrace();
}
}
private static List<float[]> locateKeyword(PdfDocument pdf, String keyword) {
List<float[]> locations = new ArrayList<>();
for (int i = 0; i < pdf.getPages().getCount(); i++) {
PdfPageBase page = pdf.getPages().get(i);
PdfTextFindCollection finds = page.findText(keyword);
for (PdfTextFind find : finds.getFinds()) {
Rectangle2D rect = find.getBounds();
locations.add(new float[]{i, (float) rect.getX(), (float) rect.getY()});
}
}
return locations;
}
}
2. 页面中心定位签章
// 获取页面中心位置
PdfPageBase page = pdf.getPages().get(0);
float pageWidth = page.getActualSize().getWidth();
float pageHeight = page.getActualSize().getHeight();
float centerX = pageWidth / 2;
float centerY = pageHeight / 2;
// 在中心位置签章
PdfSignature signature = new PdfSignature(pdf, page, cert, "中心签章");
signature.setBounds(new Rectangle2D.Float(
centerX - 100, // 宽度100,居中
centerY - 50, // 高度50,居中
200,
100
));
三、签章外观定制技巧
1. 添加图片签章
import javax.imageio.ImageIO;
// 加载签章图片
BufferedImage image = ImageIO.read(new File("signature.png"));
PdfImage pdfImage = PdfImage.fromImage(image);
// 在签名区域绘制图片
PdfAppearance appearance = signature.getAppearance();
appearance.getGraphics().drawImage(
pdfImage,
0, 0,
signature.getBounds().width,
signature.getBounds().height
);
2. 设置透明文字
// 50%透明的白色文字
signature.setSignFontColor(new PdfColor(255, 255, 255, 128));
signature.setSignFont(new Font("微软雅黑", Font.PLAIN, 12));
signature.setSignText("签署人:张三\n时间:2024-07-01");
3. 去除签名信息显示
// 方法一:不使用数字签名,直接绘制图片
PdfCanvas graphics = page.getCanvas();
graphics.drawImage(pdfImage, x, y, width, height);
// 方法二:创建极小的签名域
signature.setBounds(new Rectangle2D.Float(0, 0, 1, 1));
四、常见问题解决方案
1. OpenSSL 中文乱码问题
# 修改openssl.cnf配置
string_mask = utf8only
countryName_default = CN
stateOrProvinceName_default = area
localityName_default = city
0.organizationName_default = compary
# 生成请求证书(指定utf8编码)
openssl req -utf8 -config openssl_utf8.cnf -key server.key -new -out server.pem
2. 签名后文件变大问题
// 优化PDF保存参数
PdfSaveOptions options = new PdfSaveOptions();
options.setCompressionLevel(PdfCompressionLevel.Best);
pdf.saveToFile(outputPath, options);
3. 签章位置偏移问题
// 计算准确位置(考虑页面旋转)
PdfPageBase page = pdf.getPages().get(pageIndex);
float rotation = page.getRotation().getValue();
float adjustedX = rotation == 90 || rotation == 270 ? keywordY : keywordX;
float adjustedY = rotation == 90 || rotation == 270 ? keywordX : keywordY;
五、最佳实践建议
-
证书管理:
- 生产环境使用 CA 颁发的证书,测试环境可使用自签名证书
- 定期更新证书,确保安全性
-
性能优化:
- 批量签章时使用线程池提高效率
- 对大文件先压缩再处理
-
兼容性测试:
- 在主流 PDF 阅读器(Adobe、福昕、Chrome)中测试签章效果
- 确保不同版本的 PDF 文件(1.4/1.5/1.7)都能正常签章
-
安全增强:
- 添加时间戳服务确保签名时效性
- 对敏感文档使用多重签名验证