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
ase (not verified)
October 19, 2012 - 2:15pm
Permalink
Example for the case of passing X509Certificate2 as argument?
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
October 19, 2012 - 2:41pm
Permalink
Hi ase, I think this is what
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}
ase (not verified)
October 19, 2012 - 4:37pm
Permalink
It throw Cryptograhic Exception
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
rahul
October 22, 2012 - 12:46am
Permalink
Hi ase, you would need to try
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.
Sibasis (not verified)
June 21, 2013 - 12:41pm
Permalink
Problem Digitaly Sign...
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
June 28, 2013 - 12:01am
Permalink
Hi Sibasis, if you are
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.
Chris (not verified)
December 19, 2013 - 11:41pm
Permalink
I know this is over a year
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
December 19, 2013 - 11:50pm
Permalink
Hi Chris, I might be wrong,
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.
Chris (not verified)
December 19, 2013 - 11:59pm
Permalink
When I use Dim akp As
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
December 20, 2013 - 12:11am
Permalink
Hi Chris, if Windows Cert
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.
Chris (not verified)
December 20, 2013 - 12:32am
Permalink
One Last Thing Then
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
Julien (not verified)
December 11, 2012 - 2:23pm
Permalink
Amazing !!!
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 ...
Foakleys (not verified)
December 26, 2012 - 6:10pm
Permalink
good
hard to understand
rahul
December 26, 2012 - 7:22pm
Permalink
Well it would be unless you
Well it would be unless you know how encryption (particularly asymmetric encryption) works :)
Martha Vella (not verified)
March 13, 2013 - 2:12am
Permalink
I understand that This code
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
rahul
March 13, 2013 - 10:37am
Permalink
Thanks Martha, I am glad the
Thanks Martha, I am glad the code is proving useful :)
Hasan (not verified)
March 14, 2013 - 3:12pm
Permalink
Little help
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
Hasan (not verified)
March 14, 2013 - 3:17pm
Permalink
Here i my code for
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;
}
}
rahul
March 15, 2013 - 12:09am
Permalink
Hi Hasan, please see if any
Hi Hasan, please see if any of these help:
http://www.rahulsingla.com/blog/2011/04/serializing-deserializing-rsa-pu...
http://www.rahulsingla.com/blog/2011/04/serializing-deserializing-nets-n...
Hasan (not verified)
March 15, 2013 - 1:31pm
Permalink
Hi, My senerio is so deficult
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
March 15, 2013 - 4:49pm
Permalink
Hi Hasan, that's a pretty
Hi Hasan, that's a pretty standard scenario. Please search on web, I am sure you would find code to do exactly that.
julien (not verified)
April 17, 2013 - 10:16pm
Permalink
No so hard
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 !! )
Francisco (not verified)
April 10, 2013 - 8:52pm
Permalink
Hi, this post is awesome
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
April 10, 2013 - 8:56pm
Permalink
Hi Franchisco, this link
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}
Santiago (not verified)
April 17, 2013 - 10:06pm
Permalink
X.509 certificates in PFX format
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
April 18, 2013 - 12:36am
Permalink
Hi Santiago, why purchase
Hi Santiago, why purchase commercial libraries when the same is available in .Net's API itself as outlined in this comment.
Francisco (not verified)
April 18, 2013 - 3:19am
Permalink
Thanks
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
Francisco (not verified)
April 20, 2013 - 1:12am
Permalink
Question
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
May 12, 2013 - 10:28pm
Permalink
Hi Francisco, I am running
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.
Raviranjan (not verified)
June 3, 2013 - 5:06pm
Permalink
Problem in verifying
Fransisco i am getting the same error.. If you have solved your problem .. can you please share with me..