如何给pdf文件签名,这样pdf文件就具有不可修改性,具有鉴权、完整性、不可抵赖。
需要一个印章图片和证书文件。
可以通过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) {
}
}
}
}
}
编写测试用例生成图片
@Test
void createSignImage() {
ImageCreateUtils.createSignImage("王二狗", "海港城纵横科技有限公司", "2050.09.05", 250, 100, "D:\\test3\\wangergou.jpg");
System.out.println("-------------构建印章结束---------------------");
}
这里的证书是从阿里云下载获取的,你可以通过其它方式获取证书。
请见keytool工具生成JKS证书
需要的依赖
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itextpdfartifactId>
<version>5.5.13.3version>
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);
}
}
编写测试用例
@Test
void 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");