SpringBoot實戰(三十二)集成 ofdrw,實現 PDF 和 OFD 的轉換、SM2 簽署OFD

目錄
    • 一、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 的文件格式呢?

主要出于以下幾點原因:

  1. 格式不統一: 目前在國內可使用的版式文件格式包括 PDFCEB 等在內有不下十種,來自不同廠商和不同的技術,沒有統一標準,就會導致不同機構、不同企業之間的文件交流存在阻礙,文件長期存檔很困難。

  2. 難以自主可控: 我們知道,在目前辦公文檔市場上,來自 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-converterofdrw-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

注意事項:

  • 可以通過構造器指定導出的圖片類型,目前支持PNGJPGBPM,默認為PNG格式。
  • 若圖片格式不在上述范圍您可能需要通過手動的方式設置加入圖片大小。
  • 可以通過方法設置導出圖片的質量,也就是ppm參數,默認ppm15(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-converterOFDExporter 接口具有多個實現,其實現與導出的目的文檔有關。

接口實現類命名格式為: 目標格式+Exporter

ofdrw-converter 支持導出為以下類型:

  • 圖片:ImageExporter
  • SVG矢量圖形:SVGExporter
  • HTML網頁:HTMLExporter
  • 純文本:TextExporter
  • PDF文檔:PDFExporterITextPDFExporterPDFBox
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

注意事項:

  • 可以通過構造器指定導出的圖片類型,目前支持PNGJPGBPM,默認為PNG格式。
  • 可以通過構造器或方法設置導出圖片的質量,也就是ppm參數,默認ppm15
  • 導出圖片將存放于同一個目錄,在該目錄中圖片以的頁面索引作為文件名,如第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參數,默認ppm15
  • 導出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并覆蓋headerbootermargin_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();
}

效果如下:

img.png

詳見 測試用例

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72298.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72298.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72298.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

隨機樹算法 自動駕駛汽車的路徑規劃 靜態障礙物(Matlab)

隨著自動駕駛技術的蓬勃發展&#xff0c;安全、高效的路徑規劃成為核心挑戰之一。快速探索隨機樹&#xff08;RRT&#xff09;算法作為一種強大的路徑搜索策略&#xff0c;為自動駕駛汽車在復雜環境下繞過靜態障礙物規劃合理路徑提供了有效解決方案。 RRT 算法基于隨機采樣思想…

Vscode通過Roo Cline接入Deepseek

文章目錄 背景第一步、安裝插件第二步、申請API key第三步、Vscode中配置第四步、Deepseek對話 背景 在前期介紹【IDEA通過Contince接入Deepseek】步驟和流程&#xff0c;那如何在vscode編譯器中使用deepseek&#xff0c;記錄下來&#xff0c;方便備查。 第一步、安裝插件 在…

C++ 二叉樹代碼

二叉樹代碼&#xff0c;見下 #include <iostream> using namespace std;template<typename T> struct TreeNode{T val;TreeNode *left;TreeNode *right;TreeNode():val(0), left(NULL), right(NULL)TreeNode(T x):val(x), left(NULL), right(NULL){} };template&l…

leetcode第17題求電話號碼組合

原題出于leetcode第17題https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/題目如下&#xff1a; 題目稍微有點復雜&#xff0c;初看會感覺特別復雜&#xff0c;首先我們需要理清思路&#xff1a; 最后的結果是字母組合&#xff0c;因此遍歷的是…

Deepseek對ChatGPT的沖擊?

從測試工程師的視角來看&#xff0c;DeepSeek對ChatGPT的沖擊主要體現在**測試場景的垂直化需求與通用模型局限性之間的博弈**。以下從技術適配性、效率優化、風險控制及未來趨勢四個維度展開分析&#xff1a; --- ### **一、技術適配性&#xff1a;垂直領域能力決定工具選擇…

三十五周學習周報

目錄 摘要abstract文獻閱讀1.1相關知識1.1.1 PSO1.1.2 BI-LSTM1.1.3 BI-GRU 1.2 整體框架1.3 實驗分析 總結 摘要 在本周閱讀的文獻中&#xff0c;作者提出了一種創新的水文時間序列預測模型&#xff0c;其通過將粒子群優化&#xff08;PSO&#xff09;與Bi-LSTM和Bi-GRU相結合…

Git:多人協作

目錄 多人協作一 準備工作 開發者1準備工作 開發者2準備工作 協作開發 將內容合并進master 多人協作二 開發者1進行工作 開發者2進行工作 特殊場景 將內容合并進master 之前所學習的Git操作&#xff0c;是為了多人協作開發做鋪墊的&#xff0c;因為在公司中&#xf…

登錄次數限制

文章目錄 一、應用場景與設計目的1. 應用場景2. 設計目的 二、功能設計1. 登錄限制規則2. 解鎖機制3. 適用維度 三、技術實現1. 數據存儲2. 邏輯流程3. 實現代碼示例4. 動態鎖定時間 四、安全增強與擴展1. 防止用戶名枚舉2. 加入驗證碼3. 監控與報警4. 分布式支持 五、設計思考…

計算機畢業設計SpringBoot+Vue.js景區民宿預約系統(源碼+文檔+PPT+講解)

溫馨提示&#xff1a;文末有 CSDN 平臺官方提供的學長聯系方式的名片&#xff01; 溫馨提示&#xff1a;文末有 CSDN 平臺官方提供的學長聯系方式的名片&#xff01; 溫馨提示&#xff1a;文末有 CSDN 平臺官方提供的學長聯系方式的名片&#xff01; 作者簡介&#xff1a;Java領…

(十 五)趣學設計模式 之 命令模式!

目錄 一、 啥是命令模式&#xff1f;二、 為什么要用命令模式&#xff1f;三、 策略模式的實現方式四、 命令模式的優缺點五、 命令模式的應用場景六、 總結 &#x1f31f;我的其他文章也講解的比較有趣&#x1f601;&#xff0c;如果喜歡博主的講解方式&#xff0c;可以多多支…

Matlab 大量接單

分享一個matlab接私活、兼職的平臺 1、技術方向滿足任一即可 2、技術要求 3、最后 技術方向滿足即可 MATLAB&#xff1a;熟練掌握MATLAB編程語言&#xff0c;能夠使用MATLAB進行數據處理、機器學習和深度學習等相關工作。 機器學習、深度學習、強化學習、仿真、復現、算法、…

【自學筆記】大數據基礎知識點總覽-持續更新

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 大數據基礎知識點總覽1. 大數據概述2. 大數據處理技術3. 數據倉庫與數據挖掘4. 大數據分析與可視化5. 大數據平臺與架構6. 大數據安全與隱私 總結 大數據基礎知識點…

17、什么是智能指針,C++有哪幾種智能指針【高頻】

智能指針其實不是指針&#xff0c;而是一個&#xff08;模板&#xff09;類&#xff0c;用來存儲指向某塊資源的指針&#xff0c;并自動釋放這塊資源&#xff0c;從而解決內存泄漏問題。主要有以下四種&#xff1a; auto_ptr 它的思想就是當當一個指針對象賦值給另一個指針對…

CAN總線通信協議學習2——數據鏈路層之幀格式

1 幀格式 幀格式可理解為定義了傳輸的數據&#xff08;叫報文&#xff09;應該“長什么樣”來傳輸&#xff0c;也為后續設定一些規則如錯誤檢查機制提供了思路。 首先&#xff0c;幀格式可分為以下5種類型&#xff1a; PS&#xff1a;CAN總線任意一個設備可當收也可當發&#…

MATLAB中asManyOfPattern函數用法

目錄 語法 說明 示例 匹配盡可能多的模式實例 指定要匹配的最小模式數 指定要匹配的最小和最大模式數 asManyOfPattern函數的功能是模式匹配次數盡可能多。 語法 newpat asManyOfPattern(pat) newpat asManyOfPattern(pat,minPattern) newpat asManyOfPattern(pat,m…

1×1卷積的作用與原理詳解

11卷積的作用與原理詳解 文章目錄 11卷積的作用與原理詳解引言1. 什么是11卷積&#xff1f;2. 11卷積的數學表達3. 11卷積的主要作用3.1 改變通道數&#xff08;升維/降維&#xff09;3.1.1 降維&#xff08;Dimension Reduction&#xff09;3.1.2 升維&#xff08;Dimension I…

網絡配置的基本信息

目錄 一、網絡接口信息 1、關閉虛擬化服務 2、配置臨時IP 3、配置靜態IP 4、常見網絡命令 5、安裝Wireshark 一、網絡接口信息 輸入 ip address&#xff0c;會出現下面的內容 網卡名稱及其含義&#xff1a; 網卡名稱說明lo 表示本地回環地址。 ens32 有線網卡&#xff0c…

dify綁定飛書多維表格

dify 綁定飛書和綁定 notion 有差不多的過程&#xff0c;都需要套一層應用的殼子&#xff0c;而沒有直接可以訪問飛書文檔的 API。本文記錄如何在dify工具中使用新增多條記錄工具。 創建飛書應用 在飛書開放平臺創建一個應用&#xff0c;個人用戶創建企業自建應用。 自定義應…

深入解析Crawl4AI:為AI應用量身定制的高效開源爬蟲框架

引言 在當今數據驅動的時代&#xff0c;人工智能&#xff08;AI&#xff09;和大型語言模型&#xff08;LLM&#xff09;的發展對高質量數據的需求日益增長。如何高效地從互聯網上獲取、處理和提取有價值的數據&#xff0c;成為了研究人員和開發者面臨的關鍵挑戰。Crawl4AI作為…

nginx 動態計算攔截非法訪問ip

需求&#xff1a;在Nginx上實現一個動態攔截IP的方法&#xff0c;具體是當某個IP在1分鐘內訪問超過60次時&#xff0c;將其加入Redis并攔截&#xff0c;攔截時間默認1天。 技術選型&#xff1a;使用NginxLuaRedis的方法。這種方案通過Lua腳本在Nginx處理請求時檢查Redis中的黑…