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.
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”;
}
}
}
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
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
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
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.
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
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?
Thank you so much. I had been strugling for days.
How would you change the background of the signature and also add an image?
I m getting error, type “Pdfstamper” is not defined
Hi Rahul,
Just wanted to say thanks for your article, it helped me out a lot.
Adam
What is privateKeyStream?How can i get privateKeyStream?
hi..
i am new to c#. please help me
how can i get certificates already installed on system for signing.
certificates can be view in control panel-> internet option->content -> certificates ->personal
hi, I am using .java library for signing a pdf file with pfx.
but it is very slow. as my complete application is in php. and i am using my application in linux and windows environment.
can you please help me in solving this.
when i try to give password in the line as
keyPassword = “123456789”;(the password i gave when creating private key)
Pkcs12Store pk12=
new
Pkcs12Store(privateKeyStream, keyPassword.ToCharArray());
it is giving an error as :
PKCS12 key store MAC invalid – wrong password or corrupted file.
can u please solve my problem. thanks
i am getting this error. i have not modified your code.
can you please tell briefly which streams to use and which file paths to give as i am confused.
i have exported private key and give its path in “signPdfFile” method.
and exported the public key and given its path in “verifyPdfSignature” method.
i am using self-signed certificate from IIS.
can you please provide a full sample with all files…
please…
thank you
Sorry, i forgot.
The signature timestamp could not be verified.
this is the error.
please also tell me if i am using correct files from my previous post.
Rahul, you save me a lot of time. Thank you so much.
LuisO
just what I needed … nice wofk
thanks
Great jobs Rahul, thanks a lot.
How i can read certificates from personal store and sign it? like Ms Office (word and outlook) attach signatures.
thanks in advance
Please help i read the above but unable to rectify the line given
var akp = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signature.PrivateKey).Private;
IExternalSignature es = new PrivateKeySignature(akp, “SHA-256”);
Error:– “Key not valid for use in specified state.”
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, ‘\0’))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
appearance.Reason = “”;
appearance.Location = “”;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(20, 10, 170, 60), 1, “Icsi-Vendor”);
if (signature.HasPrivateKey == true)
{
IExternalSignature es = new X509Certificate2Signature(signature, “SHA-1”);
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);
}
hi..
i have signed pdf but it sign only a fixed page as first or sencond or third page.
i need to set option for user to sign :
all page
first page
even page
odd page
last page
selected page
i have to sign in pdf but the signature property hasprivatekey was false so i unable to use this in my programme. how to use this type of signatures . as programme bellow.
it show error in this line IExternalSignature es = new X509Certificate2Signature(st, “SHA-1”);
if (st.HasPrivateKey == false)
{
IExternalSignature es = new X509Certificate2Signature(st, “SHA-1″);//”sha256”);//
Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new[] { cp.ReadCertificate(st.RawData) };
PdfReader reader = new PdfReader(“C:\\abcd.pdf”);
using (FileStream fout = new FileStream(“C:\\OutPut.pdf”, FileMode.Create, FileAccess.ReadWrite))
{
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, ‘\0’))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Contact = Contacttext.Text;
appearance.Reason = Reasontext.Text;
appearance.Location = Locationtext.Text;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(Convert.ToInt32(X.Text.ToString().Trim()), Convert.ToInt32(Y.Text.ToString().Trim()), Convert.ToInt32(X1.Text.ToString().Trim()), Convert.ToInt32(Y1.Text.ToString().Trim())), int.Parse(TxtPageNo1.Text), null);
}
try
{
MakeSignature.SignDetached(appearance, es, chain, null, null, null, 0, CryptoStandard.CMS);
}
Hiiiii rahul
Your code is superb, but as i am a new to asp.net and crypography , digital certificates, pls help me to find out, how to Digital Sign pDF in webbrowser , where Certficate from Smart Card is picked up from client side using client Store and user Digitally Sign PDf using his Certificate, Pls help as i am serching it from last 1month in google but no help
Hi,
i need your lights here. I was trying to
a) open an existing pdf file
b) sign it
c) lock it.
But i experience some issues here. When i first sign and then try to lock it finally i am loosing the signature. When i firts lock and then sign then i am loosing the lock (SetEncryption). Do you have any idea how i can implement that?
Thx anyway
Sir,
I get two errors
1.
if
(!pk.VerifyTimestampImprint())
38
{
39
throw
new
InvalidOperationException(
"The signature timestamp could not be verified."
);
40
}
One for this one. “The signature timestamp could not be verified.”
2. Otherone while i try to verify CertificateVerification.verifyCertificates.. i tried to change from
Object[] fails = CertificateVerification.VerifyCertificates(pkc,
new
X509Certificate[] { certificate },
null
, cal);
to
List<VerificationException> errors = CertificateVerification.verifyCertificates(pkc, ks, null, cal);
But get null exception error.
I have a pfx file and generated crt from it. Can you please help.
I am getting error “The Type or Namespace name ‘IExternalSignature’ could not be found” . I have included namespace
using iTextSharp.text.pdf;
using iTextSharp.text.xml.xmp;
using iTextSharp.text.pdf.security;
still showing error
Hi Snehal,
With the version of itextsharp.dll being used when this post was originally written, ‘IExternalSignature’ was present in this namespace:
iTextSharp.text.pdf.security
I have uploaded the version of the dll here:
https://drive.google.com/file/d/1UdJNFFraYRR8aZqTVLPv7hS9sWiGlyzM/view?usp=sharing
PS: It’s a very old version from 2014.
Hi Rahul,
Thanks for your reply.
I have 3 pages in my pdf and I want to add signature to all 3 pages. How can I do it using iTextSharp.pdf
Well Snehal, I will need to check the api / SDK docs for iTextsharp myself for that as I haven’t tried that myself. You can better ask this on StackOverflow and tag iTextSharp.