Digitally sign and verify PDF documents in C# using iTextSharp 5.3.x library

While working on a project, we recently came across a requirement to be able to digitally sign pdf documents in C# code using a public/private key pair and later be able to verify the signature. Basically we were working on an online e-Tendering portal for a semi-government organization. The organization wanted to accept documents from its Vendors online through a portal and be able to verify that the documents indeed originated from a particular Vendor. I would not go into the entire workflow here which is a bit complex, for simplicity you can assume the organization had the public keys for Vendors already stored in a secure database.

We were supposed to develop an easy to use desktop application which would allow Vendors to sign their pdf documents intuitively before submitting them to the Institute. Simultaneously a module was supposed to created in the organization's portal that would allow Vendors to upload such signed documents, which would be verified for their signatures before being forwarded to various departments for processing.

We had been using the PdfSharp library extensively for various projects (one of which involved some complex pdf manipulation) with very good results. So when I took up this task today morning, I first started browsing PdfSharp's API in hope of finding the classes and methods needed to sign and verify pdf documents. But I was to be disappointed, as I neither found anything in the API nor googling revealed any such feature in Pdfsharp library.

But I discovered the iTextSharp library in my Googling session which seemed to provide this feature. I downloaded the latest version (5.3.2 at the time of writing this blog post) but browsing the API up-front was not very meaningful. I again turned to Google which threw up this and this link with code samples for signing documents using iTextSharp. The first of these links was in Java while the second was in .Net. As I started adapting the code from these links, I realized many of the classes and methods being invoked were not present in my version of itextsharp.dll assembly. Somehow I assembled code for signing the document which seemed to work fine. And the signature information was visible when opening the document in a PDF reader.

As I approched the next step of verifying the signature (with code adapted from here), I was unable to verify the signature successfully even after making numerous revisions to the code. And I was back to square zero, signing a document without being able to verify the signature was pretty much useless.

I again went back googling. It was pretty much clear to me at this moment that iTextSharp library had undergone some considerable changes, and I needed code samples that would work with its latest version. Some frantic searching finally brought me over to the online version of the second edition of "iText in Action" book and then to this page from Chapter 12 of this book. I was pretty much sure now I had found what I needed.

Although the code samples were in Java and some aspects of the API are different in its .Net port, I was able to adapt the code from there and have it work in C#.

Without more of this introduction, I would now allow you to get your hands dirty with the actual code for signing a document using iTextSharp 5.3.x in .Net:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }/// <summary> /// Signs a PDF document using iTextSharp library /// </summary> /// <param name="sourceDocument">The path of the source pdf document which is to be signed</param> /// <param name="destinationPath">The path at which the signed pdf document should be generated</param> /// <param name="privateKeyStream">A Stream containing the private/public key in .pfx format which would be used to sign the document</param> /// <param name="keyPassword">The password for the private key</param> /// <param name="reason">String describing the reason for signing, would be embedded as part of the signature</param> /// <param name="location">Location where the document was signed, would be embedded as part of the signature</param> public static void signPdfFile (string sourceDocument, string destinationPath, Stream privateKeyStream, string keyPassword, string reason, string location) { Pkcs12Store pk12=new Pkcs12Store(privateKeyStream, keyPassword.ToCharArray()); privateKeyStream.Dispose(); //then Iterate throught certificate entries to find the private key entry string alias=null; foreach (string tAlias in pk12.Aliases) { if (pk12.IsKeyEntry(tAlias)) { alias = tAlias; break; } } var pk=pk12.GetKey(alias).Key; // reader and stamper PdfReader reader = new PdfReader(sourceDocument); using (FileStream fout = new FileStream(destinationPath, FileMode.Create, FileAccess.ReadWrite)) { using (PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, '\0')) { // appearance PdfSignatureAppearance appearance = stamper.SignatureAppearance; //appearance.Image = new iTextSharp.text.pdf.PdfImage(); appearance.Reason = reason; appearance.Location = location; appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(20, 10, 170, 60), 1, "Icsi-Vendor"); // digital signature IExternalSignature es = new PrivateKeySignature(pk, "SHA-256"); MakeSignature.SignDetached(appearance, es, new X509Certificate[] { pk12.GetCertificate(alias).Certificate }, null, null, null, 0, CryptoStandard.CMS); stamper.Close(); } } }{/syntaxhighlighter}

The VSDoc comments at the top of the function should pretty much explain everything about the input parameters of this method to be able to call it.

 

And here's the counter-part, a method to verify the signature of a previously signed PDF document:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }/// <summary> /// Verifies the signature of a prevously signed PDF document using the specified public key /// </summary> /// <param name="pdfFile">a Previously signed pdf document</param> /// <param name="publicKeyStream">Public key to be used to verify the signature in .cer format</param> /// <exception cref="System.InvalidOperationException">Throw System.InvalidOperationException if the document is not signed or the signature could not be verified</exception> public static void verifyPdfSignature (string pdfFile, Stream publicKeyStream) { var parser=new X509CertificateParser(); var certificate=parser.ReadCertificate(publicKeyStream); publicKeyStream.Dispose(); PdfReader reader = new PdfReader(pdfFile); AcroFields af = reader.AcroFields; var names = af.GetSignatureNames(); if (names.Count == 0) { throw new InvalidOperationException("No Signature present in pdf file."); } foreach (string name in names) { if (!af.SignatureCoversWholeDocument(name)) { throw new InvalidOperationException(string.Format("The signature: {0} does not covers the whole document.", name)); } PdfPKCS7 pk = af.VerifySignature(name); var cal = pk.SignDate; var pkc = pk.Certificates; if (!pk.Verify()) { throw new InvalidOperationException("The signature could not be verified."); } if (!pk.VerifyTimestampImprint()) { throw new InvalidOperationException("The signature timestamp could not be verified."); } Object[] fails = CertificateVerification.VerifyCertificates(pkc, new X509Certificate[] { certificate }, null, cal); if (fails != null) { throw new InvalidOperationException("The file is not signed using the specified key-pair."); } } }{/syntaxhighlighter}

Again the VSDoc comments should explain how to call this method.

This code was assembled quickly and can certainly be improved (e.g, allow multiple iterations of signing with different public keys and corresponding verification, use a custom image for signature). Nevertheless, it should provide a base for quickly starting to use .Net's version of iTextsharp library and building from here. You would fine iText in Action book very helpful for real-world examples and explanation on how to use this library.

 

Comments

In our case we passed a  X509Certificate2 as argument how can we adjust your example so it works? Thanks beforehand

public static string FirmarPDF(X509Certificate2 signature, string sourceDocument, string destinationPath)

        {

            if (signature== null)

            {

                return "Invalid signature.";

            }

            // reader and stamper

            PdfReader reader = new PdfReader(sourceDocument);

            using (FileStream fout = new FileStream(destinationPath, FileMode.Create, FileAccess.ReadWrite))

            {

                using (PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, '\0'))

                {

                    // appearance

                    PdfSignatureAppearance appearance = stamper.SignatureAppearance;

                    //appearance.Image = new iTextSharp.text.pdf.PdfImage();

                    appearance.Reason = "";

                    appearance.Location = "";

                    //appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(20, 10, 170, 60), 1, "Icsi-Vendor");

                    // digital signature

                    //how to instanciate es with a X509Certificate2??

                    IExternalSignature es = new PrivateKeySignature(pk, "SHA-256");

                    Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();

                    Org.BouncyCastle.X509.X509Certificate[] chain = new[] { cp.ReadCertificate(signature.RawData) };

                    try

                    {

                        MakeSignature.SignDetached(appearance, es, chain, null, null, null, 0, CryptoStandard.CMS);

                    }

                    catch (CryptographicException ex)

                    {

                        switch (ex.Message)

                        {

                            case "Action aborted by user.\r\n":

                                return ex.Message;

                            case "Key not found.\r\n":

                                return "Signature not found in your computer.";

                        }

                        throw;

                    }

                    stamper.Close();

                    return "Correct";

                }

            }

        }

rahul's picture

Hi ase, I think this is what you need:

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }X509Certificate2 signature=null;//Your X509Certificate2 certificate instance here. var akp=Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signature.PrivateKey).Private; IExternalSignature es = new PrivateKeySignature(akp, "SHA-256");{/syntaxhighlighter}

Thanks but your code throws CryptographicException at the following line:

var akp=Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signature.PrivateKey).Private;

 

More details:

Ex.Message: Invalid type especified

Ex.StackTrace:

 

   en System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)

   en System.Security.Cryptography.Utils._ExportKey(SafeKeyHandle hKey, Int32 blobType, Object cspObject)

   en System.Security.Cryptography.RSACryptoServiceProvider.ExportParameters(Boolean includePrivateParameters)

   en Org.BouncyCastle.Security.DotNetUtilities.GetRsaKeyPair(RSA rsa)

   en Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(AsymmetricAlgorithm privateKey)

 

signature.PrivateKey details:

signature.PrivateKey.KeyExchangeAlgorithm = null

signature.PrivateKey.KeySize = 1024

 

signature.PrivateKey.LegalKeySizes = {System.Security.Cryptography.KeySizes[1]}

signature.PrivateKey.SignatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"

 

signature details:

signature.HasPrivateKey = true

 

We use smartcards to sign the pdf documents (we pass the selected certificate and windows handles the smartcard reader and password introduction logic), if you need more info or a full post of the previous function that worked in itextsharp.dll 5.2.1 just ask Wink

rahul's picture

Hi ase, you would need to try yourself from here, I won't be able to look into your specific use-case owing to my schedule.

EDIT: You might want to try converting your X509 certificate to .pfx format using openssl.

i have usb token to sign the pdf documents, Ca you help me how to sign digital signature ?

When i paste ur code

X509Certificate2 signature=null;//Your X509Certificate2 certificate instance here.
2 var akp=Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signature.PrivateKey).Private;
3

IExternalSignature es = new PrivateKeySignature(akp, "SHA-256");

error in IExternalSignature

rahul's picture

Hi Sibasis, if you are getting en error in constructor call for PrivateKeySignature, check your key is in correct format and you have specified the correct hash algorithm.

I know this is over a year old. But with this code listed, this assumes signature.PrivateKey is exportable? Or should this work on PrivateKeys with exportable set to False?

rahul's picture

Hi Chris, I might be wrong, but I believe if exportable is false on the certificate, then the private key won't exist in the certificate in the first place. And obviously, you can't sign if you don't have the private key.

When I use

Dim akp As AsymmetricKeyParameter = DotNetUtilities.GetKeyPair(signature.PrivateKey).Private

it errors with "Key not calid for use in specified state"

So through Visual Studio IDE I went and looked at the private key. The CspKeyContainerInfo.Exportable was set to False. So even tried to use Windows Copy Cert to file. And it refused to let me export the private key as its export was set to false.

So that is what started making me think thats the issue with using GetKeyPair, my key not being able to be exported so that function can not read it.

rahul's picture

Hi Chris, if Windows Cert Store refuses to let you export a Certificate as its export was set to false, it means the certificate was originally imported into the Certificate Store as non-exportable, which further means Windows did not import the private key into the store (but just the public key). So it cannot be exported from certificate store and neither can be used for signing in the absence of private key.

Maybe its just me and I am not reading the IDE right or mis understanding your explanaation. Was trying to  Attached an image that should show you where the signature HasAPrivateKey but the PrivateKey is not exportable but it keeps telling me 102KB is greater then 1mb.

So I will try and type out what I see:

signature.HasPrivateKey = True

signature.privatekey

 + system.security.crypto.RSACryptoServiceProvider 

  + CspKeyContainerInfo

    Exportable = False

 

I just want to thank you !! great article. I was looking for a clear explanation using the latest version of this librairy ! I was quite surprised to see the changes done over the year in this library without clear explanation. Most of the exemples I found on the web used methods which doesn't exist anymore ... 

hard to understand

rahul's picture

Well it would be unless you know how encryption (particularly asymmetric encryption) works :)

I understand that This code was assembled quickly and can certainly be improved. But I needed something and yes I found it, thanks a lot for this

Signing up for the shady gambling on line can be tempting once they are supplying an unattainable to refuse reward which lets you to take part in which has a ton of cost-free money.Everything you should additional worried about just is not an incredible bargain of what is the size of the reward, and also, when the time comes you would like to withdraw in any way you may have from the harmony, will their financial debt vs . you be honoured? Many internet gambling australia are swift to consider your hard earned casino money, although not so satisfied about forking out a payment of $30,000 for those who strike a winner.
rahul's picture

Thanks Martha, I am glad the code is proving useful :)

Hi,

This is good example that i never seen any where on the internet. i want to know about the "how i convet or you can say extrect the Private key stream from the PFX certificate" i found a code for that sepecific part but it didnt work.

Thanks
Hasan

Here i my code for that.

public static Stream PrivateKeystreem(string FileName, string Password, string directory)
        {
            MemoryStream memoryStream = new MemoryStream();
            try
            {
                Logger.LogErrorMessage(FileName, directory);
                X509Certificate2 certificate = new X509Certificate2(FileName, Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
                Logger.LogErrorMessage("reading certificate dane", directory);
              
                RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
                Logger.LogErrorMessage("PrivateKey formating done dane", directory);
              
                Logger.LogErrorMessage("Start Stream Writing", directory);
                TextWriter streamWriter = new StreamWriter(memoryStream);
                PemWriter pemWriter = new PemWriter(streamWriter);
                Logger.LogErrorMessage("Stream Writing Done", directory);

               AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(rsa);
               pemWriter.WriteObject(keyPair.Private);
                streamWriter.Flush();

                // Here is the output with ---BEGIN RSA PRIVATE KEY---
                // that should be exactly the same as in private.pem
                return memoryStream;
              
            }
            catch (Exception ex)
            {
                Logger.LogErrorMessage(ex.Message, directory);
                return memoryStream;
            }

        }

Hi,

My senerio is so deficult i worked on that from last 1.5 month but still in trubale  this is not that code that i want i need the key anableing code. i have a certifcates from and company in CER formate and i want to convert those certificates in to PFX formate i want to sign the PDF document using those file and there private keys. i cann't make my own keys. so this didnt help me.

thanks for your replay.

Hasan

rahul's picture

Hi Hasan, that's a pretty standard scenario. Please search on web, I am sure you would find code to do exactly that.

First of all you can convert the .cer file into a .pfx bu using OPENSSL (also available on Windows). Just use this command line:

openssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx

Then you can load the certificate by reading directly the file:

string certPath = <YOUR PFX FILE PATH>;
string certPass = <YOUR PASSWORD>;

// Create a collection object and populate it using the PFX file
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(certPath, certPass, X509KeyStorageFlags.PersistKeySet);

Or load it from the windows certificate store (it is another complex story but it is my prefered option !! )

i think it could really useful for me, but i need some help by doing the previous step of generation of the public and private keys, do you have some information about it? Please let me know if you can help me.

Thanks in advance.
PS. sorry for the grammar English is not me mother language.

rahul's picture

Hi Franchisco, this link gives you one approach using BountyCastel library. And this is using .NET's API:

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { string publicKey = rsa.ToXmlString(false); string privateKey = rsa.ToXmlString(true); }{/syntaxhighlighter}

Hi Francisco, check this.

You can use the following commercial API for your purpose: http://www.signfiles.com/x509-certificate-library/

It has been a great help in my project. Greetings from Mexic

rahul's picture

Hi Santiago, why purchase commercial libraries when the same is available in .Net's API itself as outlined in this comment.

thanks again, i supose that the xml generated by this methods can be the feed for the methods that show in the post, i will tried thanks again

well thanks again for your help, but i have another problem, i made the sign fine, with this.

 string cnPrefix = "CERT"; //Prefijo;

            int qty = 1; //cantidad de certificados

            ContainerType cType = (ContainerType)Enum.Parse(typeof(ContainerType), "PKCS12"); //tipo de contenedor

            HashType hType = (HashType)Enum.Parse(typeof(HashType), "SHA256withRSA");

            int bitStrength = 2047;

            DateTime validFrom = DateTime.Now; //fecha de validez inicial

            DateTime validTo = validFrom.AddYears(1); //fecha de validez final

            string destDir = "c:\\"; //destino del archivo

            string password = "password"; //password

            // Kick off GenerateCertificates()

            GenerateCertificates(cnPrefix, qty, cType, hType, bitStrength, validFrom, validTo, destDir, password);

            Signer signer = new Signer();

            FileStream fileStream = new FileStream("C:\\CERT0-2047-SHA1withRSA.pfx", FileMode.Open);

            signer.signPdfFile("C:\\NB-SP.pdf", "D:\\test.pdf", fileStream, "password", "test", "AJ");

            fileStream.Close();

 

this code made a certificate first and use your code to sign. at this point everything is fine

but when i tried to verify the signed pdf there is a problem. first i obtain the public sign with 

 

 X509Certificate2 certificate = new X509Certificate2("C:\\CERT0-2047-SHA1withRSA.pfx", password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);

            // Public Key;

            StringBuilder publicBuilder = new StringBuilder();

            publicBuilder.AppendLine("-----BEGIN CERTIFICATE-----");            publicBuilder.AppendLine(Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));

            publicBuilder.AppendLine("-----END CERTIFICATE-----");

            publicBuilder.ToString();

            System.Console.WriteLine(publicBuilder.ToString());

            File.WriteAllText("c:\\cert.ce", publicBuilder.ToString());

            fileStream = new FileStream("c:\\cert.ce", FileMode.Open);

 

and the exception is thrown 

Excepción no controlada: System.InvalidOperationException: The signature timestamp could not be verified.

   en TestCrypto.Signer.verifyPdfSignature(String pdfFile, Stream publicKeyStrea

m) en C:\Users\jalvarez.AJNACIONAL\Documents\Visual Studio 2010\Projects\TestCry

pto\TestCrypto\Signer.cs:línea 103

the same for the next  step

Excepción no controlada: System.NullReferenceException: Referencia a objeto no e

stablecida como instancia de un objeto.

   en iTextSharp.text.pdf.security.CertificateVerification.VerifyCertificate(X50

9Certificate cert, ICollection`1 crls, DateTime calendar)

   en iTextSharp.text.pdf.security.CertificateVerification.VerifyCertificates(IC

ollection`1 certs, ICollection`1 keystore, ICollection`1 crls, DateTime calendar

)

   en TestCrypto.Signer.verifyPdfSignature(String pdfFile, Stream publicKeyStrea

m) en C:\Users\jalvarez.AJNACIONAL\Documents\Visual Studio 2010\Projects\TestCry

pto\TestCrypto\Signer.cs:línea 106

 

i don't understand why of this can you tell me what is happen?

rahul's picture

Hi Francisco, I am running short on time to check in detail but it might be either the public key does not correspond to the private key or you are not creating public key correctly. Please ensure you are reading keys correctly.

Fransisco i am getting the same error.. If you have solved your problem .. can you please share with me..