一开始,我使用的是itext2.1.7,但是会出现中文不能显示问题,解决方案:下载另一个包extrajars-2.3.zip,此包中的itext-asian.jar可以帮助解决中文乱码问题,若再遇到STSong-Light' with 'UniGB-UCS2-H' is not recognized问题,可以点击链接进行解决。如若再无解决,请放弃itext2.1.7直接用itext7吧,itext7中自带解决中文显示问题。具体如下:
一、使用的工具
2.运行环境:jdk1.7
3.itext包 :itext7.0.2
3.相关jar包:bcprov-jdk15on-157.jar,bcpkix-jdk15on-157.jar (这两个一定要有,不然会报错,其他包看情况而定,参考链接里面JDK 1.5 - JDK 1.8)
二、生成test.keystore文件
生成方法请参考博客《使用Keytool工具生成证书Keystore和证书签名请求文件》
名字可以随便取):
三、代码前准备
1.先把生成好的 test.keystore文件放入 jdk1.7\bin目录下
2.把 bcprov-jdk15on-157.jar放入 jdk1.7\jre\lib\ext目录下
3.配置安全属性文件:在 jdk1.7\jre\lib\security目录下有个java.security文件,用它定义了当前可以使用的加密提供者。如看到下面的语句:
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
它表明本虚拟机有两个加密提供者以及他们的优先级和访问时使用的名称。当需要用到一个加密算法时,虚拟机会依次访问这里列出的提供者,寻找想要的算法,
并按这里的优先级顺序使用第一个找到的算法。我们应该在文件中插入新的提供者例如:
security.provider.3=org.bouncycastle.jce.provider.BouncyCastleProvider
例如下图:
4.先创建一个java工程,导入jar包,如下图(红色框内为后面运行时报出异常后添加的包)
四、代码编写
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import javax.swing.JOptionPane;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.layout.element.Image;
import com.itextpdf.signatures.BouncyCastleDigest;
import com.itextpdf.signatures.DigestAlgorithms;
import com.itextpdf.signatures.IExternalDigest;
import com.itextpdf.signatures.IExternalSignature;
import com.itextpdf.signatures.PdfSignatureAppearance;
import com.itextpdf.signatures.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.signatures.PdfSigner;
import com.itextpdf.signatures.PdfSigner.CryptoStandard;
import com.itextpdf.signatures.PrivateKeySignature;
public class SignPDF {
public static final String KEYSTORE = "E:\\Program Files\\jdk1.7\\bin\\test.keystore";//keystore文件路径
public static final char[] PASSWORD = "123456".toCharArray(); // keystore密码
public static final String SRC = "C:\\Users\\xxx\\Desktop\\欢迎信.pdf";//需要盖章的pdf文件路径
public static final String DEST = "C:\\Users\\xxx\\Desktop\\欢迎信2.pdf";//盖章后生产的pdf文件路径
public static final String stamperSrc = "C:\\Users\\xxx\\Desktop\\self_study\\stamper\\stamper7.png";//印章路径
public void sign(String src //需要签章的pdf文件路径
, String dest // 签完章的pdf文件路径
, Certificate[] chain //证书链
, PrivateKey pk //签名私钥
, String digestAlgorithm //摘要算法名称,例如SHA-1
, String provider // 密钥算法提供者,可以为null
, CryptoStandard subfilter //数字签名格式,itext有2种
, String reason //签名的原因,显示在pdf签名属性中,随便填
, String location) //签名的地点,显示在pdf签名属性中,随便填
throws GeneralSecurityException, IOException {
//下边的步骤都是固定的,照着写就行了,没啥要解释的
PdfReader reader = new PdfReader(src);
PdfDocument document = new PdfDocument(reader);
document.setDefaultPageSize(PageSize.TABLOID);
//目标文件输出流
FileOutputStream os = new FileOutputStream(dest);
//创建签章工具PdfSigner ,最后一个boolean参数
//false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
//true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
PdfSigner stamper = new PdfSigner(reader, os, true);
// 获取数字签章属性对象,设定数字签章的属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
ImageData img = ImageDataFactory.create(stamperSrc);
//读取图章图片,这个image是itext包的image
Image image = new Image(img);
float height = image.getImageHeight();
float width = image.getImageWidth();
//设置签名的位置,页码,签名域名称,多次追加签名的时候,签名与名称不能一样
//签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
//四个参数的分别是,图章左下角x,图章左下角y,图章宽度,图章高度
appearance.setPageNumber(1);
appearance.setPageRect(new Rectangle(350, 100, width, height));
//插入盖章图片
appearance.setSignatureGraphic(img);
//设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
appearance.setRenderingMode(RenderingMode.GRAPHIC);
// 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
// 摘要算法
IExternalDigest digest = new BouncyCastleDigest();
// 签名算法
IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
// 调用itext签名方法完成pdf签章
stamper.setCertificationLevel(1);
stamper.signDetached(digest,signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CADES);
}
public static void main(String[] args) {
try {
// 读取keystore ,获得私钥和证书链 jks
KeyStore ks = KeyStore.getInstance("jks");
ks.load(new FileInputStream(KEYSTORE), PASSWORD);
String alias = (String) ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
Certificate[] chain = ks.getCertificateChain(alias);
// new一个上边自定义的方法对象,调用签名方法
SignPDF app = new SignPDF();
app.sign(SRC, String.format(DEST, 1), chain, pk, DigestAlgorithms.SHA256, null, CryptoStandard.CADES, "Test 1",
"Ghent");
} catch (Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage());
e.printStackTrace();
}
}
}
五,生成后的结果如下图所示
要盖章的文档必须是通过itext已经生成完后的文档,或者盖章的同时生成文档,不然章盖不上去!!
如下图: