将签名哈希集成到原始 PDF 中

Integrate Signed Hash into original PDF

我在原始 PDF 中集成了签名哈希,但我仍然对签名的有效性有错误。据说签名后改了一个pdf

以下步骤:我计算哈希,然后发送它进行签名,最后我得到哈希符号,然后继续集成到原始 pdf 中

package com.example.hashdocument;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import com.lexpersona.commons.utils.ProcessLauncher;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.*;


public class Test2 {

    private static final String SRC = "B:/Hash-et-Reconstitution/tmp/Doc_test.pdf";
    private static final String DST = "B:/Hash-et-Reconstitution/tmp/Doc_test_DST.pdf";
    private static final String HASH = "B:/Hash-et-Reconstitution/tmp/Doc_test_hashed.hash";
    
    private static final String PATH_BAT = "C:/Repo_LP7/lpcommand.bat";
    private static final String PIN = "123456";
    private static final String CERTIFICATE = "C:/lp7command/tools/certificate.p12";
    private static final String SIGNED_HASH = "B:/Hash-et-Reconstitution/tmp/doc_signed.hash";
    
    private static byte[] readFileToByteArray(File file){
        FileInputStream fis = null;
        byte[] bArray = new byte[(int) file.length()];
        try{
          fis = new FileInputStream(file);
          fis.read(bArray);
          fis.close();                   
        }catch(IOException ioExp){
          ioExp.printStackTrace();
        }
        return bArray;
      }
    public static File bytesToFile(byte[] fileByte,String pathFile)  {
        File file = new File(pathFile);
        try {
            OutputStream os = new FileOutputStream(file);
            os.write(fileByte);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }
    public static byte[] signDocument() throws IOException {
        
        
        ProcessLauncher p = new ProcessLauncher(System.out, System.err);
        int exec;
        exec = p.exec("cmd.exe /c "+PATH_BAT+" <nul "+ SIGNED_HASH +" "+ PIN+" "
                + HASH+" "+CERTIFICATE, null, null);

        byte[] signedHash = readFileToByteArray(new File(SIGNED_HASH));
        
        return signedHash;
    }
     
    
    public static void main(String[] args) throws IOException, GeneralSecurityException, DocumentException {
        
        PdfSignatureAppearance appearance = null;
        ByteArrayOutputStream  os = null;
        String hash_document = "";
        
        InputStream data = null;
        int contentEstimated = 8192;

        PdfReader reader = new PdfReader(SRC);

        reader.unethicalreading = true;
        reader.setAppendable(true);

        int pdfPagenumber = 1;

        pdfPagenumber = reader.getNumberOfPages(); // Sign on last page

        os = new ByteArrayOutputStream ();
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=10=]', null, true);

        Calendar cal = Calendar.getInstance();        
        appearance = stamper.getSignatureAppearance();        
        appearance.setSignDate(cal);
        //appearance.setAcro6Layers(false);
        appearance.setReason("Signature de contrat");
        appearance.setLocation("MAROC");              
        appearance.setImage(null);
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);

        Rectangle rect = new Rectangle(300, 300, 20, 20);        
        appearance.setVisibleSignature(rect, pdfPagenumber, null);

        HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
        exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2));
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        dic.setReason(appearance.getReason()); 
        dic.setLocation(appearance.getLocation());
        dic.setContact(appearance.getContact());
        dic.setDate(new PdfDate(appearance.getSignDate()));
        appearance.setCryptoDictionary(dic);

        appearance.preClose(exc);

        data = appearance.getRangeStream();    

        MessageDigest messageDigest;
        String provider = null;
        String hashAlgorithm = DigestAlgorithms.SHA256;
        if (provider == null){
            messageDigest = MessageDigest.getInstance(hashAlgorithm);
        }else {
            messageDigest = MessageDigest.getInstance(hashAlgorithm,provider);
        }


        int read = 0;
        byte[] buff = new byte[contentEstimated];

        while ((read = data.read(buff, 0, contentEstimated)) > 0)
        {
            messageDigest.update(buff,0,read);                      
        }
        byte[] hashDigest = messageDigest.digest();

        byte[] documentHash = org.bouncycastle.util.encoders.Hex.encode(hashDigest);

        //eSign Start        
        hash_document = new String(documentHash, "UTF-8");        
        System.out.println("Document Hash :"+hash_document);
        

        
        PrintStream out = new PrintStream(new FileOutputStream(HASH));
        out.print(hash_document);
        
        
        byte[] hashdocumentByte = signDocument();
        
        //////////////////// ADD SIGNED BYTES/HASH TO PDF DOCUMENT.      
        int contentEstimated2 = 8192;
        byte[] paddedSig = new byte[contentEstimated2];
        byte[] signedDocByte = hashdocumentByte;
        
        System.arraycopy(signedDocByte, 0, paddedSig, 0, signedDocByte.length);
        
        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

        appearance.close(dic2);
        
        try(OutputStream outputStream = new FileOutputStream(DST)) {
            os.writeTo(outputStream);
        }

        os.close(); 
        
    }
}

您的 signDocument 方法显然不接受预先计算的哈希值,但似乎计算您提供的数据的哈希值,在您的情况下,您的哈希值的(小写)十六进制表示已经算好了

在您的第一个示例文档中,您有这些值(所有散列都是 SHA256 散列):

  • 要签名的字节范围的哈希值:

    91A9F5EBC4F2ECEC819898824E00ECD9194C3E85E4410A3EFCF5193ED7739119
    
  • "91a9f5ebc4f2ecec819898824e00ecd9194c3e85e4410a3efcf5193ed7739119".getBytes() 的哈希值:

    2F37FE82F4F71770C2B33FB8787DE29627D7319EE77C6B5C48152F6E420A3242
    
  • 嵌入签名容器签名的哈希值:

    2F37FE82F4F71770C2B33FB8787DE29627D7319EE77C6B5C48152F6E420A3242
    

在您的第一个示例文档中,您有这些值(所有散列也是 SHA256 散列):

  • 要签名的字节范围的哈希值:

    79793C58489EB94A17C365445622B7F7945972A5A0BC4C93B6444BEDFFA5A5BB
    
  • "79793c58489eb94a17c365445622b7f7945972a5a0bc4c93b6444bedffa5a5bb".getBytes() 的哈希值:

    A8BCBC6F9619ECB950864BFDF41D1B5B7CD33D035AF95570C426CF4B0405949B
    
  • 嵌入签名容器签名的哈希值:

    A8BCBC6F9619ECB950864BFDF41D1B5B7CD33D035AF95570C426CF4B0405949B
    

因此,您必须更正您的 signDocument 方法以正确解释数据,或者您必须给它一个包含要消化的整个范围流的字节数组。

您如何看待这段代码:首先我计算哈希值并发送到服务器 A 进行签名

    PdfReader reader = new PdfReader(SRC);
    FileOutputStream os = new FileOutputStream(TEMP);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=10=]');
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
    //appearance.setCertificate(chain[0]);
    ExternalSignatureContainer external = new 
    ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(appearance, external, 8192);

    InputStream inp = appearance.getRangeStream();  

    BouncyCastleDigest digest = new BouncyCastleDigest();


     byte[] hash = DigestAlgorithms.digest(inp, digest.getMessageDigest("SHA256"));
     System.out.println("hash to sign : "+ hash);
     bytesToFile(hash, HASH);
     byte[] hashdocumentByte = TEST.signed_hash(hash);
     
     PdfReader reader2 = new PdfReader(TEMP);
    FileOutputStream os2 = new FileOutputStream(DEST);
    ExternalSignatureContainer external2 = new 
    MyExternalSignatureContainer(hashdocumentByte,null);
    MakeSignature.signDeferred(reader2, "sig", os2, external2);

然后在我签署哈希的服务器 B 中:

        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);

        // we load our private key from the key store
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(new FileInputStream(CERTIFICATE), PIN);
        String alias = (String)ks.aliases().nextElement();
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKey pk = (PrivateKey) ks.getKey(alias, PIN);
        
        
     PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", null);
       BouncyCastleDigest digest = new BouncyCastleDigest();
        Calendar cal = Calendar.getInstance();
        String hashAlgorithm = signature.getHashAlgorithm();
        System.out.println(hashAlgorithm);
        PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, digest, false);

        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
        byte[] extSignature = signature.sign(sh);

        System.out.println(signature.getEncryptionAlgorithm());
        sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
        return sgn.getEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);