Table of Contents

Signing PDFs (C# / Desktop / 25.Printing and Exporting)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\csharp\VS2026\Desktop\25.Printing and Exporting\35.Signing Pdfs and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​NET-​demos/​tree/​master/​csharp/​VS2026/​Desktop/​Modules/​25.​Printing and Exporting/35.Signing Pdfs

Overview

In this example we will show how to add a visible or invisible signature to a generated PDF file.

Concepts

  • FlexCel supports adbe.pkcs7.detached (the original Adobe format), PAdES baseline level B-B, and PAdES baseline level B-T. Being older, adbe.pkcs7.detached is the most extended and compatible, but PAdES is the standard required by the European Union to sign. It probably makes sense to sign your PDFs with PAdES.

  • In order to sign a PDF file you will need a certificate issued by a valid Certificate Authority, or one issued by yourself. In this example we will use a self signed certificate. This certificate will not validate by default when you open it in Acrobat, you need to add it to your trusted list.

  • The default algorithm for CmsSigner .NET class is SHA-1, which is known to have vulnerabilities and shouldn't be used anymore. So in this example we use SHA512 instead by changing the DigestAlgorithm.

  • In order to sign a file, FlexCel will write a requirement for Acrobat 8 or newer in the generated files. This is because only Acrobat 8 or newer support SHA512. Older versions of acrobat will still display the pages but will not validate the signature.

  • We provide a default signing implementation using standard .NET crypto classes**.** You can still create your own signature engine by using a third party cryptography library or by calling CryptoApi in windows via p/invoke. This is explained in the section Signing PDF Files in the PDF exporting guide.

  • By design, FlexCel never connects to the internet. So if you want to create a PAdES B-T signature, which includes a timestamp from a TSA server, you need to provide the code to actually connect the server in an anonymous method. This demo shows how to do it.

Files

mainForm.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using FlexCel.Render;
using FlexCel.XlsAdapter;
using FlexCel.Pdf;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Pkcs;
using System.Drawing.Imaging;
using System.Reflection;
using System.Diagnostics;
#if NET5_0_OR_GREATER
using System.Net.Http;
using System.Net.Http.Headers;
#endif

namespace SigningPdfs
{
    public partial class mainForm: Form
    {
        /// <summary>
        /// The entries of the "Signature type" combo box, in the same order as they are added in the designer.
        /// </summary>
        private enum TSignatureType
        {
            /// <summary>
            /// "/adbe.pkcs7.detached": the original Adobe format. It is the most compatible, but it is not a
            /// CAdES signature, so it doesn't conform to the PAdES standard the European Union requires.
            /// </summary>
            Pkcs7,

            /// <summary>
            /// "/ETSI.CAdES.detached" without a timestamp: PAdES baseline level B-B.
            /// </summary>
            PAdES_B_B,

            /// <summary>
            /// PAdES baseline level B-T: a B-B signature plus a timestamp from a Time Stamping Authority, which
            /// proves the document was signed before a given date instead of trusting the clock of whoever signed.
            /// </summary>
            PAdES_B_T
        }

        public mainForm()
        {
            Application.EnableVisualStyles();
            InitializeComponent();
            cbSignatureType.SelectedIndex = (int)TSignatureType.PAdES_B_B;
        }

        private void cbVisibleSignature_CheckedChanged(object sender, EventArgs e)
        {
            SignaturePicture.Visible = cbVisibleSignature.Checked;
            int delta = SignaturePicture.Height + 30;
            if (cbVisibleSignature.Checked) this.Height += delta; else this.Height -= delta;
        }

        private void SignaturePicture_Click(object sender, EventArgs e)
        {
            if (OpenImageDialog.ShowDialog() != DialogResult.OK) return;
            SignaturePicture.Load(OpenImageDialog.FileName);
        }

        private void btnCreateAndSign_Click(object sender, EventArgs e)
        {
            //Load the Excel file.
            if (OpenExcelDialog.ShowDialog() != DialogResult.OK) return;
            XlsFile xls = new XlsFile();
            xls.Open(OpenExcelDialog.FileName);

            string DataPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"\..\..\";

            //Export it to pdf.
            using (FlexCelPdfExport pdf = new FlexCelPdfExport(xls, true))
            {
                pdf.FontEmbed = TFontEmbed.Embed;

                //Load the certificate and create a signer.
                //In this example we just have the password in clear. It should be kept in a SecureString.
                //Also make sure to set the flag X509KeyStorageFlags.EphemeralKeySet to avoid files created
                //on disk: https://snede.net/the-most-dangerous-constructor-in-net/
                //As X509KeyStorageFlags.EphemeralKeySet only exists in .NET 4.8 or newer, for older versions we will 
                //define it as (X509KeyStorageFlags)32. For .NET 4.8 or newer and  NET Core, you can use X509KeyStorageFlags.EphemeralKeySet
                  X509Certificate2 Cert = new X509Certificate2(DataPath + "flexcel.pfx", "password", X509KeyStorageFlags.EphemeralKeySet);  

                //Note that to use the CmsSigner class you need to add a reference to System.Security dll. 
                //It is *not* enough to add it to the using clauses, you need to add a reference to the dll.
                CmsSigner Signer = new CmsSigner(Cert);

                //By default CmsSigner uses SHA1, but SHA1 has known vulnerabilities and it is deprecated. 
                //So we will use SHA512 instead.
                //"2.16.840.1.101.3.4.2.3" is the Oid for SHA512.
                Signer.DigestAlgorithm = new System.Security.Cryptography.Oid("2.16.840.1.101.3.4.2.3");

                //The format of the signature is decided by the factory, not by the signature itself.
                TPdfSignerFactory SignerFactory = CreateSignerFactory(Signer);
                if (SignerFactory == null) return;

                TPdfSignature sig;
                if (cbVisibleSignature.Checked)
                {
                    using (MemoryStream fs = new MemoryStream())
                    {
                        SignaturePicture.Image.Save(fs, ImageFormat.Png);
                        byte[] ImgData = fs.ToArray();

                        //The -1 as "page" parameter means the last page.
                        sig = new TPdfVisibleSignature(SignerFactory,
                            "Signature", "I have read the document and certify it is valid.", "Springfield", "adrian@tmssoftware.com", -1, new RectangleF(50, 50, 140, 70), ImgData);
                    }
                }
                else
                {
                    sig = new TPdfSignature(SignerFactory,
                                "Signature", "I have read the document and certify it is valid.", "Springfield", "adrian@tmssoftware.com");
                }

                //A certifying signature (the default) says who is responsible for the document and which changes
                //are allowed in it afterwards, and only the first signature of a document can certify it.
                //An approval signature just says that whoever signed agrees with what the document says at that
                //moment, and many of them can be added to the same document.
                sig.Certify = cbCertify.Checked;

                //You must sign the document *BEFORE* starting to write it.
                pdf.Sign(sig);

                if (savePdfDialog.ShowDialog() != DialogResult.OK) return;
                using (FileStream PdfStream = new FileStream(savePdfDialog.FileName, FileMode.Create))
                {
                    pdf.BeginExport(PdfStream);
                    pdf.ExportAllVisibleSheets(false, "Signed Pdf");
                    pdf.EndExport();
                }

            }

            if (MessageBox.Show("Do you want to open the generated file?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
            Process.Start(savePdfDialog.FileName);

        }

        /// <summary>
        /// Creates the factory that will sign the document in the format selected in the combo box.
        /// Returns null when the selected format is not available, and so the document shouldn't be signed.
        /// </summary>
        private TPdfSignerFactory CreateSignerFactory(CmsSigner Signer)
        {
            switch ((TSignatureType)cbSignatureType.SelectedIndex)
            {
                case TSignatureType.PAdES_B_B:
                    //Besides writing "/ETSI.CAdES.detached" in the pdf, this adds the ESS signing-certificate-v2
                    //signed attribute that CAdES needs, so the signature says which certificate created it.
                    return new TBuiltInSignerFactory(Signer, TPdfSignatureSubFilter.EtsiCAdESDetached);

                case TSignatureType.PAdES_B_T:
                    return CreateTimestampedSignerFactory(Signer);

                default:
                    return new TBuiltInSignerFactory(Signer, TPdfSignatureSubFilter.AdbePkcs7Detached);
            }
        }

#if NET5_0_OR_GREATER
        /// <summary>
        /// The Time Stamping Authority we ask for the timestamps. Replace it with the one you use: the public ones
        /// are rate-limited, and a timestamp is only as trustworthy as the TSA that created it.
        /// </summary>
        private const string TsaUrl = "http://timestamp.digicert.com";

        //HttpClient is meant to be reused. Creating one per request runs out of sockets when signing many files.
        private static readonly HttpClient TsaClient = new HttpClient();

        /// <summary>
        /// <b>FlexCel never connects to the internet by itself.</b> It creates the RFC 3161 request and reads the
        /// answer, but the connection to the TSA is this method, which you write. That way you know that no part
        /// of FlexCel can reach the network unless you let it.
        /// </summary>
        private static byte[] GetTimestamp(byte[] timeStampRequest)
        {
            using (ByteArrayContent Content = new ByteArrayContent(timeStampRequest))
            {
                Content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query");

                //This demo signs when you click a button, so we just wait for the answer here. In a server you
                //would normally make the whole export async instead of blocking a thread on the TSA.
                using (HttpResponseMessage Response = TsaClient.PostAsync(TsaUrl, Content).Result)
                {
                    Response.EnsureSuccessStatusCode();
                    return Response.Content.ReadAsByteArrayAsync().Result;
                }
            }
        }

        private TPdfSignerFactory CreateTimestampedSignerFactory(CmsSigner Signer)
        {
            //FlexCel has to reserve the space for the signature before it knows how big the timestamp will be, so
            //it asks the TSA for one sample token the first time. Passing the url as the cache key means the size
            //is measured once for the whole application and shared by every factory using this TSA, which matters
            //because TSAs tend to rate-limit. If you already know the size, set TokenSizeHint instead and FlexCel
            //will not ask for the sample at all.
            TPdfTimestampSettings TimestampSettings = new TPdfTimestampSettings(GetTimestamp, TsaUrl);
            return new TBuiltInSignerFactory(Signer, TPdfSignatureSubFilter.EtsiCAdESDetached, TimestampSettings);
        }
#else
        private TPdfSignerFactory CreateTimestampedSignerFactory(CmsSigner Signer)
        {
            //Creating the timestamp request needs Rfc3161TimestampRequest, which only exists in .NET 5 and newer,
            //so FlexCel can only create B-T signatures there. B-B signatures work everywhere.
            MessageBox.Show("PAdES B-T signatures need .NET 5 or newer. This demo is compiled for .NET Framework, "
                + "so only PKCS#7 and PAdES B-B are available here.", "Not available",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
            return null;
        }
#endif
    }
}

mainForm.Designer.cs

namespace SigningPdfs
{
    partial class mainForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.btnCreateAndSign = new System.Windows.Forms.Button();
            this.lblSignatureType = new System.Windows.Forms.Label();
            this.cbSignatureType = new System.Windows.Forms.ComboBox();
            this.cbCertify = new System.Windows.Forms.CheckBox();
            this.cbVisibleSignature = new System.Windows.Forms.CheckBox();
            this.OpenExcelDialog = new System.Windows.Forms.OpenFileDialog();
            this.savePdfDialog = new System.Windows.Forms.SaveFileDialog();
            this.SignaturePicture = new System.Windows.Forms.PictureBox();
            this.OpenImageDialog = new System.Windows.Forms.OpenFileDialog();
            ((System.ComponentModel.ISupportInitialize)(this.SignaturePicture)).BeginInit();
            this.SuspendLayout();
            // 
            // btnCreateAndSign
            // 
            this.btnCreateAndSign.Image = global::SigningPdfs.Properties.Resources.acroread;
            this.btnCreateAndSign.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
            this.btnCreateAndSign.Location = new System.Drawing.Point(24, 118);
            this.btnCreateAndSign.Name = "btnCreateAndSign";
            this.btnCreateAndSign.Size = new System.Drawing.Size(155, 30);
            this.btnCreateAndSign.TabIndex = 3;
            this.btnCreateAndSign.Text = "Create and Sign Pdf";
            this.btnCreateAndSign.UseVisualStyleBackColor = true;
            this.btnCreateAndSign.Click += new System.EventHandler(this.btnCreateAndSign_Click);
            // 
            // lblSignatureType
            // 
            this.lblSignatureType.AutoSize = true;
            this.lblSignatureType.Location = new System.Drawing.Point(21, 15);
            this.lblSignatureType.Name = "lblSignatureType";
            this.lblSignatureType.Size = new System.Drawing.Size(82, 13);
            this.lblSignatureType.TabIndex = 5;
            this.lblSignatureType.Text = "Signature type:";
            // 
            // cbSignatureType
            // 
            this.cbSignatureType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cbSignatureType.FormattingEnabled = true;
            this.cbSignatureType.Items.AddRange(new object[] {
            "PKCS#7 (adbe.pkcs7.detached)",
            "PAdES B-B (ETSI.CAdES.detached)",
            "PAdES B-T (B-B + timestamp from a TSA)"});
            this.cbSignatureType.Location = new System.Drawing.Point(24, 33);
            this.cbSignatureType.Name = "cbSignatureType";
            this.cbSignatureType.Size = new System.Drawing.Size(260, 21);
            this.cbSignatureType.TabIndex = 0;
            // 
            // cbCertify
            // 
            this.cbCertify.AutoSize = true;
            this.cbCertify.Checked = true;
            this.cbCertify.CheckState = System.Windows.Forms.CheckState.Checked;
            this.cbCertify.Location = new System.Drawing.Point(24, 66);
            this.cbCertify.Name = "cbCertify";
            this.cbCertify.Size = new System.Drawing.Size(175, 17);
            this.cbCertify.TabIndex = 1;
            this.cbCertify.Text = "Certify the document (DocMDP)";
            this.cbCertify.UseVisualStyleBackColor = true;
            // 
            // cbVisibleSignature
            // 
            this.cbVisibleSignature.AutoSize = true;
            this.cbVisibleSignature.Location = new System.Drawing.Point(24, 89);
            this.cbVisibleSignature.Name = "cbVisibleSignature";
            this.cbVisibleSignature.Size = new System.Drawing.Size(167, 17);
            this.cbVisibleSignature.TabIndex = 2;
            this.cbVisibleSignature.Text = "Visible Signature (in last page)";
            this.cbVisibleSignature.UseVisualStyleBackColor = true;
            this.cbVisibleSignature.CheckedChanged += new System.EventHandler(this.cbVisibleSignature_CheckedChanged);
            // 
            // OpenExcelDialog
            // 
            this.OpenExcelDialog.DefaultExt = "xls";
            this.OpenExcelDialog.Filter = "Excel Files|*.xls;*.xlsx;*.xlsm|Excel 97/2003|*.xls|Excel 2007|*.xlsx;*.xlsm|All files|*.*";
            this.OpenExcelDialog.Title = "Select Excel file to convert...";
            // 
            // savePdfDialog
            // 
            this.savePdfDialog.DefaultExt = "pdf";
            this.savePdfDialog.Filter = "Pdf Files|*.pdf";
            this.savePdfDialog.Title = "Select where to save the file...";
            // 
            // SignaturePicture
            // 
            this.SignaturePicture.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.SignaturePicture.Image = global::SigningPdfs.Properties.Resources.sign;
            this.SignaturePicture.Location = new System.Drawing.Point(24, 175);
            this.SignaturePicture.Name = "SignaturePicture";
            this.SignaturePicture.Size = new System.Drawing.Size(155, 100);
            this.SignaturePicture.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
            this.SignaturePicture.TabIndex = 4;
            this.SignaturePicture.TabStop = false;
            this.SignaturePicture.Click += new System.EventHandler(this.SignaturePicture_Click);
            // 
            // OpenImageDialog
            // 
            this.OpenImageDialog.Filter = "Supported Images|*.png;*.bmp*.jpg|All files|*.*";
            // 
            // mainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(308, 165);
            this.Controls.Add(this.SignaturePicture);
            this.Controls.Add(this.cbVisibleSignature);
            this.Controls.Add(this.cbCertify);
            this.Controls.Add(this.cbSignatureType);
            this.Controls.Add(this.lblSignatureType);
            this.Controls.Add(this.btnCreateAndSign);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.Name = "mainForm";
            this.Text = "Signing PDFs";
            ((System.ComponentModel.ISupportInitialize)(this.SignaturePicture)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button btnCreateAndSign;
        private System.Windows.Forms.Label lblSignatureType;
        private System.Windows.Forms.ComboBox cbSignatureType;
        private System.Windows.Forms.CheckBox cbCertify;
        private System.Windows.Forms.CheckBox cbVisibleSignature;
        private System.Windows.Forms.OpenFileDialog OpenExcelDialog;
        private System.Windows.Forms.SaveFileDialog savePdfDialog;
        private System.Windows.Forms.PictureBox SignaturePicture;
        private System.Windows.Forms.OpenFileDialog OpenImageDialog;
    }
}


Program.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace SigningPdfs
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new mainForm());
        }
    }
}