ofd板式文件 電子簽章實現方法


前言 文檔處理一般經過三個環節:流、版、簽;流式軟件負責編輯,如:office、wps等。版式軟件負責文檔定型,保證顯示樣式不跑偏;版式文件格式有兩種:pdf、ofd。簽章軟件負責對版式文檔簽章。簽章是文檔處理的最后一個環節。
  當前,市面上的版式文件還是以pdf為主;對pdf的簽章,國內研究的比較多。但是對ofd簽章,國內研究時間不長,相關成熟的產品並不多。作者研究ofd多年,仔細分析了ofd簽章標准,編寫了一套簽章軟件,可以滿足自由簽章、騎縫章等類型的簽章。作者采用的簽章方法有以下優點:思路新穎、處理速度快、能滿足各類復雜簽章需求。

 

 

1 OFD簽章基本概念

  簽章的目的是保證數據的完整性、真實性。完整性是通過記錄ofd文件的哈希值來保證的(國產算法為SM3);真實性是通過非對稱加密算法保證的(國產算法為SM2)。簽章的過程其實就是記錄ofd內的各個文件哈希值,再用私鑰對哈希值簽名。

2 OFD簽章遵循的標准

  OFD簽章涉及的標准不止一個;這往往導致開發簽章軟件時茫然無措。

  ofd簽章遵循兩類標准:

  2.1  ofd板式文件格式標准:《 GB/T 33190-2016電子文件存儲與交換格式》
  2.2  簽章密碼技術規范: 《GM/T 0031-2014 安全電子簽章密碼技術規范》,《GB/T 38540-2020信息安全技術 安全電子簽章密碼技術規范》。

 

3 簽章后,哪些文件被改動?

  簽章過程后,以下文件被修改。

3.1 OFD.xml

3.2 Signatures.xml

  簽章匯總文件

 

3 Signature.xml

  具體簽章文件,記錄印章數據、簽章數據、各個文件哈希值、印章位置信息等。

 

4 簽章需要主要事項

  通過以上分析,可以看出簽章好像並不難。其實不然,有幾個問題要注意:

  4.1   不要想當然的認為OFD文件的路徑都是固定的。OFD文件只有入口文件“OFD.xml”,名字是固定;其它任何文件名字都是可變的。只是為了方便理解,生成的ofd文件名稱遵循一定的規則。
  下圖只是建議的組織和命名規則。

  4.2  如果是多印章,后簽的印章不能影響前一個印章。

    如果文檔已經做了簽章了,再簽章時,除了簽章匯總文件(Signatures.xml)外,其他文件不能做任何改動。

  4.3  騎縫章處理。對於騎縫章,需要計算每個章的位置。需要分析出文件的頁數以及每頁尺寸信息。

5 簽章處理步驟

5.1 分析ofd原文件,將文件分類。

  通過入口文件“OFD.xml”,層層剖析,將ofd內各類文件分類,具體分類如下:

enum class EN_OfdFileType { unset, root, doucument, publicRes, documentRes, pageContent, resFile, annotations, annotation_page, customTags, customTagContent, templatePage, signatures, signatureContent, signedValue, signedSeal, attachments, attachmentContent };

在分析過程中,同時解析出ofd頁文件的尺寸。得出每個文件的屬性。

class OfdFileInfoDetail { public: EN_OfdFileType OfdFileType = EN_OfdFileType::unset; QString FilePath; //文件的完整路徑
    QByteArray FileContent;     //文件內容

    int OfdFileIndex = -1;      //文件索引 多文檔的情況下 有用 //為ofd頁面時,有效;OfdFileType=pageContent
    int PageIndex = -1; //頁索引
    int PageId = -1;    //頁id
    QString PhysicalBox; //頁尺寸 //為ofd Signature時,有效;OfdFileType=signatureContent
    int SignatureIndex = -1;//在文件Signatures中的索引
    QString PathSeal;       //印章文件路徑
    QString PathSignedValue;//簽名后文件路徑
};

5.2 對分析后的文件處理

  如果是第一次簽章,需要生成Signatures.xml。

 QSharedPointer<OfdFileInfoDetail> signaturesFile = GetOfdFile(EN_OfdFileType::signatures);
    if(signaturesFile.isNull())
    {
        QString signaturesPath = AddSignaturesPathToRoot(rootFile);
        OfdFileInfoDetail *signaturesFileInfo = CreateSignaturesFile(signaturesPath);
        signaturesFile.reset(signaturesFileInfo);

        _listOfdFile.append(signaturesFile);
    }

  計算文件的哈希值,計算印章的位置,生成Signature.xml。

void SignOfdFile::CreateSignature(QString signaturePath,QByteArray& signatureFileContent,QString& signedValuePath)
{
    QSharedPointer<XmlNode> header(new XmlNode());
    header->SetName("Signature");
    header->SetNameSpace(OfdCreatorParam::OFD_NameSpace);
    header->SetNameSpaceUrl(OfdCreatorParam::OFD_NameSpaceUrl);

    XmlNode *nodeSignedInfo = header->AddChildByName("SignedInfo",true);

    //nodeProvider
    XmlNode *nodeProvider = nodeSignedInfo->AddChildByName("Provider",true);
    nodeProvider->SetAttr("Company",_company);
    nodeProvider->SetAttr("Version",_version);
    nodeProvider->SetAttr("ProviderName",_providerName);

    //SignatureMethod
    XmlNode *nodeSignatureMethod = nodeSignedInfo->AddChildByName("SignatureMethod",true);
    nodeSignatureMethod->SetText(_signInfoInput.signMethod);

    //SignatureDateTime
    XmlNode *nodeSignatureDateTime = nodeSignedInfo->AddChildByName("SignatureDateTime",true);
    nodeSignatureDateTime->SetText(_signInfoInput.signDateTime);

    //Seal
    if(!_sealData.isEmpty())
    {
        XmlNode *nodeSeal = nodeSignedInfo->AddChildByName("Seal",true);
        XmlNode *nodeSealBaseLoc= nodeSeal->AddChildByName("BaseLoc",true);
        QString sealPath = OfdPathHelper::GetOfdFullPath(signaturePath,"Seal.esl");
        nodeSealBaseLoc->SetText(OfdPathHelper::AddStartSlash(sealPath));
        AddSealToFile(sealPath);
    }

    //References
    XmlNode *nodeReferences = nodeSignedInfo->AddChildByName("References",true);
    nodeReferences->SetAttr("CheckMethod",SignOfdParam::MethodName_SM3);
    foreach(QSharedPointer<OfdFileInfoDetail> file , _listOfdFile)
    {
        if(file->OfdFileType == EN_OfdFileType::signatures)
            continue;
        if(file->FileContent.isEmpty())
            continue;

        XmlNode *nodeReference = nodeReferences->AddChildByName("Reference",true);
        nodeReference->SetAttr("FileRef",OfdPathHelper::AddStartSlash(file->FilePath));

        XmlNode *nodeCheckValue = nodeReference->AddChildByName("CheckValue",true);
        nodeCheckValue->SetText(sm3_digest_base64(file->FileContent));
    }

    //StampAnnot
    CreateStampAnnot(nodeSignedInfo);

    //SignedValue.dat
    signedValuePath = OfdPathHelper::GetOfdFullPath(signaturePath,"SignedValue.dat");
    XmlNode *nodeSignedValue = header->AddChildByName("SignedValue",true);
    nodeSignedValue->SetText(OfdPathHelper::AddStartSlash(signedValuePath));

    //添加到文件列表
    QSharedPointer<OfdFileInfoDetail> signatureFile(new OfdFileInfoDetail());
    signatureFile->FilePath = signaturePath;
    signatureFile->FileContent = header->CreateXml(true).toUtf8();
    signatureFile->OfdFileType = EN_OfdFileType::signatureContent;
    _listOfdFile.append(signatureFile);

    signatureFileContent = signatureFile->FileContent;
}

后記 本文粗略的描述了ofd簽章的過程,實現簽章的途徑有多種。本文給出了一種可行、易懂的簽章方法。具體的簽章過程涉及大量細節處理,對於開發人員來講是一種挑戰。作者通過多次修改完善,編寫了一款簽章服務軟件,可以與簽名接口對接,就大大減輕了簽章的難度。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM