簡體   English   中英

使用 iTextSharp 對 PDF 進行外部簽名 - 更改/損壞的文檔

[英]External signing PDF with iTextSharp - altered/corrupted document

目標是實現一個 PDF 簽名過程,其中服務器(.NET Core 服務)根據請求(電子)向客戶端提供要簽名的哈希。 然后,客戶端使用通過 PKCS#11 接口從智能卡獲得的私鑰對給定的散列進行簽名。 然后將簽名發送回服務器以使用 iTextSharp 附加到 PDF 文件中。

目前,使用 node-webcrypto-p11 使用智能卡令牌對哈希進行簽名的過程非常簡單(需要進行大量試驗和錯誤才能到達此處)。 使用的算法是 RSASSA-PKCS1-v1_5。 我可以成功簽署哈希並在之后驗證它。

我最近在使用 iTextsharp (3)外部簽名 PDF的幫助下構建了我以前的實現,其中我使用getAuthenticatedAttributeBytes來獲取要簽名的哈希值(如mkl所建議的)。

在 Acrobat Reader 中查看簽名時,我看到可怕的文檔被更改/損壞,與 OP pgkdev相同。 如上所述,客戶端的簽名過程很簡單,我不懷疑那里會出現任何問題(不過我願意對此進行審查)。

pgkdev提到了Priyanka 的問題,在那里我發現我可能在簽署文檔的兩步過程中遇到了問題,其中哈希值不再相同。

如果您檢查Grazina 的問題,我們可以看到這樣的實現是成功的,當您一步完成該過程時。

mkl進一步提到了一種通過 2 個步驟成功完成的方法,但我缺少更多關於如何實現這一目標的解釋。

注意:我(據我所知)無法在第一步中做我想做的事情,因為簽名是由 Electron 應用程序中的客戶端發起的。

在此處輸入圖片說明 在此處輸入圖片說明 在此處輸入圖片說明

單擊證書詳細信息顯示我的完整證書詳細信息。

private const string SIG_FIELD_NAME = "sigField1";

    private byte[] GetPDFHash(string pdfFilePath, byte[] certificateValue)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";

        //Get certificates chain from certificate value
        ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);

        byte[] hash = CreatePDFEmptySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
        return hash;
    }

    private void SignPDFHash(string pdfFilePath, byte[] hash, byte[] signedHash, byte[] certificateValue)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
        var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";

        //Get certificates chain from certificate value
        ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);

        CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
    }

    private byte[] CreatePDFEmptySignature(string pdfFilePath, string preparedSigPdfFilePath, ICollection<X509Certificate> certificatesChain)
    {
        byte[] hash;

        using (PdfReader reader = new PdfReader(pdfFilePath))
        {
            using (FileStream baos = System.IO.File.OpenWrite(preparedSigPdfFilePath))
            {
                PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
                PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
                sap.SetVisibleSignature(new Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);

                sap.Certificate = certificatesChain.First();

                var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath, certificatesChain);

                MakeSignature.SignExternalContainer(sap, externalEmptySigContainer, 8192);

                hash = externalEmptySigContainer.PdfHash;
            }
        }
        return hash;
    }

    private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, 
        byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
    {
        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(hash, signedHash, certificatesChain);
                MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
            }
        }
    }

    public class MyExternalEmptySignatureContainer : ExternalBlankSignatureContainer
    {
        public string PdfTempFilePath { get; set; }
        public byte[] PdfHash { get; private set; }

        public ICollection<X509Certificate> CertificatesList { get; set; }

        public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath,
            ICollection<X509Certificate> certificatesChain) : base(filter, subFilter)
        {
            PdfTempFilePath = pdfTempFilePath;
            CertificatesList = certificatesChain;
        }

        override public byte[] Sign(Stream data)
        {
            byte[] sigContainer = base.Sign(data);

            //Get the hash
            IDigest messageDigest = DigestUtilities.GetDigest("SHA-256");
            byte[] messageHash = DigestAlgorithms.Digest(data, messageDigest);

            #region Log
            var messageHashFilePath = $"{PdfTempFilePath}.messageHash-b64.txt";
            System.IO.File.WriteAllText(messageHashFilePath, Convert.ToBase64String(messageHash));
            #endregion Log

            //Add hash prefix
            byte[] sha256Prefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
            byte[] digestInfo = new byte[sha256Prefix.Length + messageHash.Length];
            sha256Prefix.CopyTo(digestInfo, 0);
            messageHash.CopyTo(digestInfo, sha256Prefix.Length);

            #region Log
            var messageHashWithPrefixFilePath = $"{PdfTempFilePath}.messageHash-with-prefix-b64.txt";
            System.IO.File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
            #endregion Log

            var sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
            var authenticatedAttributeBytes =
                sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

            PdfHash = authenticatedAttributeBytes;

            return sigContainer;
        }
    }

    public class MyExternalSignatureContainer : IExternalSignatureContainer
    {
        public byte[] Hash { get; set; }
        public byte[] SignedHash { get; set; }
        public ICollection<X509Certificate> CertificatesList { get; set; }

        public MyExternalSignatureContainer(byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesList)
        {
            Hash = hash;
            SignedHash = signedHash;
            CertificatesList = certificatesList;
        }

        public byte[] Sign(Stream data)
        {
            PdfPKCS7 sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
            sgn.SetExternalDigest(this.SignedHash, null, "RSA");    
            return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);         
        }

        public void ModifySigningDictionary(PdfDictionary signDic)  {   }
    }

    private ICollection<X509Certificate> GetCertificatesChain(byte[] certByteArray)
    {
        ICollection<X509Certificate> certChain = new Collection<X509Certificate>();

        X509Certificate2 cert = new X509Certificate2(certByteArray);

        X509Certificate regularCert = new X509CertificateParser()
            .ReadCertificate(cert.GetRawCertData());

        certChain.Add(regularCert);

        return certChain;
    }

編輯: 簽名的PDF

編輯:調整 CreateFinalSignature 以使用保存到 .txt 文件中的 messageHash。 結果是一樣的。 簽名的PDF

private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
    {
        var messageHashFilePath = $"{preparedSigPdfFilePath}.messageHash-b64.txt";
        string hashString = System.IO.File.ReadAllText(messageHashFilePath);
        byte[] hash = Convert.FromBase64String(hashString);

        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(hash, signedHash, certificatesChain);
                MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
            }
        }
    }

哈希是相同的,如下所示。 在保存之前和從文件讀取之后,我放置了一些斷點來嘗試捕獲值。

保存前的字節數組:

[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133

從 CreateFinalSignature 中的 .txt 文件讀取的字節數組:

[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133

編輯:散列authenticatedAttributeBytes ,然后返回由客戶端簽名的散列。

嘗試了 3 種不同的散列方式,結果相同:

PdfHash = DigestAlgorithms.Digest(new MemoryStream(authenticatedAttributeBytes), messageDigest)

PdfHash = SHA256.Create().ComputeHash(authenticatedAttributeBytes)

PdfHash = SHA256Managed.Create().ComputeHash(authenticatedAttributeBytes)

GetPDFHash 的使用

byte[] bytesToSign = GetPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"), Convert.FromBase64String(dto.base64certificateValue));

SignPDFHash 的使用

SignPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"),Convert.FromBase64String(dto.base64signature), Convert.FromBase64String(dto.base64certificateValue));

編輯(29.3.2020):我檢查了我的客戶端,沒有發現任何問題。 我選擇 RSASSA-PKCS1-v1_5 alg 來獲取簽名並在之后成功驗證。 在其他一些問題中,我發現在服務器和客戶端之間傳輸字節數組可能是一個問題,但我已經檢查過,base64 和字節數組的值都相同。

決定在文本編輯器中打開 PDF 並將其與定期簽名的 PDF 進行比較(相同的文本內容,只是通過 Adob​​e Reader 直接簽名)。

令我困擾和擔心的是,用 iText 簽名的 PDF 缺少大量“文本”,而直接簽名的 PDF 文件卻有。

還有什么我可以提供的可能的進一步分析嗎? 我已經看到 Stack Overflow 上的人們無法解決這個問題的趨勢,有些人甚至完全放棄了它。 我不想也不能那樣做,想追根究底。

通過 Adob​​e Reader 直接簽名 使用 iText 延遲簽名

編輯30.3.2020:如上所述,我對 AuthenticatedAttributeBytes 進行了哈希處理

PdfHash = SHA256Managed.Create().ComputeHash(authenticatedAttributeBytes);

AuthenticatedAttributeBytes

49 75 48 24 6 9 42 134 72 134 247 13 1 9 3 49 11 6 9 42 134 72 134 247 13 1 7 1 48 47 6 9 42 134 72 134 247 13 1 9 4 49 34 4 32 122 115 111 54 139 240 60 168 176 67 64 158 55 107 233 48 77 220 19 208 139 187 42 1 141 149 20 241 151 80 31 79 

AuthenticatedAttributeBytes - 散列

33 25 105 92 244 51 72 93 179 135 158 84 249 178 103 91 236 247 253 35 232 124 169 112 108 214 63 206 206 2 88 107 

(返回給客戶端)AuthenticatedAttributeBytes - 散列和 base64 編碼

IRlpXPQzSF2zh55U+bJnW+z3/SPofKlwbNY/zs4CWGs=

哈希簽名(簽名)

76 13 184 229 123 212 2 8 140 24 34 88 95 31 255 142 105 220 204 186 172 110 61 75 156 44 185 62 81 209 238 226 67 133 115 247 76 24 182 144 38 164 71 92 124 140 77 16 212 43 52 156 173 90 163 116 0 124 119 119 103 8 12 74 147 1 207 51 156 104 52 231 112 125 115 140 28 105 160 117 235 199 224 166 30 220 111 35 165 49 18 85 253 194 112 254 142 117 46 58 87 13 110 161 151 228 95 238 115 171 70 117 203 103 204 222 233 42 163 37 105 91 177 117 190 238 135 137 162 6 54 125 108 64 148 219 7 198 93 117 12 164 130 123 213 197 233 173 145 77 209 11 166 91 29 137 142 25 20 96 90 130 251 169 234 9 44 245 230 20 46 243 254 98 179 98 148 87 104 151 228 246 231 23 94 134 144 84 177 219 235 90 11 130 33 139 94 155 73 112 60 88 53 150 59 49 184 100 210 82 32 71 66 168 21 167 91 141 94 239 221 156 96 23 132 147 237 15 237 232 112 214 224 61 117 46 143 208 41 64 13 128 44 69 135 172 113 58 8 85 5 176 192 254 107 92 

(從客戶端接收)哈希簽名(簽名)- base64

TA245XvUAgiMGCJYXx//jmnczLqsbj1LnCy5PlHR7uJDhXP3TBi2kCakR1x8jE0Q1Cs0nK1ao3QAfHd3ZwgMSpMBzzOcaDTncH1zjBxpoHXrx+CmHtxvI6UxElX9wnD+jnUuOlcNbqGX5F/uc6tGdctnzN7pKqMlaVuxdb7uh4miBjZ9bECU2wfGXXUMpIJ71cXprZFN0QumWx2JjhkUYFqC+6nqCSz15hQu8/5is2KUV2iX5PbnF16GkFSx2+taC4Ihi16bSXA8WDWWOzG4ZNJSIEdCqBWnW41e792cYBeEk+0P7ehw1uA9dS6P0ClADYAsRYescToIVQWwwP5rXA==

簽名字節(從 base64 解碼)與客戶端記錄的 uint8array 匹配。 在此處輸入圖片說明

您的原始代碼

MyExternalEmptySignatureContainer.Sign您可以使用 PDF 范圍流的裸散列正確確定經過身份驗證的屬性:

var authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

但是,在檢查您的示例文件時,我發現簽名的 Message Digest 屬性包含嵌入DigestInfo對象中的哈希,在其他使用sha256Prefix作品中,您將其應用於MyExternalEmptySignatureContainer.Sign中的消息摘要的副本。

因此,顯然,當您在MyExternalSignatureContainer.Sign重新創建經過身份驗證的屬性時

return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);

你在this.Hash使用了錯誤的值。 這有兩個影響,一方面顯然 Message Digest 屬性的值現在不正確,另一方面為原始、正確的驗證屬性創建的簽名值與您不正確的屬性不匹配。 因此,生成的 PDF 簽名是雙重錯誤的。

要解決此問題,請在此處使用正確的哈希值,即沒有該前綴的 PDF 范圍流的哈希值。

由於您沒有展示如何使用GetPDFHashSignPDFHash方法,我無法更准確地指出錯誤。

您更新的代碼

事實上,現在正確的散列在 Message Digest 屬性中,但簽名仍然簽署了錯誤的散列,以防你的新示例:

Signed Attributes Hash: 54B2F135A542EEAA55270AB19210E363D00A7684405403E89B170591A7BCAB5F
Decrypted signature digest: 22D906E686A83FA1A490895A21CD6F9A9272C13FB9B16D8A6E862168458F3640

原因可能是您的MyExternalEmptySignatureContainer屬性PdfHash內容不是哈希,而是完整的經過身份驗證的屬性字節,參見。 MyExternalEmptySignatureContainer.Sign

var authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

PdfHash = authenticatedAttributeBytes;

您可能必須計算authenticatedAttributeBytes的哈希值並將其放入PdfHash

但是,由於您沒有展示如何使用GetPDFHashSignPDFHash方法,因此只能猜測。

您記錄的哈希值

3 月 30 日,您共享了一次運行中傳輸的相關數據的日志。 特別是:

AuthenticatedAttributeBytes - 散列

33 25 105 92 244 51 72 93 179 135 158 84 249 178 103 91 236 247 253 35 232 124 169 112 108 214 63 206 206 2 88 107

哈希簽名(簽名)

 76 13 184 229 123 212 2 8 140 24 34 88 95 31 255 142 105 220 204 186 172 110 61 75 156 44 185 62 81 209 238 226 67 133 115 247 76 24 182 144 38 164 71 92 124 140 77 16 212 43 52 156 173 90 163 116 0 124 119 119 103 8 12 74 147 1 207 51 156 104 52 231 112 125 115 140 28 105 160 117 235 199 224 166 30 220 111 35 165 49 18 85 253 194 112 254 142 117 46 58 87 13 110 161 151 228 95 238 115 171 70 117 203 103 204 222 233 42 163 37 105 91 177 117 190 238 135 137 162 6 54 125 108 64 148 219 7 198 93 117 12 164 130 123 213 197 233 173 145 77 209 11 166 91 29 137 142 25 20 96 90 130 251 169 234 9 44 245 230 20 46 243 254 98 179 98 148 87 104 151 228 246 231 23 94 134 144 84 177 219 235 90 11 130 33 139 94 155 73 112 60 88 53 150 59 49 184 100 210 82 32 71 66 168 21 167 91 141 94 239 221 156 96 23 132 147 237 15 237 232 112 214 224 61 117 46 143 208 41 64 13 128 44 69 135 172 113 58 8 85 5 176 192 254 107 92

但是,使用證書中的公鑰解密后一個簽名的哈希值,會得到一個包含此哈希的DigestInfo對象:

136 138 205 115 82 228 115 151 231 220 177 93 171 239 123 224 245 180 234 166 132 201 244 54 69 22 18 16 115 223 70 193

因此,無論您的客戶端代碼做什么,它都不會為您預先散列的 AuthenticatedAttributeBytes 創建簽名 可能它再次散列散列字節,可能它散列它們的 base64 表示,可能它使用一些隨機數,但它不是你期望的那樣。

您應該找出您的客戶端代碼實際執行的操作並修復它或為其提供所需的數據。

例如,如果您的客戶端代碼無法避免重新散列數據,則將未散列的已驗證屬性字節提供給它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM