目錄
-
- 一、OFD 簡介
-
- 1.1 什么是 OFD?
- 1.2 什么是 版式文檔?
- 1.3 為什么要用 OFD 而不是PDF?
- 二、ofdrw 簡介
-
- 2.1 定義
- 2.2 Maven 依賴
- 2.3 ofdrw 的 13 個模塊
- 三、PDF/文本/圖片 轉 OFD(ofdrw-conterver)
-
- 3.1 介紹:
- 3.2 Maven 依賴:
- 3.3 PDF轉換OFD
- 3.5 文本轉換OFD
- 3.6 圖片轉換OFD
- 四、OFD 轉 圖片/HTML/文本/PDF(ofdrw-conterver)
-
- 4.1 介紹:
- 4.2 Maven 依賴:
- 4.3 導出為圖片
- 4.4 導出為SVG圖形
- 4.5 導出為HTML網頁
- 4.6 導出為文本
- 4.7 [不推薦] 導出為PDF
- 五、OFD簽署
-
- 5.1 在線生成 sm2 證書
- 5.2 制作 esl 印章
- 5.3 坐標簽署OFD
- 5.4 騎縫簽署OFD
- 5.5 驗簽 OFD
- OFDRW-Gitee地址: https://gitee.com/ofdrw/ofdrw
- OFDRW-GitHub地址: https://github.com/ofdrw/ofdrw
- gmhelper-GitHub地址: https://github.com/ZZMarquis/gmhelper
一、OFD 簡介
1.1 什么是 OFD?
OFD
是開放版式文檔(Open Fixed-layout Document)
的英文縮寫,是我國國家版式文檔格式標準——《GB/T 33190-2016電子文件存儲與交換格式-版式文檔》。
1.2 什么是 版式文檔?
版式文檔
是與Word(doc、docx)
等流式文件
相對的,具有格式獨立、版本固定、固化呈現的文檔。版式文檔不宜修改,且在不同設備中顯示效果不變,而 流式文檔會根據設備版面顯示發生變化。
舉例來說:
一個 doc 格式的 Word 文檔,使用 Word 與 WPS 打開,容易發生版面(樣式)變化、內容重排現象,同一篇 Word 文檔,在 Office 的不同版本中打開也會發生不一致的情況。而版式文檔則是不受設備影響,版式固定。在版式、版面、字體、字號等方面與紙質文檔保持完全一致。版式文檔格式的特點使它稱為 嚴肅類電子文檔 發布、數字化信息傳播和存檔的理想文檔格式。
版式文檔的代表就是我們工作和生活中非常熟悉的 PDF
文檔,OFD
文檔則是 我國自主研發,自主指定的版式文件格式標準。在 PDF基礎上加入了許多基于我國社會發展需要的應用場景功能。可以說 OFD 與 PDF 定位一致,同為版式文檔格式,但 OFD 后發制人,青出于藍。
1.3 為什么要用 OFD 而不是PDF?
既然 PDF 和 OFD 都是版式文件,那么問題來了,PDF 用得好好的,我們為什么要自己再做一個 OFD 的文件格式呢?
主要出于以下幾點原因:
-
格式不統一: 目前在國內可使用的版式文件格式包括
PDF
、CEB
等在內有不下十種,來自不同廠商和不同的技術,沒有統一標準,就會導致不同機構、不同企業之間的文件交流存在阻礙,文件長期存檔很困難。 -
難以自主可控: 我們知道,在目前辦公文檔市場上,來自 Adobe 的 PDF 五一占據絕對主流,而在 PDF 閱讀工具的市場上,Adobe 旗下的
Adobe Acrobat DC
又以 55.18% 的市占率位于領先地位,國內廠商 Foxit福昕
(昕,xin,一聲)雖然排名第二,但占有率僅有 1.92%。這背后有一個嚴重的問題,就是在版式文件技術上,我們目前難以做到自主可控,如果我們相對文件做一些針對國內特殊領域的技術擴展時,極容易受制于外部廠商,或者如果未來 Adobe 停止技術授權,那可能有很多文檔遭受損失。
此外,OFD 作為我們自主研發、自主可控的版式文件格式,相比 PDF 等其他版式文件,OFD 有一些技術上的優勢:
- 第一:OFD 文檔內部采用可擴展標記語言
XML
來描述數據和結構,體積精簡,安全開放,易于擴展。 - 第二:OFD 支持國產加密算法,具有全面的安全保障體系,可防止信息被竊取,并且和數字簽名技術集合,可防篡改,更加安全。
- 第三:永久刻度可用,可對文件長久保存,且可以精準呈現,文件的版式內容在不同場景、設備下都能保持一致性。
- 第四:支持直接進行文件歸檔的一系列處理。
這些優勢總結起來,就是:在 PDF 基礎上加入了許多基于我國社會發展需要的應用場景功能。
二、ofdrw 簡介
2.1 定義
ofdrw
,全稱OFD Reader & Writer
,意為 OFD 讀寫器,是開源的 OFD 處理庫,支持 文檔生成、數字簽名、文檔保護、文檔合并、轉換、導出 等功能。
Gitee地址: https://gitee.com/ofdrw/ofdrw
GitHub地址: https://github.com/ofdrw/ofdrw
2.2 Maven 依賴
Maven 依賴如下:
<!-- ofdrw -->
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-full</artifactId><version>2.3.3</version>
</dependency>
2.3 ofdrw 的 13 個模塊
- ofdrw-core OFD核心API,參考《GB/T 33190-2016 電子文件存儲與交換格式版式文檔》實現的基礎數據結構。
- ofdrw-font 生成OFD字體相關。
- ofdrw-layout OFD布局引擎庫,用于文檔構建和渲染。
- ofdrw-pkg OFD文件的容器,用于文檔的打包。
- ofdrw-reader OFD文檔解析器,用于OFD的反序列化以及簽名簽章。
- ofdrw-sign OFD文檔數字簽章。
- ofdrw-gm 用于支持簽章模塊需要的國密電子簽章數據結構。
- ofrw-crypto 用于實現《GM/T 0099-2020 開放版式文檔密碼應用技術規范》對OFD的密碼相關功能。
- ofdrw-gv OFDRW 所有模塊所共用的全局變量。
- ofdrw-converter OFD文檔轉換。
- ofdrw-tool OFD文檔工具,文檔合并、裁剪、重組。
- ofdrw-graphics2d 實現了AWT Graphics2D接口,生成OFD文檔內容。
- ofdrw-full 上述所有模塊整合包,用于簡化依賴引入。
由于篇幅約束,這里我們只介紹 ofdrw-converter
、ofdrw-sign
兩個模塊對應的 OFD 轉換 和 OFD 簽署 兩個功能。
三、PDF/文本/圖片 轉 OFD(ofdrw-conterver)
- 官方文檔: https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/CONVERTER.md
3.1 介紹:
ofdrw-converter
提供了 將其它類型媒體文件或文檔轉換成 OFD 文檔內容 的功能。ofdrw-converter 模塊在2.0.0
之后開始提供其它文檔或媒體類型向 OFD 文檔轉換功能。
核心接口類: org.ofdrw.converter.ofdconverter.DocConverter
核心方法簽名:
void convert(Path filepath, int... indexes) throws GeneralConvertException;
3 個實現類:
接口實現類命名格式: 原媒體格式+Converter
- PDF文檔:
PDFConverter
- 純文本:
TextConverter
- 圖片:
ImageConverter
注意:
convert()
方法的參數頁碼均從 0 起,例如文檔中的第 1 頁的 Index 也就是 0,并非所有媒體格式都有頁碼,在轉換無頁碼的沒給是,頁碼參數無效。
3.2 Maven 依賴:
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.3.3</version>
</dependency>
3.3 PDF轉換OFD
將PDF中頁面轉換為OFD頁面,采用PDFBox PDFRenderer
接口,以AWT graphics2d接口橋接,并通過ofdrw-graphics2d 模塊完成轉換功能。
實現類:org.ofdrw.converter.ofdconverter.PDFConverter
注意事項:
- 轉換后的頁面將采用PDF中頁面尺寸。
- 目前該轉換器任然有改進空間,可能存在部分特性在轉換過程中丟失,顯示效果與原PDF文檔不一致。
示例:
import org.ofdrw.converter.ofdconverter.PDFConverter;import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;/*** pdf 轉 ofd*/
private static void pdf2Ofd() {Path src = Paths.get("D:\test.pdf");Path dst = Paths.get("D:\test.ofd");try (PDFConverter converter = new PDFConverter(dst)) {converter.convert(src);} catch (IOException e) {log.error("pdf 轉 ofd 失敗,請稍后重試,原因:{}", e.getMessage(), e);throw new RuntimeException("pdf 轉 ofd 失敗,請稍后重試", e);}System.out.println("pdf 轉 ofd 成功,路徑: " + dst.toAbsolutePath());
}
特有方法
用途
void setEnableCopyAttachFiles(boolean enableCopyAttachFiles)
設置是否復制附件(默認復制)。
void setEnableCopyBookmarks(boolean enableCopyBookmarks)
設置是否復制書簽(默認復制)。
void setUUPMM(double UUPMM)
設置毫米表示的用戶單元數(PDF單位)
詳見 測試用例
執行結果:
3.5 文本轉換OFD
將文本轉換為OFD。
實現類:org.ofdrw.converter.ofdconverter.TextConverter
注意事項:
- 文本文件為無格式文件,若您需要設置文本格式請使用ofdrw-layout模塊。
示例:
import org.ofdrw.converter.ofdconverter.TextConverter;import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;/*** text 轉 ofd*/
private static void text2Ofd() {Path src = Paths.get("D:\test.txt");Path dst = Paths.get("D:\test.ofd");try (TextConverter converter = new TextConverter(dst)) {converter.convert(src);converter.convert(src);converter.convert(src);} catch (IOException e) {log.error("text 轉 ofd 失敗,請稍后重試,原因:{}", e.getMessage(), e);throw new RuntimeException("text 轉 ofd 失敗,請稍后重試", e);}System.out.println("text 轉 ofd 成功,路徑: " + dst.toAbsolutePath());
}
特有方法
用途
void append(String txt)
追加文本,文本將新起一行。
void setPageSize(PageLayout pageLayout)
設置OFD頁面尺寸。
void setFontSize(double fontSize)
設置字號,單位毫米。
詳見 測試用例
執行結果:
3.6 圖片轉換OFD
導入圖片到OFD中,圖片格式支持PNG、BPM、JPG。
實現類:org.ofdrw.converter.ofdconverter.ImageConverter
注意事項:
- 可以通過構造器指定導出的圖片類型,目前支持
PNG
、JPG
、BPM
,默認為PNG
格式。- 若圖片格式不在上述范圍您可能需要通過手動的方式設置加入圖片大小。
- 可以通過方法設置導出圖片的質量,也就是
ppm
參數,默認ppm
為15(15像素1毫米)。- 每個添加的圖片都將獨立為一頁,并且居中。
示例:
import org.ofdrw.converter.ofdconverter.ImageConverter;import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;/*** img 轉 ofd*/
private static void img2Ofd() {Path src = Paths.get("D:\signature.png");Path dst = Paths.get("D:\test.ofd");try (ImageConverter converter = new ImageConverter(dst)) {// 可以加多個圖片,每張圖片一頁converter.convert(src);converter.convert(src);converter.convert(src);} catch (IOException e) {log.error("img 轉 ofd 失敗,請稍后重試,原因:{}", e.getMessage(), e);throw new RuntimeException("img 轉 ofd 失敗,請稍后重試", e);}System.out.println("img 轉 ofd 成功,路徑: " + dst.toAbsolutePath());
}
特有方法
用途
void setPPM(double ppm)
設置圖片質量,單位為:每毫米像素數量。
void append(Path filepath, double width, double height)
追加圖片到新頁面并指定顯示大小。
void setPageSize(PageLayout pageLayout)
設置OFD頁面尺寸。
詳見 測試用例
執行結果:
四、OFD 轉 圖片/HTML/文本/PDF(ofdrw-conterver)
- 官方文檔: https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/EXPORTER.md
4.1 介紹:
ofdrw-converter
的 OFDExporter
接口具有多個實現,其實現與導出的目的文檔有關。
接口實現類命名格式為: 目標格式+Exporter
ofdrw-converter
支持導出為以下類型:
- 圖片:
ImageExporter
- SVG矢量圖形:
SVGExporter
- HTML網頁:
HTMLExporter
- 純文本:
TextExporter
- PDF文檔:
PDFExporterIText
、PDFExporterPDFBox
4.2 Maven 依賴:
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.3.3</version>
</dependency>
4.3 導出為圖片
導出OFD文檔頁面為圖片,圖片格式支持PNG、BPM、JPG。
實現類:org.ofdrw.converter.export.ImageExporter
注意事項:
- 可以通過構造器指定導出的圖片類型,目前支持
PNG
、JPG
、BPM
,默認為PNG
格式。 - 可以通過構造器或方法設置導出圖片的質量,也就是
ppm
參數,默認ppm
為15。 - 導出圖片將存放于同一個目錄,在該目錄中圖片以的頁面索引作為文件名,如第1頁的文件名為
0.png
。
示例:
Path ofdPath = Paths.get("src/test/resources/999.ofd");
Path imgDirPath = Paths.get("target/999.ofd/");
try (ImageExporter exporter = new ImageExporter(ofdPath, imgDirPath, "PNG", 20d)) {exporter.export();
}
效果如下:
特有方法
用途
List<Path> getImgFilePaths()
獲取導出頁面對應圖片文件路徑,列表中次序與導出時的頁碼次序一致。
void setPPM(double ppm)
設置導出圖片質量,單位為:每毫米像素數量。
詳見 測試用例
4.4 導出為SVG圖形
導出OFD文檔頁面為SVG圖形,文本中的所有文字都將轉換為矢量路徑。
實現類:org.ofdrw.converter.export.SVGExporter
注意事項:
- 可以通過構造器或方法設置導出SVG圖形大小,也就是
ppm
參數,默認ppm
為15。 - 導出SVG圖形文件將存放于同一個目錄,在該目錄中以頁面索引作為文件名,如第1頁的文件名為
0.svg
。
示例:
Path ofdPath = Paths.get("src/test/resources/999.ofd");
Path svgPath = Paths.get("target/999.ofd/");
try (SVGExporter exporter = new SVGExporter(ofdPath, svgPath, 15d)) {exporter.export();
}
效果如下:(背景已經變成透明色了)
特有方法
用途
List<Path> getSvgFilePaths()
獲取導出頁面對應SVG文件路徑,列表中次序與導出時的頁碼次序一致。
void setPPM(double ppm)
設置導出SVG大小,單位為:每毫米像素數量。
詳見 測試用例
4.5 導出為HTML網頁
導出OFD文檔頁面為HTML網頁,需要瀏覽器支持HTML5才可正常預覽,由于是基于SVG方案導出的HTML,因此導出文件可能較大。
實現類:org.ofdrw.converter.export.HTMLExporter
注意事項:
- 若您需要調整HTML網頁樣式,可以通過繼承
HTMLExporter
并覆蓋header
、booter
、margin_bottom
屬性,使用自定義的HTML樣式。 - 導出的HTML網頁需要瀏覽器支持HTML5才可正常預覽。
- 若頁面文字內容由文字圖元構成且都由Unicode組成,那么導出網頁可能可以通過鼠標選中與復制。
示例:
Path ofdPath = Paths.get("src/test/resources/n.ofd");
Path htmlPath = Paths.get("target/n.html");
try (HTMLExporter exporter = new HTMLExporter(ofdPath, htmlPath)) {exporter.export();
}
效果如下:
詳見 測試用例
4.6 導出為文本
導出OFD文檔頁面為文本文件,并非所有OFD頁面都能導出文本,只有符合特定條件的OFD才可導出。
實現類:org.ofdrw.converter.export.TextExporter
注意事項:
- 部分OFD文檔由于采用字形索引來定位文字、有個OFD整個頁面均為路徑數據圖元而不是文字圖元、有的OFD頁面整個都為圖片等諸多原因,無法保證一定能夠導出文本。
- 由于文本布局等各種因素,導出文本順序也難以與原文文本順序一致。
示例:
Path ofdPath = Paths.get("src/test/resources/999.ofd");
Path txtPath = Paths.get("target/999.txt");
try (TextExporter exporter = new TextExporter(ofdPath, txtPath)) {exporter.export();
}
效果如下:
詳見 測試用例
4.7 [不推薦] 導出為PDF
警告:不推薦導出為PDF,OFD本身就是國產的板式文件,非特殊場景沒有必要導出為PDF文件,該模塊將進入LTS狀態,不再持續更新!
導出OFD文檔頁面為PDF文件,該導出根據實現所使用的庫不一致具有兩種導出實現。
實現類:
org.ofdrw.converter.export.PDFExporterIText
org.ofdrw.converter.export.PDFExporterPDFBox
注意事項:
- 導出無法保證文檔效果一致性,若您有建設性意見請提交PR。
基于PDFBox實現示例:
Path ofdPath = Paths.get("src/test/resources/999.ofd");
Path pdfPath = Paths.get("target/999.pdf");
try (OFDExporter exporter = new PDFExporterPDFBox(ofdPath, pdfPath)) {exporter.export();
}
詳見 基于PDFBox導出 測試用例
基于iText實現示例:
Path ofdPath = Paths.get("src/test/resources/999.ofd");
Path pdfPath = Paths.get("target/999.pdf");
try (OFDExporter exporter = new PDFExporterIText(ofdPath, pdfPath)) {exporter.export();
}
詳見 基于iText導出 測試用例
效果如下:
五、OFD簽署
5.1 在線生成 sm2 證書
- 在線生成地址: https://www.gmcrt.cn/gmcrt/index.jsp
生成證書如下所示:
5.2 制作 esl 印章
Java 實現代碼如下:
/*** 生成ESL印章*/
public static void buildEsl() throws IOException, CertificateEncodingException {String imagePath = "D:\signature.jpg";String sealerCertPath = "D:\keystore\sm2.p12";String imageBase64 = java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(imagePath)));Path userP12Path = Paths.get(sealerCertPath);PrivateKey sealerPrvKey;try {byte[] bytes = FileUtils.readFileToByteArray(new File(sealerCertPath));sealerPrvKey = NativePKCS12Tools.ReadPrvKey(java.util.Base64.getEncoder().encodeToString(bytes), "ACGkaka", "123456");} catch (Exception e) {throw new RuntimeException(e);}Certificate signCert = null ;try {signCert = PKCS12Tools.ReadUserCert(userP12Path, "ACGkaka", "123456");} catch (GeneralSecurityException e) {throw new RuntimeException(e);}byte[] encoded = signCert.getEncoded();String s1 = java.util.Base64.getEncoder().encodeToString(encoded);System.out.println("s1="+s1);System.out.println("imageBase64:"+imageBase64);long start = System.currentTimeMillis();Calendar now = Calendar.getInstance();now.add(Calendar.YEAR, 2);Date then = now.getTime();BuildSESealBase64 seal = new BuildSESealBase64().setEsID(UUID.randomUUID().toString().replace("-", ""))//印章ID.setSealImageBase64(imageBase64)//印章圖片base64.setSealName("ACGkaka測試章")//印章名稱.setBuildSealerCertBase64(s1)//制章人公鑰.setHeight(40)//印章高度單位毫米.setWidth(40)//印章寬度單位毫米.setSesheader("chinamobile sign")//印章頭部信息.setUseSealerCertBase64(s1)//用章人公鑰.setValidStartDate(new Date())//印章有效期 傳空,固定讀用章人證書有效.setValidEndDate(then);//印章有效期傳空,固定讀用章人證書有效try {PrivateKey finalSealerPrvKey = sealerPrvKey;Map<String, Object> stringObjectMap = SESeal.buildBase64(seal, new CASignInterface() {//制章人私鑰簽名方法(需要調用CA密碼機服務)@Overridepublic byte[] sign(byte[] data) {Signature sg = null;try {sg = Signature.getInstance("SM3WithSM2", new BouncyCastleProvider());} catch (NoSuchAlgorithmException e) {e.printStackTrace();}try {sg.initSign(finalSealerPrvKey);} catch (InvalidKeyException e) {e.printStackTrace();}try {sg.update(data);} catch (SignatureException e) {e.printStackTrace();}byte[] sigVal = new byte[0];try {sigVal = sg.sign();} catch (SignatureException e) {e.printStackTrace();}System.out.println(sigVal.length);return sigVal;}});String s = stringObjectMap.get("base64").toString();FileUtils.writeByteArrayToFile(new File("D:\keystore\sm2-signature.esl"), java.util.Base64.getDecoder().decode(s));} catch (Exception e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("執行耗時間="+(end-start)/1000);
}
生成 ESL 印章如下所示:
5.3 坐標簽署OFD
Java 實現代碼如下所示:
/*** OFD坐標簽*/
private static void signByXy() throws GeneralSecurityException, IOException {Pos pos = new Pos();pos.setPage(1);pos.setX(100);pos.setY(100);pos.setWidth(40);pos.setHeigh(40);List<Pos> apList = new ArrayList<>();apList.add(pos);String req = "D:\test.ofd";String reqbase = java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(req)));String eslpath = "D:\keystore\sm2-signature.esl";String eslbase = java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(eslpath)));Path userP12Path = Paths.get("D:\keystore\sm2.p12");PrivateKey sealerPrvKey = null ;try {sealerPrvKey = PKCS12Tools.ReadPrvKey(userP12Path, "ACGkaka", "123456");} catch (GeneralSecurityException e) {throw new RuntimeException(e);}Certificate signCert = null ;try {signCert = PKCS12Tools.ReadUserCert(userP12Path, "ACGkaka", "123456");} catch (GeneralSecurityException e) {throw new RuntimeException(e);}byte[] encoded = signCert.getEncoded();String s1 = java.util.Base64.getEncoder().encodeToString(encoded);System.out.println("s1="+s1);BuildOFDSignerBase64 buildOFDSigner = new BuildOFDSignerBase64().setOfdReqBase64(reqbase).setEslBase64(eslbase).setSignMode(0).setUseSealerCertBase64(s1).setApList(apList);byte[] bytes = null;try {PrivateKey finalSealerPrvKey = sealerPrvKey;bytes = OFDSigner.signByXyBase64(buildOFDSigner, new CASignInterface() {@Overridepublic byte[] sign(byte[] data) {Signature sg = null;try {sg = Signature.getInstance("SM3WithSM2", new BouncyCastleProvider());} catch (NoSuchAlgorithmException e) {e.printStackTrace();}try {sg.initSign(finalSealerPrvKey);} catch (InvalidKeyException e) {e.printStackTrace();}try {sg.update(data);} catch (SignatureException e) {e.printStackTrace();}byte[] sigVal = new byte[0];try {sigVal = sg.sign();} catch (SignatureException e) {e.printStackTrace();}System.out.println(sigVal.length);return sigVal;}});}catch (Exception e){e.printStackTrace();}FileUtils.writeByteArrayToFile(new File("D:\test3.ofd"),bytes);
}
簽署結果如下所示:
5.4 騎縫簽署OFD
Java實現代碼如下,除了 main() 方法之外還有 3 個方法:
public static void main(String[] args) throws IOException, GeneralSecurityException {byte[] pdfBytes = FileUtils.readFileToByteArray(new File("D:\test_2頁.ofd"));byte[] certBytes = FileUtils.readFileToByteArray(new File("D:\keystore\sm2.p12"));String certBase64 = Base64.getEncoder().encodeToString(certBytes);// 坐標簽署
// List<ItemMapDTO> itemList = getItemList();
// byte[] newPdfBytes = OFDSignUtil.signSm2PDFBySeal(pdfBytes, itemList, "els", certBase64);
// FileUtils.writeByteArrayToFile(new File("D:\test\test2.ofd"), newPdfBytes);// 騎縫簽署String side = "Right";double offset = 40.0d;byte[] eslBytes = FileUtils.readFileToByteArray(new File("D:\keystore\sm2-signature.esl"));String eslBase64 = Base64.getEncoder().encodeToString(eslBytes);String pdfBase64 = Base64.getEncoder().encodeToString(pdfBytes);byte[] bytes1 = OFDSignUtil.signByRidingStampPos(pdfBase64, eslBase64, certBase64, side, offset);FileUtils.writeByteArrayToFile(new File("D:\test\test3.ofd"), bytes1);
}/*** 騎縫簽署** @param reqBase64 待簽署文件base64* @param eslBase64 印章文件base64* @param certBase64 用戶證書base64* @param side Left左騎縫 Right右騎縫(默認)* @return 簽署后文件*/
public static byte[] signByRidingStampPos(String reqBase64, String eslBase64, String certBase64, String side, double offset) {log.info("OFD騎縫簽署開始");BuildOFDSignerRideBase64 buildOFDSigner = new BuildOFDSignerRideBase64().setOfdReqBase64(reqBase64).setEslBase64(eslBase64).setSignMode(0).setUseSealerCertBase64(fileToInputStream(certBase64)).setSide(side).setOffset(offset);try {byte[] bytes = OFDSigner.signByRidingStampPosBase64(buildOFDSigner, data -> {try {PrivateKey sealerPrvKey = NativePKCS12Tools.ReadPrvKey(certBase64, "ACGkaka", "123456");Signature sg = null;try {sg = Signature.getInstance("SM3WithSM2", new BouncyCastleProvider());} catch (NoSuchAlgorithmException e) {e.printStackTrace();}try {sg.initSign(sealerPrvKey);} catch (InvalidKeyException e) {e.printStackTrace();}try {sg.update(data);} catch (SignatureException e) {e.printStackTrace();}byte[] sigVal = new byte[0];try {sigVal = sg.sign();} catch (SignatureException e) {e.printStackTrace();}System.out.println(sigVal.length);return sigVal;} catch (Exception e) {log.error("OFD騎縫簽署異常", e);throw new IllegalArgumentException("騎縫簽署異常");}});log.info("騎縫簽署完成,簽署后文件大小:{}kb", bytes == null ? 0 : bytes.length / 1024);return bytes;} catch (Exception e) {log.error("OFD騎縫簽署異常", e);throw new IllegalArgumentException("騎縫簽署異常");}
}/*** 讀取證書內容* @param base64String* @return*/
public static String fileToInputStream(String base64String){try {Certificate signCert = PKCS12Tools.ReadUserCert(base64ToInputStream(base64String), "ACGkaka", "123456");byte[] encoded = signCert.getEncoded();return Base64.getEncoder().encodeToString(encoded);} catch (IOException | GeneralSecurityException e) {throw new RuntimeException(e);}
}
簽署結果如下所示:
5.5 驗簽 OFD
Java 實現代碼如下:
/*** 驗簽OFD*/
public static void signVerify() throws IOException, GeneralSecurityException {Path out = Paths.get("D:\test3.ofd");// 驗證try (OFDReader reader = new OFDReader(out);OFDValidator validator = new OFDValidator(reader)) {validator.setValidator(new SESV4ValidateContainer());validator.exeValidate();System.out.println(">> 驗證通過");}
}
驗簽結果如下:
整理完畢,完結撒花~ ??
參考地址:
1.科普 | ofd文件是什么,https://zhuanlan.zhihu.com/p/145599784
2.一文讀懂 OFD 文件格式:國產 PDF,關鍵,重要,https://www.ithome.com/0/521/264.htm