How do I create custom digital signatures?

Security, Manipulate PDF, Digital signatures

This article shows how one can use a custom digital signatures in PDFKit.NET to sign PDF files.


PDFKit.Net supports signing PDF documents with digital signatures. There are two aspects to this mechanism:

  • The readable appearance of signature fields in the PDF file, i.e. what people see.
  • The actual digital signature that is used to sign the document.

We will have a look at both below. We will start with the appearance of signatures. We assume that we are dealing with a PDF file that already has a signature field, and that you have a basic understanding of how fields and widgets work in PDFKit.NET.

Signature Field Appearance.

If you intend to sign a signature field in PDFKit.Net, you will need to set a signature handler for the field that performs the actual signing. This handler provides a Name and a DistinguishedName (DN) that end up in the appearance of the signature.

Next to this, there are a number of field and widget properties that control the appearance, such as the ContactInfo, Location and Reason fields of the field. You an also set the SignedAppearance of the SignatureWidget that is associated with the field. The code below has been taken from the signexisting sample:

1 // open certicate store. 2 Pkcs12Store store = null; 3 using (FileStream storeFile = new FileStream(@"..\..\BobStapleton.pfx", FileMode.Open, FileAccess.Read)) 4 { 5 store = new Pkcs12Store(storeFile, "studentpassword"); 6 } 7 8 // let the factory decide which type of signature handler should be used. 9 SignatureHandler handler = StandardSignatureHandler.Create(store); 10 11 // associate the handler with the signature field. 12 13 sigField.SignatureHandler = handler; 14 15 // set optional info. 16 sigField.ContactInfo = "+31 (0)77 4748677"; 17 sigField.Location = "The Netherlands"; 18 sigField.Reason = "I hereby declare!"; 19 20 //optional code to set image: 21 //enumerate widgets 22 23 foreach( SignatureWidget widget in field.Widgets ) 24 { 25 SignatureAppearance signedAppearance = new SignatureAppearance(); 26 signedAppearance.Style = SignatureAppearanceStyle.ImageAndText; 27 signedAppearance.Bitmap = new System.Drawing.Bitmap( @"..\..\..\inputdocuments\logo_pdfkit.gif" ); 28 widget.SignedAppearance = signedAppearance; 29 widget.BackgroundColor = System.Drawing.Color.LightPink; 30 }

This will lead to a Signature appearance that looks as follows:


This appearance however has nothing to do with the actual digital signature that is applied to the document. It is just some human readable information that allows users to see what is going on.

In the end, you can attach any image you want to the signature field and have that displayed. In many cases you will want to attach an image of a real human signature, possibly one that has been obtained from a signing pad.

Custom Digital Signature

The actual digital signature is not the appearance that you see, but a sequence of bytes that gets computed by the signature handler at the moment that the document gets saved. This can only be done upon saving, because these signature bytes need to be derived from parts of the document in the exact form that they are saved. This is done, so that it becomes difficult or even impossible to tamper with the document without invalidating the signature: if the document gets changed, the signature bytes will no longer match the document data, and readers like Adobe Reader will notice this:


The standard signature handler will use a public/private key mechanism to compute these bytes. It will use a private key to generate the signature bytes. Nobody but the owner of this private key will be able to generate a sequence of matching signature bytes for a particular document, so if someone tries to tamper with the document, he will not be able to generate a correct sequence of signature bytes that matches the public key of the original signer. So if you encounter a document that has a correct sequence that "belongs" to the public key of the signed, you know that the signature is correct and that the document is unaltered.

There may be times however that you want to write your own digital signature to a file, based on some other mechanism. PDFKit.NET allows this via a custom signature handler. The customSignVerifyAndUpdates sample that is included in the PDFKit.NET distribution shows you how to do this. Below we will clarify its use further.

The first step is to setup up a SignatureHandler subclass. We will override a number of methods in order to provide our own signature handler implementation.

1 private class CustomSignatureHandler : SignatureHandler 2 { 3 public CustomSignatureHandler(string name) 4 { 5 _name = name; 6 } 7 public override bool CanSign 8 { 9 get { return true; } 10 } 11 12 public override bool CanVerify 13 { 14 get { return true; } 15 } 16 17 public override string Filter 18 { 19 get { return "CustomSignatureHandler"; } 20 } 21 22 public override int Revision 23 { 24 get 25 { 26 return 1; 27 } 28 } 29 public override string Name 30 { 31 get { return _name; } 32 } 33 34 public override byte[] Sign (byte[] bytesToSign) 35 { 36 ... 37 } 38 39 public override bool Verify (byte[] bytesToVerify, byte[] digest, byte[][] certificates) 40 { 41 ... 42 } 43 44 string _name = null; 45 }

You will see the following overrides:

  • CanSign: Indicates that this handler can be used to sign documents.
  • CanVerify: Indicates that this handler can be used to verify signatures of this type.
  • Filter: This is the name of our signature handler. PDF Readers will use this to identify the correct handler and determine whether they can verify the signature. For unknown handlers they will typically pop up a message that they cannot verify the document without an appropriate plugin.
  • Revision: The revision of our handler, which is useful for identifying future variants.
  • Name: The name of the signer. In our implementation we just pass this to the constructor of the handler, and return it when called for.
  • Sign: The method that performs the actual signing. This method must be implemented when CanSign returns true. We will have a further look at this below.
  • Verify: The method that implements signature verification. This method must implemented when CanVerify is true. We will have a further look at this below.

In the code snippet below we will have a look at a possible Sign implementation.

1 public override byte[] Sign (byte[] bytesToSign) 2 { 3 UTF8Encoding utf8 = new UTF8Encoding(); 4 byte[] encodedName = utf8.GetBytes(Name); 5 6 ulong digestValue = 0; 7 8 // Compute digest for all bytes of the document. 9 for (int i = 0; i < bytesToSign.Length; i++) 10 { 11 digestValue += bytesToSign[i]; 12 } 13 // Include the length of the name, and the name itself in the digest as well. 14 digestValue += (uint)encodedName.Length; 15 for (int i = 0; i < encodedName.Length; i++) 16 { 17 digestValue += encodedName[i]; 18 } 19 20 // We assume here that the encodedName is not longer than 256 bytes. 21 byte[] digest = new byte[8 + 1 + 256]; 22 digest[0] = (byte)(digestValue & 0xff); 23 digest[1] = (byte)(digestValue >> 8 & 0xff); 24 digest[2] = (byte)(digestValue >> 16 & 0xff); 25 digest[3] = (byte)(digestValue >> 24 & 0xff); 26 digest[4] = (byte)(digestValue >> 32 & 0xff); 27 digest[5] = (byte)(digestValue >> 40 & 0xff); 28 digest[6] = (byte)(digestValue >> 48 & 0xff); 29 digest[7] = (byte)(digestValue >> 56 & 0xff); 30 31 digest[8] = (byte) encodedName.Length; 32 for (int i = 0; i < encodedName.Length; i++ ) 33 { 34 digest[8 + 1 + i] = encodedName[i]; 35 } 36 37 return digest; 38 } 39 }

The bytesToSign argument provides a sequence of bytes over which the signature needs to be computed. These bytes are a collection of bytes that PDFKit.NET has collected from the actual document as it will be stored. The goal of the Sign method is to return an array of signature bytes. These bytes - called a digest - will be stored in the document as well.

The Sign method above provides a very simple implementation. It just adds the value of all the input bytes. This will only provide minimal security against tampering, as it is easy to modify both a document and this digest so that they still match. Obviously a real implementation should be more serious than this. The point of this sample however is that you can make this as complex as you like.

In addition to the digest, we also include the name of the signer and we include this name in the digest computation. This has 2 advantages:

  • A reader will be able to extract the name of the signer.
  • A reader will be able to determine whether this name has been tampered with (provided that the digest computation is strong enough).

In a more realistic case that employs a public/private key mechanism you will commonly not just add the signers name but a complete certificate chain. The reader will then have to determine whether:

  • The provided digest matches the public key of the provided certificate (meaning that the digest belongs to this certificate).
  • Whether it trusts this certificate (meaning that the certificate belongs to the correct person).

Please note that PDFKit.NET will reserve 300 bytes for the returned digest by default. This is done because the digest will need to be stored in the document without altering its layout, as otherwise the very act of storing the digest would invalidate the signature. If you use an algorithm that requires more space, you will need to override the MaxDigestLength property of your signature handler to reflect this.

Below we will have a look at the Verify method that matches this Sign mechanism.

1 public override bool Verify (byte[] bytesToVerify, byte[] digest, byte[][] certificates) 2 { 3 ulong providedDigestValue = 4 ((ulong) digest[7] << 56) + ((ulong) digest[6] << 48) + ((ulong) digest[5] << 40) + 5 ((ulong) digest[4] << 32) + ((ulong) digest[3] << 24) + ((ulong) digest[2] << 16) + 6 ((ulong)digest[1] << 8) + (ulong)digest[0]; 7 8 byte[] encodedName = new byte[digest[8]]; 9 for (int i = 0; i < encodedName.Length; i++) 10 { 11 encodedName[i] = digest[8 + 1 + i]; 12 } 13 14 // Recompute the digest. 15 16 ulong digestValue = 0; 17 18 // Compute digest for all bytes of the document. 19 for (int i = 0; i < bytesToVerify.Length; i++) 20 { 21 digestValue += bytesToVerify[i]; 22 } 23 // Include the length of the name, and the name itself as well. 24 digestValue += (uint)encodedName.Length; 25 for (int i = 0; i < encodedName.Length; i++) 26 { 27 digestValue += encodedName[i]; 28 } 29 30 if (digestValue != providedDigestValue) 31 { 32 return false; 33 } 34 35 // The digest value is correct. We now also assume that the digest 36 // provides the correct name. 37 string name = Encoding.UTF8.GetString(encodedName); 38 39 if (name != _name) 40 { 41 // The digest name is not the same as the one stored in the field. 42 // We are not going to accept this digest. 43 return false; 44 } 45 46 // An serious implementation that is based on public/private keys 47 // would check whether it has a trusted certificate for the provided name, 48 // and whether the provided digest matches the public key of this certificate. 49 50 return true; 51 } 52 }

For our simple digest, the Verify method:

  • Extracts the sum of all bytes from the digest argument (first 8 bytes).
  • Extracts the length and the UTF8 encoded name of the signer from the digest argument.
  • It then verifies that the provided digest is correct by recomputing it from the argument input bytes and the signers name.
  • Once the digest is deemed correct, it assumes that the provided name is correct as well, and it checks whether it matches the name in the field. This is just an extra check because this digest is so simple. For more sensible digest implementations it would be sufficient to just check the digest itself.
  • If everythings right, it returns true.

Note that the bytesToVerify argument contains the same data that was previously provided to the Sign method. This implementation is very simple for the sake of this sample. In reality, you will commonly use the name of the signer to look up a known, trusted certificate, and check whether the provided digest "matches" that certificate. If the digest has been generated with a private key, you can use the trusted public key of the certificate to check whether the generated digest is correct for the provided bytes with respect to this certificate.

The result of all this, when you sign a document with this handler, is as follows in Adobe Reader. The Distinguished name is the same as the Name here, because we did not override the DistinguishedName property of the signature handler.


When you click on it, Adobe Reader will pop up the following dialogue. This is normal, because it does not have a plugin that knows how to verify this type of signature.