title: PDF External Signature
body: <p><img src="https://i.stack.imgur.com/nTMnw.png" alt="enter image description here"></p>
  
  <p><strong>Client=My application</strong>, 
  <strong>Server=MSSP(Mobile Signature Services Provider)</strong></p>
  
  <p>Server is sign only the hash values. </p>
  
  <p>Data to be sign:</p>
  
  <p>*Base64 encoded SHA-1 digest of data to be signed. (28 characters)</p>
  
  <p>*Base64 encoded SHA-256 digest of data to be signed. (44 characters)</p>
  
  <p>*Base64 encoded SHA-384 digest of data to be signed. (64 characters)</p>
  
  <p>*Base64 encoded SHA-512 digest of data to be signed. (88 characters)</p>
  
  <p>*Base64 encoded DER encoded PKCS#1 DigestInfo to sign.</p>
  
  <p>I want to external signature for pdf. I wrote the below code. But I get an error when opened the document with adobe.</p>
  
  <p>Error:</p>
  
  <p>modified or corrupted after the document is signed </p>
  
  <p>Note: I use MSSP(Mobile Signature Service Provider) architecture. DataToBeSigned.length should be 44 for SHA256 algorithm.</p>
  
  <p>My operation code:</p>
  
  <pre><code>  import com.itextpdf.text.Rectangle;
  import com.itextpdf.text.pdf.PdfDate;
  import com.itextpdf.text.pdf.PdfDictionary;
  import com.itextpdf.text.pdf.PdfName;
  import com.itextpdf.text.pdf.PdfReader;
  import com.itextpdf.text.pdf.PdfSignature;
  import com.itextpdf.text.pdf.PdfSignatureAppearance;
  import com.itextpdf.text.pdf.PdfStamper;
  import com.itextpdf.text.pdf.PdfString;
  import com.itextpdf.text.pdf.security.DigestAlgorithms;
  import com.itextpdf.text.pdf.security.ExternalDigest;
  import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
  import com.itextpdf.text.pdf.security.PdfPKCS7;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.InputStream;
  import java.security.GeneralSecurityException;
  import java.security.MessageDigest;
  import java.security.Security;
  import java.security.cert.X509Certificate;
  import java.util.Calendar;
  import java.util.HashMap;
  import org.bouncycastle.jce.provider.BouncyCastleProvider;
  
  /**
   *
   * @author murat.demir
   */
  public class PdfSignOperation {
  
      private byte[] content = null;
      private X509Certificate x509Certificate;
      private PdfReader reader = null;
      private ByteArrayOutputStream baos = null;
      private PdfStamper stamper = null;
      private PdfSignatureAppearance sap = null;
      private PdfSignature dic = null;
      private HashMap&lt;PdfName, Integer&gt; exc = null;
      private ExternalDigest externalDigest = null;
      private PdfPKCS7 sgn = null;
      private InputStream data = null;
      private byte hash[] = null;
      private Calendar cal = null;
      private byte[] sh = null;
      private byte[] encodedSig = null;
      private byte[] paddedSig = null;
      private PdfDictionary dic2 = null;
      private X509Certificate[] chain = null;
  
      static {
          Security.addProvider(new BouncyCastleProvider());
      }
  
      public PdfSignOperation(byte[] content, X509Certificate cert) {
          this.content = content;
          this.x509Certificate = cert;
      }
  
      public byte[] getHash() throws Exception {
          reader = new PdfReader(new ByteArrayInputStream(content));
  
          baos = new ByteArrayOutputStream();
          stamper = PdfStamper.createSignature(reader, baos, '\0');
          sap = stamper.getSignatureAppearance();
  
          sap.setReason("Test");
          sap.setLocation("On a server!");
          sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
          sap.setCertificate(x509Certificate);
  
          dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
          dic.setReason(sap.getReason());
          dic.setLocation(sap.getLocation());
          dic.setContact(sap.getContact());
          dic.setDate(new PdfDate(sap.getSignDate()));
          sap.setCryptoDictionary(dic);
  
          exc = new HashMap&lt;PdfName, Integer&gt;();
          exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
          sap.preClose(exc);
  
          externalDigest = new ExternalDigest() {
              @Override
              public MessageDigest getMessageDigest(String hashAlgorithm)
                      throws GeneralSecurityException {
                  return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
              }
          };
  
          chain = new X509Certificate[1];
          chain[0] = x509Certificate;
  
          sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
          data = sap.getRangeStream();
          cal = Calendar.getInstance();
          hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
          sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
          sh = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));
          return sh;
      }
  
      public String complateToSignature(byte[] signedHash) throws Exception {
          sgn.setExternalDigest(signedHash, null, "RSA");
          encodedSig = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
          paddedSig = new byte[8192];
  
          System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
          dic2 = new PdfDictionary();
  
          dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
          sap.close(dic2);
  
          return Base64.encodeBytes(baos.toByteArray());
      }
  
  }
  </code></pre>
  
  <p>My test code :</p>
  
  <pre><code>  public static void main(String[] args) throws Exception {
      TokenService.refreshAllTokens();
      String alias = "alias";
      String pin = "1234";
      TokenService.setAliasPin(alias, pin);
  
  
      File pdf = new File("E:/sample.pdf");
      FileInputStream is = new FileInputStream(pdf);
      byte[] content = new byte[is.available()];
      is.read(content);
  
      X509Certificate certificate = null;
      for (CertInfo certInfo : TokenService.getCertificates().values()) {
          if (certInfo.cert != null) {
              certificate = certInfo.cert;
          }
      }
  
      PdfSignOperation operation = new PdfSignOperation(content, certificate);
  
      byte[] hash = operation.getHash();
  
      //simule control for mobile signature.
      String encodeData = Base64.encodeBytes(hash);
      if (encodeData.length() != 44) {
          throw new Exception("Sign to data must be 44 characters");
      }
  
      // This function is local in the test run function (Simulated MSSP mobile signature)
      // return  a signed message digest
      byte[] signedData = TokenService.sign(encodeData, alias);
  
      //Combine signed hash value with pdf.
      System.out.println(operation.complateToSignature(signedData));
  }
  </code></pre>
  
  <p><a href="https://www.dropbox.com/s/3grt2i9dre23zuk/remote_token6.pdf" rel="nofollow noreferrer">SignedPDF</a> </p>
  
  <p>Update:</p>
  
  <p>I tried with the old library version and successful signature operation. My new code:</p>
  
  <pre><code>InputStream data = sap.getRangeStream();
  
  X509Certificate[] chain = new X509Certificate[1];
  chain[0] = userCert;
  
  PdfPKCS7 sgn = new PdfPKCS7(null, chain, null, algorithm, null, false);
  
  MessageDigest digest = MessageDigest.getInstance("SHA256", "BC");
  byte[] buf = new byte[8192];
  int n;
  while ((n = data.read(buf, 0, buf.length)) &gt; 0) {
      digest.update(buf, 0, n);
  }
  byte hash[] = digest.digest();
  logger.info("PDF hash  created");
  Calendar cal = Calendar.getInstance();
  
  byte[] ocsp = null;
  byte sh[] = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp);
  sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
  
  final String encode = Utils.base64Encode(sh);
  SignatureService service = new SignatureService();
  
  logger.info("PDF hash  sended to sign for web service");
  MobileSignResponse signResponse = service.mobileSign(mobilePhone, signText, encode, timeout, algorithm, username, password, "profile2#sha256", signWsdl);
  
  if (!signResponse.getStatusCode().equals("0")) {
      throw new Exception("Signing fails.\n" + signResponse.getStatusMessage());
  }
  
  byte[] signedHashValue = Utils.base64Decode(signResponse.getSignature());
  sgn.setExternalDigest(signedHashValue, null, "RSA");
  
  byte[] paddedSig = new byte[csize];
  
  
  byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null, ocsp);
  System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
  if (csize + 2 &lt; encodedSig.length) {
      throw new Exception("Not enough space for signature");
  }
  
  PdfDictionary dic2 = new PdfDictionary();
  dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
  sap.close(dic2);
  logger.info("Signing successful");
  </code></pre>