在pdf上加蓋電子簽章,并不是只是加個印章圖片,。而是要使用一對密鑰中的私鑰對文件進行簽字。為啥要用私鑰呢?很簡單,因為公鑰是公開的,其他人才可以用公鑰為你證明,這個文件是你簽的。這就是我們常說的:私鑰簽字,公鑰加密。用公鑰進行加密,才只有有對應私鑰的人能解開。
下面給出具體代碼。
另外,這對密鑰應該去官方申請,我這里只是測試,所以就自己生成了。
數字證書常見標準
- 符合PKI ITU-T X509標準,傳統標準(.DER .PEM .CER .CRT)
- 符合PKCS#7 加密消息語法標準(.P7B .P7C .SPC .P7R)
- 符合PKCS#10 證書請求標準(.p10)
- 符合PKCS#12 個人信息交換標準(.pfx *.p12)X509是數字證書的基本規范,而P7和P12則是兩個實現規范,P7用于數字信封,P12則是帶有私鑰的證書實現規范。
生成p12數字證書文件
使用JDK的keytool工具
- keytool在jdk的bin目錄下
- 生成數字文件
keytool -genkeypair -alias serverkey -keypass 111111 -storepass 111111 -dname "C=CN,ST=SD,L=QD,O=haier,OU=dev,CN=haier.com" -keyalg RSA -keysize 2048 -validity 3650 -keystore D:keystoreserver.keystore
storepass keystore 文件存儲密碼
keypass 私鑰加解密密碼
alias 實體別名(包括證書私鑰)
dname 證書個人信息
keyalt 采用公鑰算法,默認是DSA keysize 密鑰長度(DSA算法對應的默認算法是sha1withDSA,不支持2048長度,此時需指定RSA)
validity 有效期
keystore 指定keystore文件
3,轉換為p12格式
keytool -importkeystore -srckeystore D:keystoreserver.keystore -destkeystore D:keystoreserver.p12 -srcalias serverkey -destalias serverkey -srcstoretype jks -deststoretype pkcs12 -srcstorepass 111111 -deststorepass 111111 -noprompt
使用IText對pdf文件進行數字簽名
public static final String sourceFolder = "./src/test/resources/com/itextpdf/signatures/sign/SigningTest/";
public static final String destinationFolder = "./target/test/com/itextpdf/signatures/sign/SigningTest/";
public static final String keystorePath = "D:\keystore\server.p12";
public static final char[] password = "111111".toCharArray();
public static final String stamperSrc = "./src/test/resources/seal.png";//印章路徑
private Certificate[] chain; // 證書鏈
private PrivateKey pk;
@BeforeClass
public static void before() {
Security.addProvider(new BouncyCastleProvider());
createOrClearDestinationFolder(destinationFolder);
}
@Before
public void init() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
pk = Pkcs12FileHelper.readFirstKey(keystorePath, password, password);
chain = Pkcs12FileHelper.readFirstChain(keystorePath, password);
}
@Test
public void testSign() {
String src = sourceFolder + "simpleDocument.pdf";
String fileName = "dest.pdf";
String dest = destinationFolder + fileName;
try {
ImageData img = ImageDataFactory.create(stamperSrc);
//讀取圖章圖片,這個image是itext包的image
Image image = new Image(img);
float height = image.getImageHeight();
float width = image.getImageWidth();
Rectangle rectangle = new Rectangle(150, 200, width, height);
int pageNum = 1;
sign(src, String.format(dest, 1), img, pageNum, rectangle, chain, pk, DigestAlgorithms.SHA256, null, PdfSigner.CryptoStandard.CADES, "測試",
"青島");
} catch (Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage());
e.printStackTrace();
}
}
public void sign(String src //需要簽章的pdf文件路徑
, String dest // 簽完章的pdf文件路徑
, ImageData img // 印章圖片
, int pageNum // 印在第幾頁
, Rectangle rectangle // 印章顯示的位置
, Certificate[] chain //證書鏈
, PrivateKey pk //簽名私鑰
, String digestAlgorithm //摘要算法名稱,例如SHA-1
, String provider // 密鑰算法提供者,可以為null
, PdfSigner.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可以被追加簽名,驗簽工具可以識別出每次簽名之后文檔是否被修改
PdfReader reader2 = new PdfReader(src);
// PdfSigner stamper = new PdfSigner(reader2, os, true);
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.useAppendMode();
PdfSigner stamper = new PdfSigner(reader2, os, stampingProperties);
// 獲取數字簽章屬性對象,設定數字簽章的屬性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
//設置簽名的位置,頁碼,簽名域名稱,多次追加簽名的時候,簽名與名稱不能一樣
//簽名的位置,是圖章相對于pdf頁面的位置坐標,原點為pdf頁面左下角
//四個參數的分別是,圖章左下角x,圖章左下角y,圖章寬度,圖章高度
appearance.setPageNumber(pageNum);
appearance.setPageRect(rectangle);
//插入蓋章圖片
appearance.setSignatureGraphic(img);
//設置圖章的顯示方式,如下選擇的是只顯示圖章(還有其他的模式,可以圖章和簽名描述一同顯示)
appearance.setRenderingMode(PdfSignatureAppearance.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);
}
效果如下:
可以查看下證書,會看到我們生成數字證書時的信息
需要源碼請關注公-眾-號: 技術筆記與開源分享