title: iText7 deferred signed pdf document shows “the document has been altered or corrupted since the signature was applied”
body: <p>I checked with other similar issues on Stackoverflow, but it doesn’t work on my case.</p>
  <p>Situation: I am developing an application which needs to sign the pdf document. The signing key is held by another company, let’s say it’s CompanyA.</p>
  <p>I did the following steps:</p>
  <ol>
  <li>Got the pdf document to sign ready.</li>
  <li>Created a Temp pdf file which added an Empty Signature in the original pdf.</li>
  <li>Read the Temp pdf to get the message digest. (Encode it to base64)</li>
  <li>Send the message digest (Base64 encoded) to the CompanyA to get signed.</li>
  <li>Get the signed digest (base64 encoded) from CompanyA.</li>
  <li>Do the base64 decoding. And embedded the result into the Temp pdf to get the final signed pdf.</li>
  </ol>
  <p>Everything goes well and I can get the final signed pdf. But When I open it in Adobe reader, it says “the document has been altered or corrupted since the signature was applied”.</p>
  <p>I used this <code>getHashBase64Str2Sign</code> to get the message digest (in base64). This method calls the <code>emptySignature()</code> method to create the Temp file with empty signature, and then calls the <code>getSignatureHash()</code> method to read the Temp file to get the message digest.</p>
  <pre><code>public static String getHashBase64Str2Sign() {
      try {
          // Add BC provider
          BouncyCastleProvider providerBC = new BouncyCastleProvider();
          Security.addProvider(providerBC);
  
          // Create parent path of dest pdf file, if not exist
          File file = new File(DEST).getParentFile();
          if (!file.exists()) {
              file.mkdirs();
          }
  
  
          CertificateFactory factory = CertificateFactory.getInstance(&quot;X.509&quot;);
          Certificate[] chain = new Certificate[1];
          try (InputStream certIs = new FileInputStream(CERT)) {
              chain[0] = factory.generateCertificate(certIs);
          }
  
          // Get byte[] hash
          DeferredSigning app = new DeferredSigning();
  
          app.emptySignature(SRC, TEMP, &quot;sig&quot;, chain);
  
          byte[] sh = app.getSignatureHash(TEMP, &quot;SHA256&quot;, chain);
  
          // Encode byte[] hash to base64 String and return
          return Base64.getEncoder().encodeToString(sh);
      } catch (IOException | GeneralSecurityException e) {
          e.printStackTrace();
          return null;
      }
  }
  
      
  
  private void emptySignature(String src, String dest, String fieldname, Certificate[] chain)
          throws IOException, GeneralSecurityException {
      PdfReader reader = new PdfReader(src);
      PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
      PdfSignatureAppearance appearance = signer.getSignatureAppearance();
      appearance.setPageRect(new Rectangle(100, 500, 200, 100));
      appearance.setPageNumber(1);
      appearance.setCertificate(chain[0]);
      appearance.setReason(&quot;For test&quot;);
      appearance.setLocation(&quot;HKSAR&quot;);
      signer.setFieldName(fieldname);
  
      /*
       * ExternalBlankSignatureContainer constructor will create the PdfDictionary for
       * the signature information and will insert the /Filter and /SubFilter values
       * into this dictionary. It will leave just a blank placeholder for the
       * signature that is to be inserted later.
       */
      IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite,
              PdfName.Adbe_pkcs7_detached);
      
  
      // Sign the document using an external container.
      // 8192 is the size of the empty signature placeholder.
      signer.signExternalContainer(external, 100000);
  }
  
  private byte[] getSignatureHash(String src, String hashAlgorithm, Certificate[] chain)
          throws IOException, GeneralSecurityException {
      InputStream is = new FileInputStream(src);
      // Get the hash
      BouncyCastleDigest digest = new BouncyCastleDigest();
      PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
  
      byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(sgn.getHashAlgorithm()));
      return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
  }
  
  private void createSignature(String src, String dest, String fieldName, byte[] sig)
          throws IOException, GeneralSecurityException {
      PdfReader reader = new PdfReader(src);
      try (FileOutputStream os = new FileOutputStream(dest)) {
          PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
  
          IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
  
          // Signs a PDF where space was already reserved. The field must cover the whole
          // document.
          PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
      }
  }
  </code></pre>
  <p>Then, the message digest is sent to CompanyA for signing. After I got the signed digest from CompanyA (which is base64 encoded), I call the <code>embedSignedHashToPdf()</code> method to get the signed pdf document.</p>
  <pre><code>public static void embedSignedHashToPdf(String signedHash) {
      try {
          byte[] sig = Base64.getDecoder().decode(signedHash);
          // Get byte[] hash
          DeferredSigning app = new DeferredSigning();
          app.createSignature(TEMP, DEST, &quot;sig&quot;, sig);
      } catch (IOException | GeneralSecurityException e) {
          e.printStackTrace();
      }
  }
  
  class MyExternalSignatureContainer implements IExternalSignatureContainer {
  
      protected byte[] sig;
  
      public MyExternalSignatureContainer(byte[] sig) {
          this.sig = sig;
      }
  
      @Override
      public void modifySigningDictionary(PdfDictionary signDic) {
  
      }
  
      @Override
      public byte[] sign(InputStream arg0) throws GeneralSecurityException {
          return sig;
      }
  
  }
  
  </code></pre>
  <p>At last, I can get the signed pdf document, but it shows error in Adobe Reader, like this:</p>
  <p><img src="https://i.stack.imgur.com/FmOCG.png" alt="Adobe shows error on signature" /></p>
  <p>Please check the original pdf, temp pdf, and the final signed pdf file as follows:</p>
  <p><a href="https://drive.google.com/file/d/1M79V9ZZhUaOn-HtbmmsCnUg6Nnr62LVZ/view?usp=sharing" rel="nofollow noreferrer">Original pdf - helloworld.pdf</a></p>
  <p><a href="https://drive.google.com/file/d/1DvN9CoTfuWlSkkPY6klao_m7LMX2Wm1m/view?usp=sharing" rel="nofollow noreferrer">Temp pdf - helloworld_empty_signed.pdf</a></p>
  <p><a href="https://drive.google.com/file/d/15Oi58xbW2Bzp10Ul4S2CAAWcrgO0ogeU/view?usp=sharing" rel="nofollow noreferrer">Final pdf - helloworld_signed_ok.pdf</a></p>