Cómo firmar un documento PDF desde C# con iTextSharp

Jueves, 24 jun, 2010 @ 18:40 | Por Gustavo Cantero (The Wolf) | .NET Framework, Certificados Digitales, Seguridad

Muchas veces tenemos que firmar un PDF utilizando un certificado X.509, y el iTextSharp (una excelente librería) nos puede ayudar a realizar esta tarea. Para esto les dejo un método que utilizo para hacer esto, que seguramente les va a ser de utilidad.

Para poder utilizar este código deben bajarse la librería iTextSharp (http://sourceforge.net/projects/itextsharp), y referenciar esta DLL y “System.Security” desde su proyecto.

A continuación les dejo el código:

using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using iTextSharp.text;
using iTextSharp.text.pdf;
using Org.BouncyCastle.X509;
using SysX509 = System.Security.Cryptography.X509Certificates;

/// <summary>
/// Helper para el firmado de PDFs con la librería iTextSharp
/// </summary>
public static class PDF
{
    /// <summary>
    /// Firma un documento
    /// </summary>
    /// <param name="Source">Documento origen</param>
    /// <param name="Target">Documento destino</param>
    /// <param name="Certificate">Certificado a utilizar</param>
    /// <param name="Reason">Razón de la firma</param>
    /// <param name="Location">Ubicación</param>
    /// <param name="AddVisibleSign">Establece si hay que agregar la firma visible al documento</param>
    public static void SignHashed(string Source, string Target, SysX509.X509Certificate2 Certificate, string Reason, string Location, bool AddVisibleSign)
    {
        X509CertificateParser objCP = new X509CertificateParser();
        X509Certificate[] objChain = new X509Certificate[] { objCP.ReadCertificate(Certificate.RawData) };

        PdfReader objReader = new PdfReader(Source);
        PdfStamper objStamper = PdfStamper.CreateSignature(objReader, new FileStream(Target, FileMode.Create), '\0');
        PdfSignatureAppearance objSA = objStamper.SignatureAppearance;

        if (AddVisibleSign)
            objSA.SetVisibleSignature(new Rectangle(100, 100, 300, 200), 1, null);

        objSA.SignDate = DateTime.Now;
        objSA.SetCrypto(null, objChain, null, null);
        objSA.Reason = Reason;
        objSA.Location = Location;
        objSA.Acro6Layers = true;
        objSA.Render = PdfSignatureAppearance.SignatureRender.NameAndDescription;
        PdfSignature objSignature = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
        objSignature.Date = new PdfDate(objSA.SignDate);
        objSignature.Name = PdfPKCS7.GetSubjectFields(objChain[0]).GetField("CN");
        if (objSA.Reason != null)
            objSignature.Reason = objSA.Reason;
        if (objSA.Location != null)
            objSignature.Location = objSA.Location;
        objSA.CryptoDictionary = objSignature;
        int intCSize = 4000;
        Hashtable objTable = new Hashtable();
        objTable[PdfName.CONTENTS] = intCSize * 2 + 2;
        objSA.PreClose(objTable);

        HashAlgorithm objSHA1 = new SHA1CryptoServiceProvider();

        Stream objStream = objSA.RangeStream;
        int intRead = 0;
        byte[] bytBuffer = new byte[8192];
        while ((intRead = objStream.Read(bytBuffer, 0, 8192)) > 0)
            objSHA1.TransformBlock(bytBuffer, 0, intRead, bytBuffer, 0);
        objSHA1.TransformFinalBlock(bytBuffer, 0, 0);

        byte[] bytPK = SignMsg(objSHA1.Hash, Certificate, false);
        byte[] bytOut = new byte[intCSize];

        PdfDictionary objDict = new PdfDictionary();

        Array.Copy(bytPK, 0, bytOut, 0, bytPK.Length);

        objDict.Put(PdfName.CONTENTS, new PdfString(bytOut).SetHexWriting(true));
        objSA.Close(objDict);
    }

    /// <summary>
    /// Crea la firma CMS/PKCS #7
    /// </summary>
    private static byte[] SignMsg(byte[] Message, SysX509.X509Certificate2 SignerCertificate, bool Detached)
    {
        //Creamos el contenedor
        ContentInfo contentInfo = new ContentInfo(Message);

        //Instanciamos el objeto SignedCms con el contenedor
        SignedCms objSignedCms = new SignedCms(contentInfo, Detached);

        //Creamos el "firmante"
        CmsSigner objCmsSigner = new CmsSigner(SignerCertificate);

        // Include the following line if the top certificate in the
        // smartcard is not in the trusted list.
        objCmsSigner.IncludeOption = SysX509.X509IncludeOption.EndCertOnly;

        //  Sign the CMS/PKCS #7 message. The second argument is
        //  needed to ask for the pin.
        objSignedCms.ComputeSignature(objCmsSigner, false);

        //Encodeamos el mensaje CMS/PKCS #7
        return objSignedCms.Encode();
    }
}

Descargar proyecto de ejemploAquí les dejo un proyecto de ejemplo donde se pide un PDF a firmar, luego donde escribir PDF firmado y toma el primer certificado personal que posea clave privada y lo utiliza para firmar el PDF.

Espero que este código les sea de utilidad.
Suerte!

Artículos relacionados

VN:F [1.7.3_972]
Rating: 10.0/10 (1 voto cast)

Reciente

  • Cómo firmar un documento PDF desde C# con iTextSharp
  • Obtener lista de contactos de Gmail
  • Pósters de tecnologías y productos de Microsoft
  • Parámetros opcionales en C# 4.0
  • Enlace via código de grillas en ASP.NET
  • Poster con las clases de .NET Framework 4.0
  • Especificación de los lenguajes C# 4.0 y Visual Basic 10.0
  • Tamaño de los parámetros en string.Format
  • 101 ejemplos de Visual C# 2010 y Visual Basic 2010
  • Enlace de elementos en controles de lista en ASP.NET
  •  

    14 Respuestas a “Cómo firmar un documento PDF desde C# con iTextSharp”

      Alberto Pérez Alberto Pérez dijo:

      Hola Gustavo… espero no me odies, pero sigo con el mismo problema… “La clave no existe” AHGG!!.

      No sé que es lo que hago mal, pero en local me funciona y en el servidor no.
      La diferencia entre estas dos pruebas es la manera de obtener el certificado.
      En local (a través de la función “GetCertificate” de iTextSharp).
      En el servidor a través del Request.ClientCertificate indicando al IIS Requerir Canal Seguro SSL y Requerir certificado cliente.

      “Dim objCertHttp As HttpClientCertificate
      objCertHttp = Request.ClientCertificate
      Dim certificate As X509Certificate2
      certificate = New System.Security.Cryptography.X509Certificates.X509Certificate2(objCertHttp.Certificate)”

      A la hora de llamar a la función:
      PDF.SignHashed(source, target, certificate, “Razón”, “Localización”, True)

      Donde source es “C:\Inetpub\wwwroot\documentos\Prueba.pdf” y la carpeta documentos con permisos de escritura, lectura.
      y target “C:\Inetpub\wwwroot\documentos\Prueba_sign.pdf”

      Me persigue el error: “LA CLAVE NO EXISTE”.
      Creo que en local la clave existe porque cuando le doy al botón de firmar me pide el certificado (funcion getCertificate), mientras que en el servidor cuando entro en la página es cuando me pide el certificado (Request.ClientCertificate) (por el SSL de IIS) y me pide la clave, despues he de picar al botón de firmar y da el error.

      Por favor… ya se que abuso, pero necesito algo de luz!.
      Saludos

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      darmowe e booki darmowe e booki dijo:

      darmowe e booki…

      I was interested in your article. Well, there are some people who are able to present this issue in such a way. I wish you continued success and looks forward to further articles….

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Alvaro Pardo Alvaro Pardo dijo:

      Hola Gustavo,

      Estamos probando a utilizar tu ejemplo, pero nos da error al compilarlo… ya que el método SingMsg no está definido.

      ¿Podrías añadirlo al ejemplo?

      Muchas gracias

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Gustavo Cantero (The Wolf) Gustavo Cantero (The Wolf) dijo:

      Tienes razón, Álvaro, acabo de actualizar el código, me había faltado un método que justamente es el que crea la firma.
      Después cuéntame cómo te anduvo.
      Saludos.

      VN:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Juan Fran Juan Fran dijo:

      Hola, Gustavo. En primer lugar agradecerte que hayas dejado este codigo en tu blog.
      Al compilar tu codigo me da el siguiente error

      Error 2 Argumento ’1′: no se puede convertir de ‘System.Collections.Hashtable’ a ‘System.Collections.Generic.Dictionary’

      y la verdad es que estoy un poco perdido.

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Juan Fran Juan Fran dijo:

      Hola Gustavo. En primer lugar agradecerte que haya sacado de la oscuridad. Eres un crack!!
      Al compilar el codigo me dan los siguientes errores y no se como solucionarlo:

      Error 1 La mejor coincidencia de método sobrecargado para ‘iTextSharp.text.pdf.PdfSignatureAppearance.PreClose(System.Collections.Generic.Dictionary)’ tiene algunos argumentos no válidos

      Error 2 Argumento ’1′: no se puede convertir de ‘System.Collections.Hashtable’ a ‘System.Collections.Generic.Dictionary’

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Gustavo Cantero (The Wolf) Gustavo Cantero (The Wolf) dijo:

      Juan, muchas gracias por tus palabras.
      Sobre el proyecto, puede ser que tengas una versión distinta del iTextSharp. Acabo de subir en este mismo artículo un proyecto de ejemplo funcionando para que lo puedas descargar y probar.
      Cualquier consulta no dudes en escribir.
      Suerte!

      VN:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Alberto Pérez Alberto Pérez dijo:

      Primero Hola JuanFran, yo el tema de “System.Collections.Hashtable”/”System.Collections.Generic.Dictionary” lo he “solucionado (a mi me funciona)” modificando el código.
      Te lo dejo en VB.net que es lo que uso:
      ‘Dim objTable As New Hashtable()
      Dim objTable As New Dictionary(Of PdfName, Integer)
      objTable(PdfName.CONTENTS) = intCSize * 2 + 2
      objSA.PreClose(objTable)
      ———————————————–
      Hola Gustavo, estoy retomando el proyecto despues de unas vacacione si aún me encuentro algun problema, a ver si puedes ayudarme o cualquiera que pueda echarme una mano.

      - Cuando estoy en desarrollo, el proyecto funciona bien (firma el pdf), pero cuando intento publicarlo en el servidor IIS me da algun fallo.
      1-. (OPCION 1)Si publico el proyecto en un directorio virtual que SI requiere canal seguro SSL, a la hora de firmar el documento PDF me da el siguiente error:
      “La clave no existe”
      2-. (OPCION 2) Si publico el proyecto en un directorio virtual que NO requiere canal seguro SSL, al intentar acceder a la función GetCertificate me da el siguiente error:
      “The current session is not interactive”

      Os dejo el código en VB.net que estoy usando a ver si alguien da con la solución:

      Imports System.Collections
      Imports System.IO
      Imports System.Security.Cryptography
      Imports System.Security.Cryptography.Pkcs
      Imports iTextSharp.text
      Imports iTextSharp.text.pdf
      Imports Org.BouncyCastle.X509
      Imports SysX509 = System.Security.Cryptography.X509Certificates

      Imports System.Security
      Imports System.Security.Cryptography.X509Certificates
      Imports System.Security.Cryptography.X509Certificates.X509Certificate2

      ”’
      ”’ Helper para el firmado de PDFs con la librería iTextSharp
      ”’
      ”’
      Partial Class FirmaDigital02
      Inherits System.Web.UI.Page

      Protected Sub btnSignDigital_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSignDigital.Click
      Try
      ”OPCIÓN 1: DIRECTORIO VIRTUAL IIS con SSL (requerir canal seguro)
      Dim objCertHttp As HttpClientCertificate
      objCertHttp = Request.ClientCertificate
      Dim certificate As X509Certificate2
      certificate = New System.Security.Cryptography.X509Certificates.X509Certificate2(objCertHttp.Certificate)
      ‘certificate = New SysX509.X509Certificate2(objCertHttp.Certificate)

      ”OPCIÓN 2: VENTANA SELECCIÓN CERTIFICADO
      ‘Dim certificate As X509Certificate2 = GetCertificate()

      Dim source As String = “C:\Inetpub\wwwroot\TestVB\documents\signats\PruebaSignatura.pdf”
      Dim target As String = “C:\Inetpub\wwwroot\TestVB\documents\signats\PruebaSignatura_hashed2.pdf”
      SignDigital.SignHashed(source, target, certificate, “Razón”, “Localización”, True)
      lblSignaPDF.Text = “Finalitzat correctament. Comprobar signa”
      Catch ex As Exception
      lblSignaPDF.Text = lblSignaPDF.Text & “Descripció error: ” & ex.Message
      End Try
      End Sub

      Public Shared Function GetCertificate() As X509Certificate2
      Dim st As New X509Store(StoreName.My, StoreLocation.CurrentUser)

      st.Open(OpenFlags.[ReadOnly])
      Dim col As X509Certificate2Collection = st.Certificates
      Dim card As X509Certificate2 = Nothing

      Dim sel As X509Certificate2Collection = X509Certificate2UI.SelectFromCollection(col, “Certificates”, “Seleccioni un certificat per a signar”, X509SelectionFlag.SingleSelection)
      If sel.Count > 0 Then
      Dim en As X509Certificate2Enumerator = sel.GetEnumerator()
      en.MoveNext()
      card = en.Current
      End If
      st.Close()
      Return card
      End Function

      ‘Private Sub New()
      ‘End Sub
      ”’
      ”’ Firma un documento
      ”’
      ”’ Documento origen
      ”’ Documento destino
      ”’ Certificado a utilizar
      ”’ Razón de la firma
      ”’ Ubicación
      ”’ Establece si hay que agregar la firma visible al documento
      Public Shared Sub SignHashed(ByVal Source As String, ByVal Target As String, ByVal Certificate As SysX509.X509Certificate2, ByVal Reason As String, ByVal Location As String, ByVal AddVisibleSign As Boolean)
      Dim objCP As New X509CertificateParser()
      ‘NOTA PRUEBAS de X509Certificate (ambiguo “Org.BouncyCastle.X509.X509Certificate()= X509Certificate()”)
      Dim objChain As Org.BouncyCastle.X509.X509Certificate() = New Org.BouncyCastle.X509.X509Certificate() {objCP.ReadCertificate(Certificate.RawData)}

      Dim objReader As New PdfReader(Source)
      Dim objStamper As PdfStamper = PdfStamper.CreateSignature(objReader, New FileStream(Target, FileMode.Create), ControlChars.NullChar)
      Dim objSA As PdfSignatureAppearance = objStamper.SignatureAppearance

      If AddVisibleSign Then
      objSA.SetVisibleSignature(New Rectangle(100, 100, 300, 200), 1, Nothing)
      End If

      objSA.SignDate = DateTime.Now
      objSA.SetCrypto(Nothing, objChain, Nothing, Nothing)
      objSA.Reason = Reason
      objSA.Location = Location
      objSA.Acro6Layers = True
      objSA.Render = PdfSignatureAppearance.SignatureRender.NameAndDescription
      Dim objSignature As New PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1)
      objSignature.[Date] = New PdfDate(objSA.SignDate)
      objSignature.Name = PdfPKCS7.GetSubjectFields(objChain(0)).GetField(“CN”)
      If objSA.Reason IsNot Nothing Then
      objSignature.Reason = objSA.Reason
      End If
      If objSA.Location IsNot Nothing Then
      objSignature.Location = objSA.Location
      End If
      objSA.CryptoDictionary = objSignature
      Dim intCSize As Integer = 4000

      ‘Dim objTable As New Hashtable()
      Dim objTable As New Dictionary(Of PdfName, Integer)
      objTable(PdfName.CONTENTS) = intCSize * 2 + 2
      objSA.PreClose(objTable)

      Dim objSHA1 As HashAlgorithm = New SHA1CryptoServiceProvider()

      Dim objStream As Stream = objSA.RangeStream
      Dim intRead As Integer = 0
      Dim bytBuffer As Byte() = New Byte(8191) {}
      While (InlineAssignHelper(intRead, objStream.Read(bytBuffer, 0, 8192))) > 0
      objSHA1.TransformBlock(bytBuffer, 0, intRead, bytBuffer, 0)
      End While
      objSHA1.TransformFinalBlock(bytBuffer, 0, 0)

      Dim bytPK As Byte() = SignMsg(objSHA1.Hash, Certificate, False)
      Dim bytOut As Byte() = New Byte(intCSize – 1) {}

      Dim objDict As New PdfDictionary()

      Array.Copy(bytPK, 0, bytOut, 0, bytPK.Length)

      objDict.Put(PdfName.CONTENTS, New PdfString(bytOut).SetHexWriting(True))
      objSA.Close(objDict)
      End Sub

      ”’
      ”’ Crea la firma CMS/PKCS #7
      ”’
      Private Shared Function SignMsg(ByVal Message As Byte(), ByVal SignerCertificate As SysX509.X509Certificate2, ByVal Detached As Boolean) As Byte()
      ‘Creamos el contenedor
      Dim contentInfo As New ContentInfo(Message)
      ‘Instanciamos el objeto SignedCms con el contenedor
      Dim objSignedCms As New SignedCms(contentInfo, Detached)
      ‘Creamos el “firmante”
      Dim objCmsSigner As New CmsSigner(SignerCertificate)
      ‘ Include the following line if the top certificate in the
      ‘ smartcard is not in the trusted list.
      objCmsSigner.IncludeOption = SysX509.X509IncludeOption.EndCertOnly
      ‘ Sign the CMS/PKCS #7 message. The second argument is
      ‘ needed to ask for the pin.
      objSignedCms.ComputeSignature(objCmsSigner, False)
      ‘Encodeamos el mensaje CMS/PKCS #7
      Return objSignedCms.Encode()
      End Function

      Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
      target = value
      Return value
      End Function
      End Class

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Alberto Pérez Alberto Pérez dijo:

      Hola Gustavo y compañía.
      He visto que has dejado un proyecto para descargar y probar (funciona perfectamente) y creo que he encontrado el porque de uno de los dos errores que me dan.
      El error (OPCION 2) que comento anteriormente se debe a que está utilizando X509Certificate2UI que solo se puede utilizar en una aplicación interactiva, como aplicaciones de WINDOWS FORMS.
      Esta funcion muestra la interfaz de usuario cuadros de dialogo que le permiten seleccionar y ver los certificados X.509.
      Esto no puede ser utilizado en APLICACIONES WEB asp.net (que es lo que estoy creando)
      Ahora solo me queda la opción 1, guardar la clave y poder usarla.. si alguien sabe como ¿?

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Gustavo Cantero (The Wolf) Gustavo Cantero (The Wolf) dijo:

      Hola, Alberto. Si, como dices, no puedes utilizar la interfaz desde una aplicación web.
      Sobre el punto 1, seguramente el problema sea que el certificado a utilizar lo tienes instalado en un repositorio de usario, por lo tanto, cuando la aplicación se ejecuta en producción no lo encuentra. Prueba instalando tu certificado (con la clave privada) en un repositorio de la máquina (no de usuario) en el servidor utilizando la herramienta “certmgr”. En este artículo muestro un ejemplo de cómo hacerlo: Crear certificados de prueba para servidor y cliente.
      Suerte!

      VN:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Juan Juan dijo:

      ¿Estos ejejmplos corren con Vb2005?

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Gustavo Cantero (The Wolf) Gustavo Cantero (The Wolf) dijo:

      El proyecto está hecho en Visual Studio 2008 con C#, pero se podría pasar facilmente a un proyecto de Visual Basic.NET con Visual Studio 2005.
      Podrías probar con esta herramienta para migrar código C# a VB.NET: http://www.developerfusion.com/tools/convert/csharp-to-vb.
      Cualquier consulta no dudes en escribirla aquí.
      Suerte!

      VN:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Juan Juan dijo:

      Gracias gustavo, ando buscando una manera para firmar pdf’s yme ha parecido simple…voy a probarlo…¿El ejemplo valido es el que has puesto para descargar?….

      VA:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)
      Gustavo Cantero (The Wolf) Gustavo Cantero (The Wolf) dijo:

      El proyecto de ejemplo tiene la misma clase que muestro en este artículo, pero tiene una ventana para ingresar el PDF a firmar y poder probar más rápido esta clase.
      Cualquier otra consulta no dudes en escribirla aquí.
      Suerte!

      VN:F [1.7.3_972]
      Rating: 0.0/5 (0 votos cast)

    Responder

    XHTML: Puede utilizar estos tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>