使用itext7进行pdf签名印章
🧀前言
-
需要准备ca证书的.keystore文件,测试的话一般可以用jdk进行生成
使用cmd命令切换到jdk安装路径bin目录下,输入命令 keytool -genkeypair -alias xh_https -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore C:/Users/86185/Desktop/html-pdf/resource/xh_https.keystore -storepass 123456 alias是别名,keypass与storepass是密码,keyalg是密钥算法,keysize是密钥大小,validity是有效期,-keystore是路径加名称 回车后会要求输入姓名、组织单位、城市区域等,可以自定义输入,输入完成后则在-keystore指定位置生成.keystore文件
- 相关依赖导入pom文件
<!-- itext依赖 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>sign</artifactId> <version>7.1.16</version> </dependency> <!-- 加密软件包 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.69</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.69</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
一、签名实现类
package cn.cxyfyf.base.test.pdf.itext.config;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.source.RASInputStream;
import com.itextpdf.io.source.RandomAccessFileOrArray;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.layout.element.Image;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* pdf 签名工具类
* @author fyf
* Create by 2023/1/28 11:30
*/
public class SignUtils {
private static final PdfSignDTO DEFAULT_SIGN_INFO = new PdfSignDTO();
private static String KEYSTORE = "C:\\Users\\86185\\Desktop\\html-pdf\\resource\\tomcat_https.keystore";// keystore文件路径
private static final char[] PASSWORD = "123456".toCharArray();// keystore密码
static {
DEFAULT_SIGN_INFO.setReason("系统自动签章");
DEFAULT_SIGN_INFO.setLocation("Windows");
DEFAULT_SIGN_INFO.setContactInfo("123456");
DEFAULT_SIGN_INFO.setSignImgPath("C:\\Users\\86185\\Desktop\\html-pdf\\resource\\sign.png"); //印章图片路径
}
/**
* 电子签章
* @param inputStream 需要签章的pdf文件输入流
* @param outputStream 签完章的pdf文件输出流
*/
public static void signPdf(InputStream inputStream, OutputStream outputStream){
signPdf(inputStream, outputStream, DEFAULT_SIGN_INFO);
}
/**
* 电子签章
* @param inputStream 需要签章的pdf文件输入流
* @param outputStream 签完章的pdf文件输出流
* @param pdfSignDTO 签名信息封装类
*/
public static void signPdf(InputStream inputStream, OutputStream outputStream, PdfSignDTO pdfSignDTO) {
try (PdfReader reader = new PdfReader(inputStream);){
//读取keystore ,获得私钥和证书链 jks
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(KEYSTORE), PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
Certificate[] certificates = ks.getCertificateChain(alias);
//创建签章工具PdfSigner、设定数字签章的属性
PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
// 读取文档相关属性
PdfDocument document = signer.getDocument();
appearance.setReason(pdfSignDTO.getReason());
appearance.setLocation(pdfSignDTO.getLocation());
appearance.setContact(pdfSignDTO.getContactInfo());
//加盖图章图片
ImageData img = ImageDataFactory.create(pdfSignDTO.getSignImgPath());
Image image = new Image(img);
appearance.setPageNumber(document.getNumberOfPages()); // 签名放到第几页
appearance.setPageRect(new Rectangle(document.getLastPage().getPageSize().getWidth() - image.getImageWidth() - 50, 100, image.getImageWidth(), image.getImageHeight()));
appearance.setSignatureGraphic(img);
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
//No such provider: BC : 问题解决,加BC库支持
Security.addProvider(new BouncyCastleProvider());
//摘要算法
IExternalDigest digest = new BouncyCastleDigest();
//签名算法
IExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, BouncyCastleProvider.PROVIDER_NAME);
//调用itext签名方法完成pdf签章
signer.setCertificationLevel(1); // 设置认证级别
signer.signDetached(digest,signature, certificates, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 验签pdf
* @param bytes 签名好的pdf字节数组
* @return 验签结果 true/false
*/
public static boolean verifyPdf(byte[] bytes){
Objects.requireNonNull(bytes);
return verifyPdf(new ByteArrayInputStream(bytes));
}
/**
* 验签pdf
* @param inputStream 签名好的pdf输入流
* @return 验签结果 true/false
*/
public static boolean verifyPdf(InputStream inputStream) {
Objects.requireNonNull(inputStream);
boolean result = false;
try(PdfReader pdfReader = new PdfReader(inputStream);PdfDocument pdfDocument = new PdfDocument(pdfReader);) {
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
List<String> signedNames = signatureUtil.getSignatureNames();
//遍历签名的内容并做验签
for (String signedName : signedNames) {
//获取源数据
byte[] originData = getOriginData(pdfReader, signatureUtil, signedName);
//获取签名值
byte[] signedData = getSignData(signatureUtil , signedName);
//校验签名
// result = SignUtil.verifyP7DetachData(originData , signedData);
if (!result) break;
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 获取签名数据
* @param signatureUtil itextpdf signatureUtil
* @param signedName itextpdf signedName
*/
private static byte[] getSignData(SignatureUtil signatureUtil, String signedName) {
PdfDictionary pdfDictionary = signatureUtil.getSignatureDictionary(signedName);
PdfString contents = pdfDictionary.getAsString(PdfName.Contents);
return contents.getValueBytes();
}
/**
* 获取源数据(如果subFilter使用的是Adbe.pkcs7.detached就需要在验签的时候获取 源数据 并与 签名数据 进行 p7detach 校验)
* @param pdfReader itextpdf pdfReader
* @param signatureUtil itextpdf signatureUtil
* @param signedName itextpdf signedName
*/
private static byte[] getOriginData(PdfReader pdfReader, SignatureUtil signatureUtil, String signedName) {
byte[] originData = null;
PdfSignature pdfSignature = signatureUtil.getSignature(signedName);
PdfArray pdfArray = pdfSignature.getByteRange();
RandomAccessFileOrArray randomAccessFileOrArray = pdfReader.getSafeFile();
try(InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(randomAccessFileOrArray.createSourceView(), SignatureUtil.asLongArray(pdfArray)));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
byte[] buf = new byte[8192];
int n = 0;
while (-1 != (n = rg.read(buf))) {
outputStream.write(buf, 0, n);
}
originData = outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return originData;
}
/**
* 解析返回签名信息
* @param buf pdf字节数组
* @return 签名信息列表
*/
public static List<PdfSignDTO> getPdfSignInfo(byte[] buf) {
Objects.requireNonNull(buf);
return getPdfSignInfo(new ByteArrayInputStream(buf));
}
/**
* 解析返回签名信息
* @param inputStream pdf输入流
* @return 签名信息列表
*/
public static List<PdfSignDTO> getPdfSignInfo(InputStream inputStream) {
//添加BC库支持
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
List<PdfSignDTO> signInfoList = new ArrayList< >();
try(PdfReader pdfReader = new PdfReader(inputStream);
PdfDocument pdfDocument = new PdfDocument(pdfReader);) {
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
List<String> signedNames = signatureUtil.getSignatureNames();
//遍历签名信息
for (String signedName: signedNames) {
PdfSignDTO pdfSignInfo = new PdfSignDTO();
pdfSignInfo.setSignatureName(signedName);
pdfSignInfo.setRevisionNumber(signatureUtil.getRevision(signedName));
PdfPKCS7 pdfPKCS7 = signatureUtil.readSignatureData(signedName);
pdfSignInfo.setSignDate(pdfPKCS7.getSignDate().getTime());
pdfSignInfo.setDigestAlgorithm(pdfPKCS7.getDigestAlgorithm());
pdfSignInfo.setLocation(pdfPKCS7.getLocation());
pdfSignInfo.setReason(pdfPKCS7.getReason());
pdfSignInfo.setEncryptionAlgorithm(pdfPKCS7.getEncryptionAlgorithm());
X509Certificate signCert = pdfPKCS7.getSigningCertificate();
pdfSignInfo.setSignerName(CertificateInfo.getSubjectFields(signCert).getField("CN"));
PdfDictionary sigDict = signatureUtil.getSignatureDictionary(signedName);
PdfString contactInfo = sigDict.getAsString(PdfName.ContactInfo);
if (contactInfo != null) {
pdfSignInfo.setContactInfo(contactInfo.toString());
}
signInfoList.add(pdfSignInfo);
}
} catch(IOException e) {
e.printStackTrace();
}
return signInfoList;
}
}
签名信息封装实体类
package cn.cxyfyf.base.test.pdf.itext.config;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 获取签名信息实体类
*/
@Data
public class PdfSignDTO implements Serializable {
private static final long serialVersionUID = 2098635697074981376L;
/** 签名时间 */
private Date signDate;
/** 摘要算法 */
private String digestAlgorithm;
/** 原因 */
private String reason;
/** 地点 */
private String location;
/** 签名 */
private String signatureName;
/** 加密算法 */
private String encryptionAlgorithm;
/** 签名者 */
private String signerName;
/** 联系方式 */
private String contactInfo;
/** 修订号 */
private int revisionNumber;
/** 签名图片地址 */
private String signImgPath;
}
二、相关测试类
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis();
// pdf文件绝对路径
String pdfFile = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshi.pdf";
// 签名后Pdf文件要储存的绝对路径
String signPdf = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshisign2.pdf";
try(
InputStream signInputStream = new FileInputStream(pdfFile);
OutputStream singOutputStream = new FileOutputStream(signPdf);
) {
// 电子签名
SignUtils.signPdf(signInputStream, singOutputStream);
// 获取签名信息
List<PdfSignDTO> pdfSignInfo = SignUtils.getPdfSignInfo(new FileInputStream(pdfFile));
for (PdfSignDTO pdfSignDTO : pdfSignInfo) {
System.out.println(pdfSignDTO.toString());
}
// SignUtils.verifyPdf(signInputStream);
}
log.info("转换结束,耗时:{}ms",System.currentTimeMillis()-startTime);
}
本文作者:玩单机的零度
本文链接:https://www.cnblogs.com/cxyfyf/p/17089037.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步