文章目录
- 给pdf文件签名
- 一、文件准备
- 1. 构建印章
- 2. 获取证书
- 方法一 阿里云申请证书
- 方法二 自建证书
- 二、利用证书给pdf签名
- 三、设定签名位置
- 在指定坐标签名
- 在指定签名域签名
- 传送门
给pdf文件签名
如何给pdf文件签名,这样pdf文件就具有不可修改性,具有鉴权、完整性、不可抵赖。
一、文件准备
需要一个印章图片和证书文件。
1. 构建印章
可以通过ps或者其它方式自由构建一张透明底的图片印章或者用户手写的签名。
这里为了方便,直接使用代码生成一张方形印章。
import sun.font.FontDesignMetrics;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/*** 图片生成*/
public class ImageCreateUtils {/*** @param username* @param companyName* @param date* @param width* @param height* @param picname* @return*/public static boolean createSignImage(String username, //String companyName, //String date,int width,int height,String picname) {FileOutputStream out = null;//背景色Color bgcolor = Color.WHITE;//字色Color fontcolor = Color.RED;Font userNameFont = new Font(null, Font.BOLD, 20);Font companyNameFont = new Font(null, Font.BOLD, 18);try { // 宽度 高度BufferedImage bimage = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);Graphics2D g = bimage.createGraphics();g.setColor(bgcolor); // 背景色g.fillRect(0, 0, width, height); // 画一个矩形g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); // 去除锯齿(当设置的字体过大的时候,会出现锯齿)g.setColor(Color.RED);g.fillRect(0, 0, 8, height);g.fillRect(0, 0, width, 8);g.fillRect(0, height - 8, width, height);g.fillRect(width - 8, 0, width, height);g.setColor(fontcolor); // 字的颜色g.setFont(userNameFont); // 字体字形字号FontMetrics fm = FontDesignMetrics.getMetrics(userNameFont);int font1_Hight = fm.getHeight();int strWidth = fm.stringWidth(username);int y = 35;int x = (width - strWidth) / 2;g.drawString(username, x, y); // 在指定坐标除添加文字g.setFont(companyNameFont); // 字体字形字号fm = FontDesignMetrics.getMetrics(companyNameFont);int font2_Hight = fm.getHeight();strWidth = fm.stringWidth(companyName);x = (width - strWidth) / 2;g.drawString(companyName, x, y + font1_Hight); // 在指定坐标除添加文字strWidth = fm.stringWidth(date);x = (width - strWidth) / 2;g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐标除添加文字g.dispose();ImageIO.write(bimage, picname.substring(picname.lastIndexOf(".") + 1), new File(picname));out.flush();return true;} catch (Exception e) {return false;} finally {if (out != null) {try {out.close();} catch (IOException e) {}}}}
}
编写测试用例生成图片
@Testvoid createSignImage() {ImageCreateUtils.createSignImage("王二狗", "海港城纵横科技有限公司", "2050.09.05", 250, 100, "D:\\test3\\wangergou.jpg");System.out.println("-------------构建印章结束---------------------");}
运行测试用例
2. 获取证书
方法一 阿里云申请证书
这里的证书是从阿里云下载获取的,你可以通过其它方式获取证书。
方法二 自建证书
请见keytool工具生成JKS证书
二、利用证书给pdf签名
需要的依赖
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13.3</version></dependency>
签名工具
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;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");
// appearance.setVisibleSignature("sign2");/*** 用于盖章的印章图片,引包的时候要引入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);}
}
编写测试用例
@Testvoid addSign() {try {String KEYSTORE = "D:\\test3\\cert2\\www.xxx.space.jks";//证书文件char[] PASSWORD = "0t5uiwai".toCharArray();//证书密码String IMG = "D:\\test3\\wangergou.jpg";//用户的签名String SRC = "D:\\test3\\test1.pdf"; //待签文件String OUTPUT_SRC = "D:\\test3\\test1_sign123.pdf";//签名输出的文件//读取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, "文件已签名", "Beijing");System.out.println("--------------签名完成---------------");} catch (Exception e) {JOptionPane.showMessageDialog(null, e.getMessage());e.printStackTrace();}}
执行测试用例生成签名后文件
三、设定签名位置
在指定坐标签名
/*** 1 三个参数依次为:设置签名的位置、页码、签名域名称,多次追加签名的时候,签名域名称不能一样* 1.1 签名的位置四个参数:印章左下角的X、Y轴坐标,印章右上角的X、Y轴坐标,* 这个位置是相对于PDF页面的位置坐标,即该坐标距PDF当前页左下角的坐标*/appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "sign");
该方法不需要提前在pdf文件设定签名域,直接根据坐标位置签名
在指定签名域签名
编辑pdf模板,使用pdf软件,编辑表单,在需要的位置添加数字签名。
这里设定的数字签名的签名域为sign2
代码中在此位置签名,由于文件已经设定了数字签名的位置,所以不需要指定坐标了。
appearance.setVisibleSignature("sign2");
再次执行签名测试用例。
传送门
Pdf文件签名检查