哼哼,完成這篇,這周的任務算是完成了,上篇文章主要生成了本身的證書與和密碼,這篇主要實現數字簽名及HTML轉成PDF功能。html
今天下午把功能基本完成了,常帥走過來講,你知道簽名的解籤的原理嗎?尼瑪...不清楚,cer,crt,pfx,csr..一大堆名稱,看過轉身就忘記了。。java
在弄功能時有幾個細節,調試了半天,第一個是將不標準的HTML轉成標準的HTML格式(就是必須是善始善終),否則itextpdf轉不了,報錯。 第二個是印章定位的問題,最後通過一遍遍調試,終於能將圖在指定的位置顯示了。node
主要功能以下apache
1,將HTML轉成PDFapp
2,對此PDF進行簽名maven
3,打印簽名。ide
一,HContent.java 常量類,定義圖片資源的位置 ui
package com.wowoyoo.helper; /** * @author hubs * @version 建立時間:2017年3月22日 下午3:34:56 * 類說明 */ public class Content { public static String BASE = "/home/hubs/桌面/ssl/ok/"; //文件存放路徑 /** The resulting PDF */ public static String PDF_NEW = BASE + "/test1.pdf"; // 建立的PDF /** The resulting PDF */ public static String PDF_SIGNED = BASE + "/signature_1.pdf"; // 簽名的文件 /** Info after verification of a signed PDF */ public static String PDF_VERIFICATION = BASE + "verify.txt"; // 簽證的信息,若是已更改,則值爲空 public static final String PFX_PATH = BASE + "client.pfx";// 證書,包含密鑰 public static final String PFX_PASS = "xxxxx"; // 這是簽名的密碼 public static final String STAMP_PATH = BASE + "test.gif"; // 簽名的圖 public static final String LOGO_PATH = BASE + "logo.png"; // 簽名的圖 public static final String CER_PATH = BASE + "client.crt"; // 解答的證書,公鑰 public static byte[] OPEN_PASSWORD = "hello".getBytes(); // 密碼 public static final String HTML_PATH = BASE + "test.html"; public static final String WORD_PATH = BASE + "word.doc"; }
二,HTML轉PDF類加密
package com.wowoyoo.helper; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import org.jsoup.Jsoup; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.PageSize; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.XMLWorkerHelper; /** * @author hubs * @version 建立時間:2017年3月22日 下午3:29:21 * 類說明"HTML轉PDF */ public class HtmlToPdf { public static void createPdf(String HTML,String outFileName) throws IOException,DocumentException { Document document = new Document(PageSize.A4); PdfWriter pdfWriter = PdfWriter.getInstance(document,new FileOutputStream(outFileName)); document.open(); document.addAuthor("蝸蝸遊旅行網"); document.addTitle("蝸蝸遊旅行網電子合同"); document.addCreator("蝸蝸遊旅行網"); document.addHeader("wowoyoo.com", "蝸蝸遊"); document.addKeywords("蝸蝸遊,電子合同 "); document.addSubject("蝸蝸遊旅行網電子合同"); // 標準化HTML代碼 org.jsoup.nodes.Document _doc = Jsoup.parse(new File(HTML), "UTF-8"); _doc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml); String _html = _doc.html(); //HTML轉PDF XMLWorkerHelper.getInstance().parseXHtml(pdfWriter, document,new ByteArrayInputStream(_html.getBytes()),Charset.forName("UTF-8"), new AsianFontProvider()); document.close(); } }
三,中文漢字類,主要是使用了itext-asian包調試
package com.wowoyoo.helper; import com.itextpdf.text.BaseColor; import com.itextpdf.text.Font; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.tool.xml.XMLWorkerFontProvider; /** * @author hubs * @version 建立時間:2017年3月21日 下午4:18:49 類說明 */ public class AsianFontProvider extends XMLWorkerFontProvider { public Font getFont(final String fontname, final String encoding, final boolean embedded, final float size, final int style, final BaseColor color) { BaseFont bf = null; try { bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } catch (Exception e) { e.printStackTrace(); } Font font = new Font(bf, size, style, color); font.setColor(color); return font; } }
四,簽名類
package com.wowoyoo.helper; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.PrintWriter; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import com.itextpdf.text.Image; import com.itextpdf.text.PageSize; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.PdfAnnotation; import com.itextpdf.text.pdf.PdfAppearance; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.text.pdf.security.BouncyCastleDigest; import com.itextpdf.text.pdf.security.CertificateInfo; import com.itextpdf.text.pdf.security.CertificateVerification; import com.itextpdf.text.pdf.security.ExternalDigest; import com.itextpdf.text.pdf.security.ExternalSignature; import com.itextpdf.text.pdf.security.MakeSignature; import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard; import com.itextpdf.text.pdf.security.PdfPKCS7; import com.itextpdf.text.pdf.security.PrivateKeySignature; import com.itextpdf.text.pdf.security.VerificationException; /** * @author hubs * @version 建立時間:2017年3月22日 下午3:39:42 * 類說明 */ public class Sign { //對PDF進行簽名 public static void signPdf(String src, String dest) throws Exception { KeyStore ks = KeyStore.getInstance("pkcs12", "BC"); //證書與證書密碼(不要問我是什麼意思,個人直接copy網上的....) ks.load(new FileInputStream(Content.PFX_PATH),Content.PFX_PASS.toCharArray()); String alias = (String) ks.aliases().nextElement(); PrivateKey pk = (PrivateKey) ks.getKey(alias, Content.PFX_PASS.toCharArray()); Certificate[] chain = ks.getCertificateChain(alias); //讀取須要簽名的PDF源文件 PdfReader reader = new PdfReader(src); //寫入目標文件 FileOutputStream os = new FileOutputStream(dest); PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0'); // 加密,只充許打印 stamper.setEncryption(Content.OPEN_PASSWORD, null, PdfWriter.ALLOW_PRINTING,PdfWriter.ENCRYPTION_AES_128| PdfWriter.DO_NOT_ENCRYPT_METADATA); int qtyPages = reader.getNumberOfPages(); //這個是在每一頁的印章(除最後一頁) Image _image = Image.getInstance(Content.LOGO_PATH); //這個寬度是圖片定位用的 float _width = PageSize.A4.getWidth() - _image.getScaledWidth(); //設置爲A4紙大小 Rectangle _rect = PageSize.A4; Rectangle _cell = new Rectangle(_rect); //除了最後一頁,其餘每頁都蓋章 for (int i = 1; i < qtyPages; i++) { PdfAnnotation stp = PdfAnnotation.createStamp(stamper.getWriter(),_cell, "蝸蝸遊旅行網", "wowoyoo.com");// 每一頁加標籤 PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(), _rect.getWidth(), _rect.getHeight()); _image.setAbsolutePosition(_width - 50, 50);// X:座標,Y:右下角座標 tp.addImage(_image, true); stp.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp); stamper.addAnnotation(stp, i); } // 最後一個界面加印章 Image _last_image = Image.getInstance(Content.STAMP_PATH); PdfAnnotation stp = PdfAnnotation.createStamp(stamper.getWriter(),_cell, "蝸蝸遊旅行網", "wowoyoo.com");// 每一頁加標籤 PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(),_rect.getWidth(), _rect.getHeight()); _last_image.setAbsolutePosition(_width - 150, 200);// X:座標,Y:右下角座標,這裏本身調位置 tp.addImage(_last_image, true); stp.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp); stamper.addAnnotation(stp, qtyPages); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC"); ExternalDigest digest = new BouncyCastleDigest(); MakeSignature.signDetached(appearance, digest, es, chain, null, null,null, 0, CryptoStandard.CMS); } //簽證簽名(這裏暫時只打印出信息,不做判斷) /** * 打印結果以下: * Signature name: Signature1 * Signature covers whole document: true *Document revision: 1 of 1 *Subject: {E=[273579540@qq.com], ST=[guanxi], C=[zh], L=[guilin], OU=[wowoyoo], O=[wowoyoo], CN=[wowoyoo]} * Revision modified: false * Certificates verified against the KeyStore * @throws Exception */ public static void verifySignatures() throws Exception { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); Certificate cert1 = getPublicCertificate(); ks.setCertificateEntry("cacert", cert1); PrintWriter out = new PrintWriter(new FileOutputStream(Content.PDF_VERIFICATION)); PdfReader reader = new PdfReader(Content.PDF_SIGNED, Content.OPEN_PASSWORD);//這裏要加上簽名的密碼 AcroFields af = reader.getAcroFields(); ArrayListnames = af.getSignatureNames(); for (String name : names) { out.println("Signature name: " + name); out.println("Signature covers whole document: "+ af.signatureCoversWholeDocument(name)); out.println("Document revision: " + af.getRevision(name) + " of "+ af.getTotalRevisions()); PdfPKCS7 pk = af.verifySignature(name); Calendar cal = pk.getSignDate(); Certificate[] pkc = pk.getCertificates(); out.println("Subject: "+ CertificateInfo.getSubjectFields(pk.getSigningCertificate())); out.println("Revision modified: " + !pk.verify()); Listerrors = CertificateVerification.verifyCertificates(pkc, ks, null, cal); if (errors.size() == 0) out.println("Certificates verified against the KeyStore"); else out.println(errors); } out.flush(); out.close(); } //公鑰證書 private static Certificate getPublicCertificate() throws Exception { FileInputStream is = new FileInputStream(Content.CER_PATH); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(is); return cert; } }
五,直接調用
package com.wowoyoo.main; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; import com.wowoyoo.helper.Content; import com.wowoyoo.helper.HtmlToPdf; import com.wowoyoo.helper.Sign; /** * @author hubs * @version 建立時間:2017年3月20日 下午6:03:10 類說明 */ public class Main { public static void main(String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); HtmlToPdf.createPdf(Content.HTML_PATH, Content.PDF_NEW); Sign.signPdf(Content.PDF_NEW, Content.PDF_SIGNED); Sign.verifySignatures(); } }
上圖:
依賴類
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>wowoyoo</groupId> <artifactId>ConverPdf</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.10</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.56</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-pdfa</artifactId> <version>5.5.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk16 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-ext-jdk16</artifactId> <version>1.46</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-xtra</artifactId> <version>5.5.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcmail-jdk16 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcmail-jdk16</artifactId> <version>1.46</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bouncycastle/bctsp-jdk16 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bctsp-jdk16</artifactId> <version>1.46</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-scratchpad --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>3.16-beta2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.7-beta3</version> </dependency> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.10</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.18</version> </dependency> </dependencies> </project>