Twitter Facebook RSS Feed

viernes, 21 de agosto de 2009 a las 09:20hs por Gustavo Cantero (The Wolf)

Luego de mis artículos sobre certificados digitales, firma digital y hash sólo me queda escribir el último de esta serie en donde me gustaría mostrar cómo buscar, leer y utilizar estos certificados X.509 desde .NET.

Consultar los repositorios de certificados

Como primer paso vamos a recorrer los certificados que tenemos instalados en nuestra máquina. Para esto debemos utilizar la clase X509Store, la cual nos da la posibilidad de consultar un repositorio de certificados, por ejemplo, el repositorio raíz (Root), el repositorio donde están los certificados de las autoridades certificantes (CertificateAuthority), o el repostorio personal (My). También tenemos que elegir la ubicación del certificado, es decir, si vamos a querer consultar los certificados que están a nivel de máquina o de usuario. Tengan en cuenta que si van a utilizar la clase X509Store desde ASP.NET deben leer los certificados que están a nivel de máquina, ya que el usuario ASP.NET rara vez va a tener algún certificado instalado.

Luego de elegido el repositorio que queremos abrir debemos utilizar el método Open para que consulte al mismo.

Una vez abierto el repositorio, la propiedad Certificates contendrá una colección (basada en la clase X509Certificate2Collection) de los certificados almacenados.

Después de utilizar el repositorio no hay que olvidar de cerrar el mismo con método Close.

A continuación muestro cómo abrir el repositorio personal y escribir en la consola de debug los nombres y firmas digitales (hash) de cada certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
...
X509Store objStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
objStore.Open(OpenFlags.ReadOnly);

foreach (X509Certificate2 objCert in objStore.Certificates)
    Debug.Print(objCert.SubjectName.Name + ": " + objCert.Thumbprint);

objStore.Close();

Consultar la información del certificado y sus extensiones

Cada certificado posee una serie de datos (los cuales se describen en el artículo Conceptos de Certificado Digital y Firma Digital y creamos en el artículo Crear certificados de prueba para servidor y cliente). Además de estos datos los certificados poseen extensiones, que definen distintos posibles usos dependiendo de las mismas, y según la extensión poseen distintos datos almacenados. Por ejemplo, en la extensión del tipo “2.5.29.37” (también llamada “Enhanced Key Usage”) se guarda cual va a ser el destino del certificado, por ejemplo para autenticación del cliente.

Estas extensiones están en la colección Extensions del certificado, y para obtener la información almacenada debe castearse según el tipo del mismo. Para saber qué tipo de extensión es la almacenada se puede leer la propiedad Oid, la cual devuelve la clase homónima que representa un identificador de un objeto criptográfico (cryptographic object identifier) y luego, de este objeto, podemos leer la propiedad FriendlyName para obtener el nombre de la extensión.

A continuación hay un código de ejemplo donde se muestra la información de un certificado:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
StringBuilder objSB = new StringBuilder("Detalle del certificado: \n\n");

//Detalle
objSB.AppendLine("Persona = " + objCert.Subject);
objSB.AppendLine("Emisor = " + objCert.Issuer);
objSB.AppendLine("Válido desde = " + objCert.NotBefore.ToString());
objSB.AppendLine("Válido hasta = " + objCert.NotAfter.ToString());
objSB.AppendLine("Tamaño de la clave = " + objCert.PublicKey.Key.KeySize.ToString());
objSB.AppendLine("Número de serie = " + objCert.SerialNumber);
objSB.AppendLine("Hash = " + objCert.Thumbprint);

//Extensiones
objSB.AppendLine("\nExtensiones:\n");
foreach (X509Extension objExt in objCert.Extensions)
{
    objSB.AppendLine(objExt.Oid.FriendlyName + " (" + objExt.Oid.Value + ')');

    if (objExt.Oid.FriendlyName == "Key Usage")
    {
        X509KeyUsageExtension ext = (X509KeyUsageExtension)objExt;
        objSB.AppendLine("    " + ext.KeyUsages);
    }

    if (objExt.Oid.FriendlyName == "Basic Constraints")
    {
        X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)objExt;
        objSB.AppendLine("    " + ext.CertificateAuthority);
        objSB.AppendLine("    " + ext.HasPathLengthConstraint);
        objSB.AppendLine("    " + ext.PathLengthConstraint);
    }

    if (objExt.Oid.FriendlyName == "Subject Key Identifier")
    {
        X509SubjectKeyIdentifierExtension ext = (X509SubjectKeyIdentifierExtension)objExt;
        objSB.AppendLine("    " + ext.SubjectKeyIdentifier);
    }

    if (objExt.Oid.FriendlyName == "Enhanced Key Usage") //2.5.29.37
    {
        X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)objExt;
        OidCollection objOids = ext.EnhancedKeyUsages;
        foreach (Oid oid in objOids)
            objSB.AppendLine("    " + oid.FriendlyName + " (" + oid.Value + ')');
    }
}
Debug.Print(objSB.ToString());

Validar certificados

Una de las tareas comunes que se realizan cuando estamos trabajando con certificados es la validación de los mismos. Esta tarea incluye verificar las fechas de validez, chequear que el certificado no haya sido revocado, etc. Esta validación debe hacerse en todos y cada uno de los certificados de la cadena hasta el certificado raíz.

Para validar la cadena de certificados se puede utilizar la clase X509Chain. Esta clase posee varias propiedades para especificar la forma en la que se quiere hacer la validación, por ejemplo, en la propiedad RevocationFlag se puede establecer si queremos validar sólo el certificado final (X509RevocationFlag.EndCertificateOnly), toda la cadena excepto el raíz (X509RevocationFlag.ExcludeRoot) o la cadena completa (X509RevocationFlag.EntireChain). También puede establecerse la forma en la que se quiere validar las revocaciones a través de la propiedad RevocationMode, pudiendo seleccionar en línea (X509RevocationMode.Online), fuera de línea (X509RevocationMode.Offline) o no chequear las listas de revocados (X509RevocationMode.NoCheck).

Para iniciar la validación, en la instancia de la clase X509Chain, se debe utilizar el método Build pasándole como parámetro el certificado. Luego de esto, en la propiedad ChainStatus, vamos a encontrar la lista de errores en las validaciones o, en caso de estar vacío este vector, significa que el certificado es válido.

Acá les dejo un código de ejemplo donde validamos un certificado y toda la cadena:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
X509Chain objChain = new X509Chain();

//Verifico toda la cadena de revocación
objChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;

//Timeout para las listas de revocación
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

//Verificar todo
objChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

//Se puede cambiar la fecha de verificación
//objChain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

objChain.Build(objCert);

if (objChain.ChainStatus.Length != 0)
    foreach (X509ChainStatus objChainStatus in objChain.ChainStatus)
        Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation);
else
    Debug.Print("Ok");

Firma de datos

Una de las tareas más comunes para las cuales se utilizan los certificados es para firmar documentos. Para esto casi siempre se utiliza el estándar de sintaxis para mensajes criptográficos PKCS #7, el cual está definido en el RFC 2315.

En .NET, para crear firmas digitales utilizando PKCS #7, se pueden utilizar las clases que están en el namespace System.Security.Cryptography.Pkcs.
Pienso que lo mejor para explicar cómo hacerte esto es a través de un ejemplo, por lo tanto, a continuación muestro un ejemplo donde creo un documento con su firma digital a partir de un texto:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Documento con firma: " + Convert.ToBase64String(bytSigned));

Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent, true);

//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);

//Firmamos los datos
objSignedData.ComputeSignature(objSigner);

//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();

Debug.Print("Firma digital: " + Convert.ToBase64String(bytSigned));

Verificar firma

Obviamente si creamos un documento con una firma digital es porque, tarde o temprano, vamos a querer verificar esta firma. Para realizar esto utilizamos nuevamente la clase SignedCms, la cual posee el método CheckSignature, y en caso de ser un documento firmado (no la firma “suelta”), en la propiedad ContentInfo.Content va a estar el documento sin firma.

A continuación les dejo los dos ejemplos, la verificación de la firma digital:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytFirma = ... //Acá tenemos que poner la firma digital calculada en el ejemplo anterior
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

SignedCms objDatos = new SignedCms(objContent, true);

//Deserealizamos la firma
objDatos.Decode(bytFirma);

try
{
//Verificamos si la firma concuerda con los datos
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}

Y el ejemplo de la verificación de la firma y obtención del documento:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDocFirmado = ... //Acá tenemos que poner el documento firmado obtenido en el ejemplo anterior
SignedCms objDatos = new SignedCms();

//Deserializo los bytes PKCS#7
objDatos.Decode(bytDocFirmado);

//Verifico la firma y obtengo el documento
try
{
    objDatos.CheckSignature(true);
    Debug.Print("Ok - La firma concuerda con los datos");
    Debug.Print(Encoding.ASCII.GetString(objDatos.ContentInfo.Content));
}
catch
{
    Debug.Print("Error - La firma no concuerda con los datos");
}

Encriptación y desencriptación utilizando un certificado

Por último voy a mostrar como encriptar y desencriptar documentos utilizando certificados digitales. Para esto, al igual con la firma de documentos, voy a mostrar un ejemplo, que creo es la mejor manera de entenderlo:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado

//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));

//Creamos el objeto que representa los datos firmados
EnvelopedCms objEncryptedData = new EnvelopedCms(objContent);

//Creamos el destino
CmsRecipient objRecipient = new CmsRecipient(objCert);

//Encriptamos los datos
objEncryptedData.Encrypt(objRecipient);

//Datos encriptados
byte[] bytResult = objEncryptedData.Encode();

Debug.Pring("Datos encriptados:  " + Convert.ToBase64String(bytResult));

Y ahora voy a mostrar como desencriptar los datos obtenidos en el paso anterior. Para esto previamente tenemos que tener instalado el certificado con el que se encriptaron estos datos, junto con su clave privada, en un repositorio sobre el cual el usuario tenga permisos:

using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDatos = … //Datos encriptados

EnvelopedCms objEncryptedData = new EnvelopedCms();

//Leemos los datos encriptados
objEncryptedData.Decode(bytDatos);

//Desencriptamos los datos
objEncryptedData.Decrypt();

//Documento original
byte[] bytDoc = objEncryptedData.ContentInfo.Content;

//Mostramos el resultado
Debug.Print("Datos desencriptados: " + Encoding.ASCII.GetString(bytDoc));

Espero que este resumen pueda servirle a cualquier que necesite trabajar con certificados digitales desde .NET.

Artículos relacionados

153 comentarios »

  1. Santiago dice:

    Excelente serie. De mucha utilidad

  2. Santiago dice:

    Hola Gustavo:
    Nuevamente felicitarte por la serie. Probando el codigo fuente en lo referente a firmar datos obtengo CryptographicException «El conjunto de claves no existe» cuando se invoca al metodo objSignedData.ComputeSignature(objSigner);
    Estoy usando certificados de prueba tal cual lo describiste en el inicio de serie.
    Puedes indicarme cual es el problema? Muchas gracias.

    • Santiago: Muchas gracias por las felicitaciones.
      Sobre el error, parecería ser porque no encuentra el certificado digital. ¿Guardaste un certificado en la variable objCert? De ser así: ¿El usuario tiene permisos para leer la clave privada del mismo? Ten en cuenta que si lo estás usando desde ASP.NET, el usuario «ASP.NET» debe tener permisos para leer el certificado.
      Si sigue fin funcionar te invito a nuestro foro en http://foro.scientia.com.ar para dejar tus consultas, las cuales trataremos de responder a la brevedad.
      Espero tus comentarios.
      Suerte!

      • jonatan dice:

        Hola Buenos dias, me pasa exactamente lo mismo. Pero no se cómo darle permisos al usuario ASP.NET.
        Me podrian dar una mano con esto? por favor?
        Muchas gracias y felicitaciones por el blog. Lo sigo siempre.

        Saludos

  3. Santiago dice:

    Gracias por responder. El problema estaba en los permisos.
    Saludos

  4. Jonathan dice:

    Primera que nada felicitaciones un muy buen aporte.
    Estoy haciendo una aplicacion vb net, para firmar un documento xml o parte de este, y lo unico que me entregan es un archivo en formato .PEM, y no he encontrado como hacerlo..seria una gran ayuda si me das un empujoncito.
    Saludos, Jonathan

  5. jonas13 dice:

    Hola..felicitaciones me ha ayudado bastante.
    Necesito ayuda, estoy haciendo una aplicacion en vb .net y necesito firma un documento xml o parte de este, para ello tengo una string con la rsa key private (ese texto que esta en base 64 entre —begin rsa private key y end rsa private key), mi duda es como firmo este documento teniendo esta llave privada..Un abrazo y ojala me ayudes porfa.

  6. Leonardo Quintana dice:

    hola felicitaciones por el articulo y el código son bastante token.

    estoy realizando la validación del certificado, pero después de correr el codigo me muestra el siguiente mensaje:
    «RevocationStatusUnknown – La función de revocación no puede comprobar la revocación para el certificado»

    que puede estar pasando… entre los parametros se encuentra objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; que tiene que ver con la CRL pero en donde le puedo decir la URL en la cual debe buscar la CRL o como se hace esta parte.

    muchas gracias de antemano.

    saludos…

    • Leonardo:
      Antes que nada, gracias por los elogios.
      Sobre tu consulta: la URL de la lista de revocación está dentro del certificado. A simple vista parece ser que no puede obtener la lista, y creo que las posibles causas debe ser alguna de las siguientes:

      • La PC donde está corriendo la aplicación no tiene conexión a internet
      • El servidor donde está la lista está caido o ya no existe
      • Demora mucho en obtener la lista. Para esto puedes aumentar el tiempo modificando la propiedad UrlRetrievalTimeout asi:
        objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 3, 0);

      Cualquier otra consulta no dudes en escribirnos.
      Suerte!

  7. Leonardo Quintana dice:

    Hola Gustavo

    he verificado los puntos 1 y 2 accediendo directamente a la URL de la CRL que trae el certificado el cual descarga un archivo .crl, y le modifiqué el tiempo de espera, pero aun asi me sigue presentando el mismo mensaje «RevocationStatusUnknown – La función de revocación no puede comprobar la revocación para el certificado.»

    Por otro lado tambien estoy intentando firmar con el ejemplo tuyo, pero todos los certificados que posee mi maquina requieren password, no encontre en el ejemplo como le paso el password, le agradezco si me puede por favor indicar como hacerlo.

    saludos

    • Leonardo, te pido mil disculpas por la demora en la respuesta.
      Sobre tu problema, imagino que el problema no está en el certificado que necesitas validar sino en alguno de la cadena de certificación, por ejemplo, el que se utiliza como entidad certificadora de éste.
      Espero que puedas resolver el inconveniente, sino te invito a nuestro foro http://foro.scientia.com.ar donde intentaremos ayudarte lo más rápido posible.
      Saludos y suerte.

  8. Jonathan dice:

    Al tratar de desencriptar los datos, nos un error de «Clave Inválida». el error da en la siguiente linea de código:

    objEncryptedData.Decrypt();

    Puedo leer certificados, encriptar documentos sin ningun problema.

    Por favor si me pueden ayudar,
    Saludos,
    Jonathan.

    • Dario Krapp dice:

      Hola Jonathan,
      Alguna vez nos pasó algo similar y el problema estaba relacionado con la longitud de los datos que estabamos intentando encriptar, ahora no recuerdo los límites (creo que eran 117 bytes) pero cuando probamos con datos más cortos el problema se resolvió, claro que no es la idea acortar los datos por que esa opción no es posible en casi ninguna ocasión.
      Quizás una posible solución (al menos a nosotros nos sirvió) sea utilizar algorítmos asimétricos para encriptar las claves y algorítmos simétricos para encriptar la información, ya que estos no poseen esta restricción.
      Espero que esta respuesta te sirva.

  9. Leonardo Quintana dice:

    Buenas Tardes

    Bueno la solución para que el código pida la clave del certificado digital almacenado en el store es simplemente colocar un false

    signedCms.ComputeSignature(cmsSigner, false);

    pero ahora tengo otro problema, necesito firmar un archivo extremadamente grande y el código que tenemos hasta el momento solo firma hasta 80 MB con archivos por ejemplo de un archivo de 1 GB no lo puede firmar dado que saca un error de memoria.

    si alguien sabe de esto por favor les agradeceria me dieran la solución

    cuando tenga el código completo lo pìenso subir… por ahora mi correo es jlqr@hotmail.com por si necesitan algo.. no se mucho pero igual ya tengo funcionando un pedazo de código.. y todo gracias a lo que he visto en esta página y en algunas otras…

  10. juan dice:

    Hola, queria saber como levantar una clave privada que se encuentra en un archivo.p12 desde .net? desde ya muchas gracias

  11. Hola Fredys, acá respondo la pregunta que hiciste en mi página: para obtener un certificado tienes varias formas, la más común es abrir un repositorio, por ejemplo el del usuario, y buscar por el nombre u otra información. Un ejemplo de lo mencionado es el siguiente:

    X509Certificate2 objCert;
    X509Store objStore = new X509Store(StoreLocation.CurrentUser);
    X509Certificate2Collection objCol = objStore.Certificates.Find(X509FindType.FindByIssuerName, "Gustavo", true);
    if (objCol.Count > 0)
    {
        objCert = objCol[0];
        //Sigo usando el certificado
    }

    Otra posibilidad sería abrir el certificado desde un archivo:

    X509Certificate2 objCert = new X509Certificate2("Archivo.pfx");

    o también podrias leer el certificado desde la base de datos u otro lado:

    byte[] bytCert = //Obtengo el certificado de algún lado, como la base de datos
    X509Certificate2 objCert = new X509Certificate2(bytCert);

    Espero que esta información te sea de utilidad, cualquier otra cosa no dudes en escribir.
    Suerte!

  12. Fredys Simanca dice:

    Gracias Gustavo por su ayuda y colaboración para quienes se la solicitamos, me ha funcionado perfectamente el asunto, mi objetivo era encriptar una cadena de texto para realizar una validación ante un servicio web, y bien excelente, simplemente que al momento de descencriptar la cadena me arroja error en la linea: objEncryptedDataD.Decrypt(); en la parte de arriba leía que posiblemente era por el tamaño de la cadena, no es mi caso ya que es corta. Dice que no puede encontrar el objeto o la propiedad.

    Muchas gracias por su ayuda,

    • Hola Fredys! Necesitaría consultarte dos cosas para poder ayudarte: la primera es la longitud de lo que quieres encriptar, y la segunda es el mensaje exacto del error.
      Saludos.

      • Jose Emmanuel dice:

        Hola Gustavo

        No se, si todavia revises esto, ya que veo que es de hace 10 años, pero bueno si corro con suerte. Tengo el mismo problema que Fredys, devuelve esto «No puede encontrar el objeto o la propiedad\r\n»
        Podrías orientarme a la solución. Esto es debido al certificado?

  13. Luis dice:

    Hola, muy interesante tu publicación, me gustaría saber como puedo encriptar documentos en pdf con un certificado digital, muchas gracias.

  14. Antonio Manrique dice:

    Hola Gustavo,
    Tus articulos son de mucha utilidad.
    estoy realizando una aplicacioon con vb net
    y la duda que tengo es que me dan una llave privada .key( que correspon de al emisor ) y un certificado .cer
    la duda como encriptar la salida del mD5 que ya la tengo usando la llave privada,
    en tu articulo encriptas y desecriptas pero no observo donde se utiliza la llave privada , en mi caso seria .key

    saludos.

  15. Alberto Pérez dice:

    Hola Gustavo.
    Lo primero es felicitarte por el artículo sobre certificados digitales.
    Pese a que no me acaba de salir del todo (por mi falta de nociones), sé que el artículo es bueno y me está ayudando mucho.
    Tengo unas cuantas dudas, pero para no cansar de momento solo haré una pregunta.
    Trabajo con VS2008 en vb y a la hora de importar el espacio de nombres System.Scurity.Cryptography.Pkcs no me aparece, a que puede ser debido? (lo más parecido es System.Security.Cryptography.PKCS1MaskGenerationMethod), pero entonces el código me marca error. Alguna idea?

  16. Alberto Pérez dice:

    Otra vez. MUCHAS GRACIAS y felicidades por el artículo es muy bueno y me está ayudando muchisimo.
    Efectivamente era la´referencia (se ve que por defecto no debía venir, pero la agregue y parece que empieza a funcionar).
    Hasta ahora ya leo el certificado del usuario, repositorio servidor, y extensiones&info.
    La idea es que pueda firmar un pdf alojado en el servidor….continuo en ello a ver si lo consigo…

  17. Alberto Pérez dice:

    Hola Gustavo, que tal?
    De nuevo gracias por el aporte.
    He estado trabajando el código de tus ejemplos y pese a que voy avanzando, me he quedado estancado en estos dos:

    Validar certificados (me devuelve):
    RevocationStatusUnknown – La función de revocación no puede comprobar la revocación para el certificado.
    Firma documento (me devuelve):
    La clave no existe.

    En cuanto a lo de iTextSharp
    Mi intención es leer el certificado de cliente y firmar un pdf, así que supongo que el que me interesa es el último punto:
    «How to sign with a smartcard using an external signature dictionary with iTextSharp and .NET 2.0»

    Lo estoy intentando traducir de CSharp a VB con la ayuda de (http://www.developerfusion.com/tools/convert/csharp-to-vb/).
    Pero el código me marca 2 errores, uno en:
    sap.PreClose(exc);
    – Me dice que exc «Un valor de tipo «System.Collection.Hastable» no se puede convertir en «System.Collections.Generic.Dictionary(Of iTextSharp.text.pdf.PdfName,Integer)»

    y otro en:
    While (InlineAssignHelper(read, s.Read(buff, 0, 8192))) > 0
    sha.TransformBlock(buff, 0, read, buff, 0)
    End While
    – Me marca como error: «No se ha declarado el nombre InlineAssignHelper»

    Si me pudieses orientar te lo agradecería.
    Una vez tengo el código, lo puedo dejar en el foro para aquellos que usen VB y lo necesiten.
    Saludos.

  18. Alberto Pérez dice:

    Hola Gustavo.
    Yo sigo intendando firmar pdf’s 😉
    De los problemas planteados creo que he solucionado alguno…pongo la solución por si ha alguien le pasa lo mismo.

    Con el código de iTextsharp
    1-. “Un valor de tipo “System.Collection.Hastable” no se puede convertir en “System.Collections.Generic.Dictionary(Of iTextSharp.text.pdf.PdfName,Integer)”
    Donde ponía:
    Dim exc As New Hashtable()
    Lo he sustituido por:
    Dim exc As New Dictionary(Of PdfName, Integer)()

    2-. “No se ha declarado el nombre InlineAssignHelper”
    Lo he suprimido y dejado así:
    While s.Read(buff, 0, 8192) > 0
    sha.TransformBlock(buff, 0, read, buff, 0)
    End While

    En local me firma el pdf, aunque me deja la alerta de que la firma no es valida porque se ha modificado el documento.
    Y en el servidor me da el error de:
    (si leo el certificado con iis):
    «La clave no existe»
    o (si leo el certificado con la funcion de itextsharp)
    «The current session is not interactive»

    Si teneis alguna idea, os lo agradecería.

  19. beto hdez dice:

    Hola Gustavo buscando como firmar una cadena llegue a tu blog. Mira estoy realizando una aplicacion en c# para sellar una factura el proceso es este:
    1- con los datos de la factura se crea una cadena (Ya lo pude hacer)
    2- Sacas el digest md5 de la cadena(Ya lo pude hacer)
    3- hacienda (la oficina que recauda impuestos en Mexico )te da para pruebas dos archivos uno aaa010101aaa_CSD_01.cer, aaa010101aaa_CSD_01.key y la clave del key privada ‘a0123456789’ los archivos estan en formato DER PKCS8
    4- Con la clave privada que se encuentra en el archivo aaa010101aaa_CSD_01.key se debe firma el resultado de la digestion md5 usando el algoritmo RSA esto ultimo es lo que no he se como hacer.

    He visto alguno ejemplos em MSDN pero solo ultizan archivos xml para el RSA
    no se si se pueda ultizar los archivos DER PKCS8 en .NET

    Espero que me puedas ayudar o darme alguna orientacion donde pueda investigar para poder relizarlo.

  20. beto hdez dice:

    Gracias por los enlaces ya los checo

  21. Rich Rod dice:

    Hola, realmente te felicito por este aporte, es utilísimo, necesito hacer un proyecto en C# el cual coja ficheros XML, los comprima en Zip (uno a uno) y luego firme digitalmente de forma Detached (con PKCS7#) usando un CD Personal, creo que esto es con tu ejemplo «Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:» pero necesito hacerlo con ficheros en lugar que con texto, a medida que vaya avanzando te voy indicando del avance.

    Un Saludo y vuelvo a agradecerte por tu aporte

    • Hola, Rich. Fíjate que en mi ejemplo el texto con convierto a un vector de bytes, ahí deberías poner los bytes de tu archivo en lugar de los del texto.
      Acá te paso un ejemplo que debería funcionarte, deberías reemplazar la línea 8 por esto:

      byte[] bytes;
      using (FileStream In = File.OpenRead("c:\archivo.bin"))
      using (MemoryStream MS = new MemoryStream())
      using (GZipStream Zip = new GZipStream(MS, CompressionMode.Compress, true))
      {
          In.CopyTo(Zip);
          bytes = MS.GetBuffer();
      }
      ContentInfo objContent = new ContentInfo(bytes);

      Espero que te sirva.
      Suerte!

  22. Rich Rod dice:

    Hola Gustavo, ya comencé con el proyecto que te he comentado, voy a hacer una forma que muestre los CD y que Firme un String de Texto, y que haga l propio con un fichero, al tener algo avanzado te lo dejo saber por si alguien lo necesita.

    un Saludo-

  23. Rich Rod dice:

    Hola Gustavo, he hecho algunos avances, pero no logro generar el fichero .p7s, he logrado ubicar los CD, encriptar un String, pero no sé como hacer para generar el fichero .p7s detached, que es el que se debe enviar junto con el fichero original. te adjunto el código que llevo hasta ahora (de más está decir que estoy incursionando en esto del C# recientemente) y aprovecho para volver a agradecerte por tus aportes, por otro lado, el código me falla en la instruccion «CopyTo»,

    Error 1 ‘System.IO.FileStream’ does not contain a definition for ‘CopyTo’ and no extension method ‘CopyTo’ accepting a first argument of type ‘System.IO.FileStream’ could be found (are you missing a using directive or an assembly reference?)

    te dejo el Código:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Diagnostics;
    using System.Security.Cryptography.Pkcs;
    using System.IO;
    using System.IO.Compression;
    
    namespace form
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, EventArgs e)
            {
             X509Store objStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); //StoreLocation.LocalMachine);
             objStore.Open(OpenFlags.ReadOnly);
               foreach (X509Certificate2 objCert in objStore.Certificates)
                rtxtCerts.Text = rtxtCerts.Text + objCert.SubjectName.Name + ": " + objCert.Thumbprint + "\n";
                objStore.Close();
            }
    
            private void btnExcrypt_Click(object sender, EventArgs e)
            {
                try
                {
                X509Certificate2 objCert = new X509Certificate2(@"C:\Documents and Settings\USERNAME\Datos de programa\Microsoft\SystemCertificates\My\Certificates\THECERTIFICATECODE"); 
                ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes(txtSource.Text));
                SignedCms objSignedData = new SignedCms(objContent, true);
                CmsSigner objSigner = new CmsSigner(objCert);
                objSignedData.ComputeSignature(objSigner);
                byte[] bytSigned = objSignedData.Encode();
                rtxtEncrypted.Text = Convert.ToBase64String(bytSigned);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Exception caught " + ex.ToString());
                }
            }
    
            private void btnCryptFile_Click(object sender, EventArgs e)
            {
                try
                {
                 MemoryStream destination = new MemoryStream();
                 X509Certificate2 objCert = new X509Certificate2(@"C:\Documents and Settings\rerodriguez\Datos de programa\Microsoft\SystemCertificates\My\Certificates\THECERTIFICATECODE"); 
                 using (FileStream fsSource = new
    // File 2 Encrypt (pedido en la forma)
     FileStream(txtFile2Crypt.Text, FileMode.Open, FileAccess.Read))
                 using (MemoryStream MS = new MemoryStream())
                 using (GZipStream Zip = new GZipStream(MS, CompressionMode.Compress, true))
                 {
    
                   //  fsSource.CopyTo(Zip);  Error no le gusta
    
                     byte[] bytes = new byte[fsSource.Length];
                     int numBytesToRead = (int)fsSource.Length;
                     int numBytesRead = 0;
                     while (numBytesToRead > 0)
                     {
                         int n = fsSource.Read(bytes, numBytesRead, numBytesToRead);
                         if (n == 0)
                             break;
    
                         numBytesRead += n;
                         numBytesToRead -= n;
                     }
                     numBytesToRead = bytes.Length;
    
                ContentInfo objContent = new ContentInfo(bytes);
                SignedCms objSignedData = new SignedCms(objContent, true);
                CmsSigner objSigner = new CmsSigner(objCert);
    
                objSignedData.ComputeSignature(objSigner);
    
                byte[] bytSigned = objSignedData.Encode();
                rtxtEncrypted.Text = "Fichero Firmado"; // Convert.ToBase64String(bytSigned);
                }    
                 }
                catch (Exception ex)
                {
                    MessageBox.Show("Exception caught here" + ex.ToString());
                }
            }
        }
    }
    
  24. Sebastian dice:

    Hola Gustavo muy bueno tu aporte. la verdad que lamento no haberlo encontrado cuando empeze a lidiar con certificados me hubiese ahorrado un par de dolores de cabeza. aunque todavia tengo uno.
    Paso a comentarte cual es el Problema:
    Debo firmar una parte de un xml que envio a una entidad, logro encriptarla y firmarla pero la misma me dice que la firma no es valida. El Certificado ya esta validado y llego al web services de ellos por lo que descarto que por el Certificado no llegue ya que recibo un error de que la firma no es valida por parte de ellos.
    Estoy intentando hacerlo de esta manera

            Dim hasher As New SHA1CryptoServiceProvider()
            Dim _Certificado As New X509Certificate2("certificado", "pass")
            'CREO OBTENER LA CLAVE PRIVADA PARA LUEGO CON EL OBJETO rsa poder firmar
            Dim rsa As RSACryptoServiceProvider = _Certificado.PrivateKey
    
    
            Dim msbyt() As Byte
    
            Dim byteconverter As New ASCIIEncoding()
    
            msbyt = byteconverter.GetBytes("XML a encriptar")
    
            Dim hshbyt() As Byte
    
            Dim sha As New SHA1CryptoServiceProvider()
    
            hshbyt = sha.ComputeHash(msbyt)
    
            Dim sgnbyt() As Byte
    
            'FIRMO EL DOCUMENTO
            sgnbyt = rsa.SignHash(hshbyt, "SHA1")
    
            Return Convert.ToBase64String(sgnbyt)

    esto no produce errores ni nada sino que me devuelven como que la clave es invalida. Lo que busco hacer es poder levantar la clave privada de mi certificado para poder firmarla el tema es que no logro levantar dicha clave. Cualquier ayuda me sirve sin mas y agradeciendo por adelantado. te mando saludos

    • Hola, Sebastián, gracias por los cumplidos.
      Sobre tu problema, puede ser que el CA del certificado con el que firmaste tus datos no esté instalado en un repositorio de confianza en el servidor destino? ¿Podés verificar la firma en tu máquina para ver si da algún error?
      Suerte!

  25. jomarmen dice:

    Hola, primero que nada fantástico articulo.

    Estoy teniendo problemas a la hora de desencriptar un masaje mediante certificado, he seguido tus pasos pero obtengo este error cuando intento realizar el decrypt

    El mensaje con datos envueltos no contiene el recipiente especificado.

    Concretamente en la linea

    objEncryptedData.Decrypt();

    No entiendo en tu código porque cuando desencriptas no le indicas el certificado pero cuando encriptas si.

    un saludo

  26. reinaldovb dice:

    Buenos dias, muchas gracias por ayudarnos son pocas las personas q comparten su conocimiento.

    tengo un problema al generar la firma de un texto en este caso es un string de un conjunto de tag XML.

    el codigo es el siguiente VB.NET

            Dim _Certificado As New X509Certificate2("C:\certificadodigital.pfx", "1234")
    
            'Dim rsa As RSACryptoServiceProvider = _Certificado.PrivateKey
            Dim rsa As New RSACryptoServiceProvider(512)
    
            Dim msbyt() As Byte
    
            Dim byteconverter As New ASCIIEncoding()
    
            msbyt = byteconverter.GetBytes("Texto a Encriptar")
    
            Dim hashbyte() As Byte
    
            Dim sha As New SHA1CryptoServiceProvider()
    
            hashbyte = sha.ComputeHash(msbyt)
    
            Dim sgnbyt() As Byte
    
            'FIRMO EL DOCUMENTO
            sgnbyt = rsa.SignHash(hashbyte, "SHA1")
    
            Debug.Print (Convert.ToBase64String(sgnbyt))
    

    Mi problema radica en que el certificado al poseer un largo de llave 1024 como resultado me entrega 172 caracteres.
    pero si no cargo la llave del certificado y genero un RSACryptoServiceProvider de 512(por defecto crea de 1024) asi q lo cambio RSACryptoServiceProvider(512). esto me genera mis 88 caracteres finales. pero obiamente no me sirve por que no se genera con las llaves del certificado.
    estas son las lineas q ocupo la que esta con comentario es la que genera el RSACryptoServiceProvider con la llave del certificado (pero con 1024)

            'Dim rsa As RSACryptoServiceProvider = _Certificado.PrivateKey
            Dim rsa As New RSACryptoServiceProvider(512)
    

    como puedo generar la firma pero con 512???
    gracias de antemano.

    Quisiera arreglar el mundo… pero no me dan el codigo fuente!

  27. Rolivar Peraza dice:

    Excelente información, solo por favor, te lo ruego, cambia tu combinación de colores porque es cansadísimo leer con fondo negro.

  28. Erika dice:

    Hola. Este sito me ayudo mucho.
    Pero tengo que realizar los siguiente.
    Localmente me funciona. estoy con vb.net.
    pero ahora necesito que el usuario desde una aplicacion web me firme el documento pdf.
    No he logrado realziar esto.
    Mepodrian ayudar por favor.
    Gracias.

    • Hola, Erika.
      El problema con esto que querés hacer es que en el servidor no tenés la clave privada de los certificados del usuario, deberías pensar en hacerlo de alguna otra manera como una aplicación de escritorio o un flash o Silverlight donde el usuario debería elegir su certificado desde un archivo para poder utilizarlo.
      Saludos.

  29. giancarlo dice:

    Amigo tendras un ejemplo de como Firmar documentos pero con formato CAdES… o conoces algun LINK?

  30. He encontrado este post el día de hoy , mientras que en la oficina de gran utilidad envió el enlace a mí mismo y lo más probable es marcar https://www.programandoamedianoche.com cuando hago lo que respecta mejor hogar

  31. Hernan dice:

    Excelente Articulo! Felicidades

    yo tengo la misma duda que «jomarmen» pues quiero hacer mis pruebas y no me permite hacer el decifrado solo obtengo la excepcion

    ASN1 bad tag value met.

    Agradeceria su apoyo Saludos!

    • majo2013 dice:

      Hola! Gracias por el artículo! me sirvió muchísimo para enteder cómo funciona firma digital.
      Estoy implementando una aplicación en el cliente para firmar y varios años después me encuentro con el mismo problema que Hernan

                  byte[] bytDocFirmado = GetDocument(fullPathArchivoFirmado); //obtener el archivo firmado en bytes
                  SignedCms objDatos = new SignedCms();
      
                  //Deserializo los bytes PKCS#7
                  objDatos.Decode(bytDocFirmado);   // -->>> "Se ha encontrado un valor de etiqueta ASN1 incorrecto"
      

      El mismo documento lo abro con Acrobat, y el documento verifica.

      Para obtener el archivo en bytes utilizo la siguiente información:

              private Byte[] GetDocument(string strDocPath)
              {
                  FileStream objfilestream = new FileStream(strDocPath, FileMode.Open, FileAccess.Read);
                  int len = (int)objfilestream.Length;
                  Byte[] documentcontents = new Byte[len];
                  objfilestream.Read(documentcontents, 0, len);
                  objfilestream.Close();
      
                  return documentcontents;
              }
      

      Encontraron alguna solución?

      Gracias!

    • majo dice:

      Hola! Gracias por el artículo! me sirvió muchísimo para enteder cómo funciona firma digital.
      Estoy implementando una aplicación en el cliente para firmar y varios años después me encuentro con el mismo problema que Hernan

      byte[] bytDocFirmado = GetDocument(fullPathArchivoFirmado); //obtener el archivo firmado en bytes
      SignedCms objDatos = new SignedCms();
      //Deserializo los bytes PKCS#7
      objDatos.Decode(bytDocFirmado); // –>>> “Se ha encontrado un valor de etiqueta ASN1 incorrecto”
      El mismo documento lo abro con Acrobat, y el documento verifica.
      Para obtener el archivo en bytes utilizo la siguiente información:
      private Byte[] GetDocument(string strDocPath)
      {
      FileStream objfilestream = new FileStream(strDocPath, FileMode.Open, FileAccess.Read);
      int len = (int)objfilestream.Length;
      Byte[] documentcontents = new Byte[len];
      objfilestream.Read(documentcontents, 0, len);
      objfilestream.Close();
      return documentcontents;
      }
      • Gema dice:

        Hola majo, para pasar un documento a array de bytes puedes usar:

        byte[] bytDocFirmado = File.ReadAllBytes(fullPathArchivoFirmado);

        A mí no me da el error de etiqueta ASN1 incorrecto…

        Un saludo

        • majo2013 dice:

          Hola Gema!
          Gracias por responder.

          hice la prueba de utilizar el método «File.ReadAllBytes(fullPathArchivoFirmado);» y se produce la misma excepción.

          hice la prueba de luego de obtener el arreglo de bytes, guardarlo con: File.WriteAllBytes(fullPathArchivoFirmado+»prueba»,bytDocFirmado);

          y el documento de prueba es exactamente el mismo, incluso lo abro con acrobat reader y sigue verificado. La lectura de los bytes no es el problema.

          =/

          • Gema dice:

            Por lo que veo es un documento PDF. Para validar la firma en PDFs yo uso itextsharp. Puedes ver un ejemplo de verificar firmar aquí: https://stackoverflow.com/questions/9823240/how-to-retrieve-digital-signature-information-name-date-with-itextsharp

            Un saludo

            • majo2013 dice:

              Gracias por la ayuda!..

                          StringBuilder sb = new StringBuilder();  
                          PdfReader reader = new PdfReader(fullPathArchivoFirmado);
                          AcroFields af = reader.AcroFields;    ArrayList names = af.GetSignatureNames();
                          for (int i = 0; i < names.Count; ++i)    { String name = (string)names[i]; PdfPKCS7 pk = af.VerifySignature(name); sb.AppendFormat("Signature field name: {0}\n", name);
                              sb.AppendFormat("Signature signer name: {0}\n", pk.SignName); sb.AppendFormat("Signature date: {0}\n", pk.SignDate);
                              sb.AppendFormat("Signature country: {0}\n", PdfPKCS7.GetSubjectFields(pk.SigningCertificate).GetField("C"));
                              sb.AppendFormat("Signature organization: {0}\n",PdfPKCS7.GetSubjectFields(pk.SigningCertificate).GetField("O"));
                              sb.AppendFormat("Signature unit: {0}\n", PdfPKCS7.GetSubjectFields(pk.SigningCertificate).GetField("OU")); }

              Te cuento las pruebas con el código que me pasaste que hice:
              1ero) traté de verificar un pdf firmado con Acrobat Reader DC: Da la excepción Org.BouncyCastle.SecurityUtilityException: {"Signer 2.16.840.1.101.3.4.2.1WITH1.2.840.113549.1.1.11 not recognised."}
              2do) traté de verificar un pdf firmado con itextsharp: y ya no dio la excepción. Este caso si anduvo.
              Me preocupa un poco, se supone que puedo recibir documentos firmados con cualquier herramienta. Alguna idea de la excepción que describí?

              • majo2013 dice:

                bueno bueno.. Leyendo «https://stackoverflow.com/questions/22407897/pdf-digital-signature-in-java-and-signature-verification-in-c-sharp-itext» en uno de los comentarios recomienda actualizar la librería de itextSharp. Yo tenía la 4.1.2, así que actualicé a la versión 5.5.11 (https://www.nuget.org/packages/iTextSharp/5.5.11).

                Y la verificación funcionó tanto para documentos firmados con itextsharp como el firmado por acrobat reader DC.
                tuve que modificar la línea de código: «ArrayList names = af.GetSignatureNames();» por «List names = af.GetSignatureNames();» y «PdfPKCS7.GetSubjectFields(pk.SigningCertificate).GetField(«C»)» aun tengo que ver como funciona en la nueva versión.

                Pero es un paso enorme!

                Gracias Chicos por la colaboración.

          • Luis Alvarez dice:

            debes tener en cuenta que para leer debes usar la misma libreria que usaste para escribir, ese puede ser el motivo del error.

  32. Luis dice:

    Gustavo: bajé el ejemplo hecho en c# y funciona perfecto!!

    Mi pregunta es es posible hacer lo mismo con java. Tengo que desarrollar la firma de pdf utilizando si o si java.
    He estado probando por semanas y no consigo dar con el resultado.
    Tengo un certificado emitido por la onti, el cual instalé en mi pc.
    Necesitaria firmar de la misma forma que lo estas haciendo en el ejemplo, es decir leer el repositorio de certificados, tomar uno y firmarlo introduciendo el pin o password de la clave privada.
    Hasta ahora he conseguido leer el repositorio y tomar el ceritificado sin problemas, pero no he logrado o no comprendo como hacer para firmar con la clave privada solicitando el pin o password de la misma.
    Podés ayudarme o darme una pista de adonde investigar?
    Muchas gracias!!

    Luis.

  33. senavirtual dice:

    buenas acabo de enterarme de tu pagina web y la verdad es que me parece excelente no sabia de mas personas interesadas en estos temas, aqui tienes un nuevo lector que seguira visitandote mensualmente.

  34. Jose Tejada dice:

    Hola Gustavo,

    muchas gracias por tu artículo.

    En estos momentos estamos valorando un proyecto en el que el cliente desea que los usuarios puedan firmar algunos documentos digitalmente.

    Esta aplicación será web y estará alojada en un servidor en que el se accederá desde algunos terminales, y desde estos, se debe poder firmar digitalmente.

    El problema es que no acabo de entender aún como funciona esto, te explico:

    En estos terminales habrá un usuario que atenderá a clientes, realizará las acciones oportunas en la aplicación web, y llegará el momento que se abrirá un documento PDF que necesita ser firmado digitalmente por el cliente. Esto lo hará el usuario en el terminal pero a través del DNI del cliente.

    Ahora las preguntas:

    Cada vez que se lea un DNI, se generará un certificado en ese terminal ? Si es así y debido a que existirán el de todos los clientes que hayan pasado, se deberá siempre seleccionar en la aplicación el que sea en ese momento no ?

    Espero que hayas entendido el planteamiento para que me puedas ayudar un poco en como funciona esto según las necesidades de este proyecto.

    Muchisimas gracias por tu artículo y tu ayuda.

  35. HENRY dice:

    GUSTAVO SOY UN ESTUDIANTE DE ULTIMO SEMESTRE DE LA CARRERA DE ING DE SISTEMAS, PARA MI PROYECTO DE GRADO ESTABA PENSANDO HACER UN SISTEMA WEB Q ADMINISTRE LA CORRESPONDENCIA INTERNA DE MI UNIVERSIDAD APLICANDO FIRMAS DIGITALES, LA VERDAD TU APORTE ME ABRIO UN POCO LA MENTE Y ME DIO MASOMENOS UNA IDEA DE COMO HACERLO, QUISIERA SABER SI PODRIAMOS COMUNICARNOS VIA MAIL PARA QUE PUEDA COMENTARTE DE LO Q ESTOY PLANTEANDO Y TAL VEZ ME PUEDAS DAR ALGUNOS CONSEJOS

    MUCHAS GRACIAS DE ANTEMANO POR TU TIEMPO

  36. Ubaldo dice:

    Hola Gustavo,
    Tengo un problema de encriptacion y buscando por la web me encontre tus grandes aportes sobre el manejo de certificados y quiero ver si me puedes ayudar o iluminarme, mi problema consiste en que mi solucion en el servidor funciona de maravilla, pero para guardar documentos en otra ruta utilice el impersonate en el web config, al generar este cambio ahora tengo problemas de que las llaves no existen, busque informacion y ya le di permisos a los certificados mediante la consola de mmc, pero aun asi me sigue mandando el error, espero puedas ayudarme o indicarme como puedo resolver mi problema, saludos.

    An unhandled exception occurred and the process was terminated.
    Application ID: /LM/W3SVC/2/ROOT
    Process ID: 6292
    Exception: System.Security.Cryptography.CryptographicException

    Message: Keyset does not exist

    StackTrace: at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
    at System.Security.Cryptography.SafeKeyHandle._FreeHKey(IntPtr pKeyCtx)
    at System.Security.Cryptography.SafeKeyHandle.ReleaseHandle()
    at System.Runtime.InteropServices.SafeHandle.InternalFinalize()
    at System.Runtime.InteropServices.SafeHandle.Dispose(Boolean disposing)

    • Hola, Ubaldo.
      ¿Es raro lo que te pasa, probaste dándole permisos también al usuario del IIS?

      • Ubaldo dice:

        Hola Gustavo,
        De hecho si le di los permisos y con esos trabaja de maravilla, la cuestion es que necesito guardar los documentos en un contenedor que no pertenece a ese servidor y cuando utilizo el impersonate para poner el usuario y que pueda guardar, es cuando me da los problemas y ya agregue mediante la consola del mmc el usuario de IIS, el Network Service y hasta el uduario que utilizo en el impersonate, pero me sigue dando el error, vi en algunos blog algo que se realiza mediante la clase cacls.exe, segun para al usuario darle permisos de escritura y ejecucion, pero hasta el momento sigo intentando

  37. Leonel dice:

    Hola Gustavo,
    Soy de Ecuador y estoy tratando de firma un documento XML, tengo un avance del tema pero no consigo encontrar algunos valores en el certificado ejemplo «QualifyingProperties», o «Signedproperties». Por favor ayudame con un empujon para encontrar una solucion.

    muchas gracias.
    Te felicito por tu Blog…

  38. SilviaR dice:

    Hola Gustavo buena tarde,
    tu página me ha servido mucho para aprender y comprender como funciona la criptografía, son excelentes aportes. Pero tengo una duda, si bien se puede cifrar con la llave publica y descrifrar con la privada.. existe el proceso contrario? cifrar con privada y descifrar con publica?.. Bouncy Castle tiene los metodos para hacerlo? espero puedas apoyarme a resolver esta duda.. es que esta libreria es enorme. Saludos 🙂

    • Hola, Silvia.
      La verdad que no conozco una forma de «invertir» las claves, pero igual no entiendo para que podrías necesitar algo así, ya que en ese caso lo que cifres de forma «privada» lo podría ver todo el mundo, pudiéndolo descifrar con la clave pública.
      Saludos.

  39. te felicito y muchas gracias por la potencia de los datos !!!

  40. José Vindas dice:

    Buenas, tengo una duda, para hacer el desarrollo funciono a la perfección, pero tengo el problema que cuando lo coloco en mi servidor, no lee los certificados del cliente! tenes algún consejo para eso???
    Para leer los certificados del cliente utilizo StoreLocation.CurrentUser.

    Gracias de antemano.

    • José, creo que el problema es que, cuando ejecutás tu aplicación en el IIS, el usuario que utiliza es el IUSR_XXX (XXX es el nombre de la máquina), por eso es que no encuentra el certificado.
      Te recomiendo instalar el certificado a nivel de máquina, si es que esto no te trae ningún problema de seguridad (como que otro usuario en ese servidor pueda leer el certificado).
      Suerte!

      • José Vindas dice:

        Gustavo antes que nada gracias por responder.

        No se, si tenes idea si esto se puede usar para leer los certificados instalados en la computadora de cada usuario.
        Te explico rápido lo que estoy haciendo.
        Un usuario entra al sistema(ya teniendo su firma instalada en su propia pc), yo quiero leer su firma y entonces una vez que la leo proceso que digite su llave privada para poder validarla y ya con eso avanzo en mi proceso.

        No se si tenes algún tipo de información o ayuda que me podas brindar, de antemano muchas gracias!!

        • José, en realidad el usuario ya ingresó una contraseña al logearse, ya que sino no podría usar los certificados de su repositorio.
          De todas formas, la clave privada no es un texto que pueda usar, es un array de bytes.
          Tal vez lo que podrías hacer es que el usuario ingrese un «password», lo firmes con su certificado y luego verifiqués, a través de la aplicación, si su contraseña es la correcta y si la firma digital corresponde al certificado que tenés guardado con suyo.
          Suerte!

  41. Andres Bustos dice:

    Hola, estoy tratando de consumir un servicio que solicita autenticacion SSL TWO WAY,ya tengo mi certificado en el keystore y lo puedo levantar desde el codigo, como deberia hacer para enviar mi certificado autenticarme en el webservice para poder consumirlo. estoy trabajando con vb2008 en C#.
    gracias

  42. Indalecio Trujillo dice:

    Realice el siguiente ejemplo http://www.codeproject.com/Articles/36683/9-simple-steps-to-enable-X-509-certificates-on-WCF?msg=4691986#xx4691986xx

    Todo funciona de manera correcta en ambiente de desarrollo, pero al mover el certificado al servidor de produccion veo el siguiente error.

    El conjunto de claves no existe

    Descripción: Excepción no controlada al ejecutar la solicitud Web actual. Revise el seguimiento de la pila para obtener más información acerca del error y dónde se originó en el código.

    «Detalles de la excepción: System.Security.Cryptography.CryptographicException: El conjunto de claves no existe»

    En el servidor he puesto el certificado en el almacen de la computadora pero aun asi no funciona.

    Al momento de exportar el certificado he exportado tanto clave publica como clave privada.

  43. Alexfr8 dice:

    Buenas, solo una pequeña reseña para los que utilicen DNI electronico en España.
    El que quiera firmar usando DNIe Debe usar el codigo con el modo silent en false.

    Concretamente esta linea.
    objSignedData.ComputeSignature(objSigner,false);

    En otro caso no conseguirar liberar el certificado y no pedirá el PIN de acceso a la tarjeta.

    Saludos.

  44. Carlos Palma dice:

    Una consulta, en que tipo de aplicacion esta realizado estos ejemplos?? ASPX?? desktop?? consola??

  45. shespi1088 dice:

    Gustaba aun puedes ayudar

  46. Juan dice:

    Buen día Gustavo. Excelente aporte, me ha ayudado mucho y prácticamnete todo el código me ha funcionado. Estoy probándolo en C# Visual Studio 2005 y solo tengo un error en la línea de desencriptación:

    //Desencriptamos los datos
    objEncryptedData1.Decrypt();

    Ahí se genera un error:
    Source: System.Security
    Message: «Bad Length.\r\n»

    La asignación de los datos ecriptados la tengo así:
    byte[] bytDatos = bytResult; //Datos encriptados

    El certificado está en un eToken PRO72k.

    Alguna idea o guía del origen de este error?

  47. Carlos dice:

    Hola Gustavo, realmente muy buenos tus artículos, quisiera hacerte un par de consultas. Yo ya he hecho una aplicación windows para firmar documentos PDF. Las clases de .Net lo resuelven muy bien y se puede usar la clave privada sin problemas.
    El inconveniente lo tengo a la hora de hacer una aplicación web, donde el documento se aloja en servidor y el certificado (con su clave privada) está en la PC del cliente.
    Y la otra consulta es, cómo hacer más amplia la aplicación web para que el cliente pueda firmar desde cualquier lugar, llevando consigo, por ejemplo, un eToken.
    Te agradeceré me puedas encaminar un poco con estos dos temas.
    Saludos, Carlos

    • Hola, Carlos.
      Te cuento que nosotros tuvimos el mismo problema para firmar un PDF con el certificado del usuario en una aplicación web.
      Para resolverlo, creo yo, tenés dos posibilidades:
      1- Pedirle al usuario que «seleccione» un archivo con su certificado, enviarlo al servidor y firmar el documento ahí. Obviamente esta opción no es viable debido a que estás llevando el certificado al servidor y podrías firmar 1000 cosas más, el usuario no creo que quiera correr el riesgo.
      2- Hacer una aplicación o applet que corra en el cliente y firmar ahí el documento (esta es la que hicimos nosotros). En nuestro caso desarrollamos una aplicación con Silverlight que obtenía el header del PDF a firmar, lo recibía en el cliente, lo firmaba con el certificado de éste, lo enviaba al servidor, y ahí lo ensamblábamos con el resto del documento. Esto no fue una tarea sencilla, pero pudimos hacer que funcione.
      Espero te sirvan las ideas.
      Suerte!

  48. LCisterna dice:

    Hola Gustavo.
    Muchas Gracias por toda la información que has entregado.

    Estoy terminando un proyecto de firma Electronica en docs PDF (aplicación web) donde los certificados están instalados en el servidor (no pudimos hacer que funcionara con los certificados instalados en el cliente).

    Pero cuando la quisimos poner en el servidor de explotación, no firmaba y enviaba el siguiente error: «No se encuentra el certificado y la clave privada para el descifrado».

    Por lo que he leído podría ser problemas de privilegios sobre los certificados. Trate de darle permisos en el servidor con winhttpcertcfg -g -c LOCAL_MACHINE\TrustedPeople -s «certificado» -a IIS_IUSRS, pero ni siquiera esta la carpeta del winhttpcertcfg.

    Mis preguntas son: será efectivamente problemas de privilegio y si es asi como puedo darle permisos en el servidor.

    Muchas Gracias.

    • Disculpas por la demora en responder.
      ¿Pudiste resolver el problema? Si no es así: ¿probaste de hacer que el application pool de tu aplicación web corra con un usuario que tenga permisos para ver los certificados en el servidor? (tal vez podrías usar el mismo usuario con el que instalaste los certificados) Asi podemos ver si es un tema de permisos.
      Suerte!

  49. Giovanni Rugerio dice:

    [+] Gracias por el contenido me ha sido de mucha utilidad.

  50. Gustavo solo una duda q tipo de licencia tiene esta libreria?? para uso comercial

  51. William Gomez dice:

    Hola a todos

    estoy utilizando visual studio community 2013 y me aparece que en la linea donde uso

    using System.Security.Cryptography.Pkcs;

    que el tipo o nombre del espacio de nombre «Pkcs» no existe en el espacio de nombres actual System.Security.Cryptography, sera por el tipo de visual studio que utilizo?

    Gracias por su ayuda

  52. Karla Sanz dice:

    Hola Gustavo,

    Pregunta: La validación de firma, el encriptado y desencriptado funciona para un documento firmado con la FIEL?

    Saludos

  53. Teresa dice:

    Buenos días,

    Magnifico post!!
    He creado los certificados con makecert en lugar de con xml y solo tengo un problemilla cuando intento desencriptar me aparece el siguiente error:

    «Excepción no controlada del tipo ‘System.Security.Cryptography.CryptographicException’ en System.Security.dll
    Información adicional: Clave incorrecta.»

    ¿sabes que puedo hacer para solucionarlo?
    Gracias.
    Saludos.

  54. veronica lopez dice:

    Que tal quisiera realizar una consulta sencilla. En la parte de validar ceertificados como se pone el certificado ??

    X509Certificate2 objCert = … //Acá tenemos que poner el certificado

    desde visual???? Gracias de antemano

    • Hola Verónica.
      Depende de dónde tengas tu certificado. Por ejemplo, si lo tenés como parte de otro archivo deberías ver cómo tomarlo de ahí, o si lo en un archivo lo podés leer de la siguiente manera:

      X509Certificate2 objCert = new X509Certificate2("archivo");

      Y si lo tenés con contraseña podés abrirlo así:

      X509Certificate2 objCert = new X509Certificate2("archivo", "contraseña");

      Saludos.

      • jose carlos dice:

        Estimado Gustavo. una pregunta : ¿como puedo hacer para que en lugar de X509Certificate2 objCert = new X509Certificate2(«archivo», «contraseña»);
        pueda escojer un certificado instalado previamente en el almacen de certificados

        • Hola José.
          Para mostrar el diálogo del sistema operativo que te permite seleccionar uno o varios certificados podés utilizar la clase X509Certificate2UI.
          Sería reemplazar la linea

          X509Certificate2 objCert = new X509Certificate2(“archivo”, “contraseña”);

          por esto:

          X509Store objStore = new X509Store("MY", StoreLocation.CurrentUser);
          objStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
          
          var objColection = (X509Certificate2Collection)objStore.Certificates;
          var objFColection = (X509Certificate2Collection)objColection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
          var objSelected = X509Certificate2UI.SelectFromCollection(objFColection, "Selección de certificados", "Por favor seleccione un certificado de la lista", X509SelectionFlag.SingleSelection);
          X509Certificate2 objCert = null;
          if (objSelected.Count > 0)
              objCert = objSelected[0];

          Si necesitás que el usuario seleccione más de un certificado, reemplazá la definición de objSelected por esto:

          var objSelected = X509Certificate2UI.SelectFromCollection(objFColection, "Selección de certificados", "Por favor seleccione los certificados de la lista", X509SelectionFlag.MultiSelection);

          Espero te sirva.
          ¡Suerte!

  55. Olga dice:

    Hola,
    Tienes algún ejemplo de esto mismo, pero firmando un fichero XML

  56. CHIQUY dice:

    HOLA GUSTAVO: LOGRA HACER EL CERTIFICADO Y PONERLO EN UN PDF EXISTENTE… EL TEMA ES QUE NO SÉ COMO PUEDO HACER PARA ACOMODARLO (POSICIONARLO) EN EL PDF O CAMBIAR EL TAMANO DE LA FIRMA… TENES IDEA COMO SE HACE GRACIAS

  57. Felipe dice:

    Sorry la pregunta pero soy muy nuevo en los certificados y quiero saber que cuando dice » //Acá tenemos que poner el certificado» el objCert.Subject
    que me deja como este nombre del certificado (CN=IndexaSignCert) y al momento de hacer X509Certificate2 objCert = … //Acá tenemos que poner el certificado
    me manda error.
    Gracias

  58. Luis Alvarez dice:

    Hola gustavo muchas gracias por compartir esta informacion me fue muy util, ahora tengo una pregunta si quisiera consultar los certificados agregados al navegador desde una aplicacion mvc, sabes como seria?

    y otra vez muchas gracias por el post.

    • Hola Luis.
      Podrías consultar el certificado utilizado en el navegador utilizando esta linea:

      var cert = System.Web.HttpContext.Current.Request.ClientCertificate

      Suerte!

      • Luis Alvarez dice:

        ya eso lo probe pero llega vacio, mira te explico la necesidad para que tengas una idea, lo que quiero hacer es que el usuario elija de las firmas que tiene agregadas al navegador con cual desea firmar el documento.

        con el archivo fisico ya lo esta haciendo pero ahora en este punto estoy estancado he estado leyendo mucho pero nada no encuentro solucion

        • El problema es que no vas a poder hacerlo como estás pensando, porque con el navegador podés hacer que el usuario elija un certificado para autenticarse contra el IIS, pero no vas a poder leer la clave privada de éste para firmar un documento.
          La única manera de hacerlo es utilizando algo externo, como un applet, plugin del browser, ejecutable o algo parecido. Hace unos años habíamos hecho un componente en Silverlight que leía el certificado y firmaba un PDF del lado del cliente, deberías crear algo así (obviamente no con Silverlight, que ya es obsoleto).
          Suerte!

  59. Jorge Catalá dice:

    Excelente Post, gracias por compartir tu conocimiento.

  60. Hans Cruz Buchelli dice:

    Hola Gustavo, tengo una duda, como se podria firmar con contraseña pero una imagen o otro documento que no sea PDF.

    Gracias

  61. Buenos días Gustavo, saludos a todos con estas líneas; leyendo tu código he podido firmar un documento PDF incluyendo la firma en el mismo haciéndola visible o invisible. El tema que tengo es el siguiente: La necesidad de firmar varios archivos pdf y/o xml, el certificado lo selecciono de mi repositorio, pero tiene una clave privada, al hacerlo con una sólo documento, no hay problema que se pida la clave, pero al ser varios hay alguna forma de almacenar esa clave ingresada una única vez? e ir firmando uno a uno los documentos. Gracias de antemano por la atención. Tus artículos con excelentes y de mucha ayuda.

    • Buenas tardes!
      La contraseña utilizada para abrir un certificado se puede especificar en el constructor de la clase X509Certificate2, por lo cual, podrías crear un diálogo para consultarle al usuario la contraseña, y guardarla en una variable para usarla en cada firma que debas hacer.
      Espero te sirva.
      Saludos.

  62. Marco dice:

    Hola, buenos días.
    Estoy intentado coger el certificado digital que hay instalado en el navegador pero no lo consigo, solo consigo mostrar el de windows,
    En Firefox no tengo instaldo ningún certificado, sin embargo me muestra el de windows.
    Me podéis ayudar.

    Gracias

    • Luis Alvarez dice:

      yo intentaba hacer lo mismo, y no se puede esta fue la respuesta por si te interesa, y arriba esta toda la conversacion, como recomendacion lee los comentarios antes de preguntar para que no se repitan las preguntas.
      «El problema es que no vas a poder hacerlo como estás pensando, porque con el navegador podés hacer que el usuario elija un certificado para autenticarse contra el IIS, pero no vas a poder leer la clave privada de éste para firmar un documento.
      La única manera de hacerlo es utilizando algo externo, como un applet, plugin del browser, ejecutable o algo parecido. Hace unos años habíamos hecho un componente en Silverlight que leía el certificado y firmaba un PDF del lado del cliente, deberías crear algo así (obviamente no con Silverlight, que ya es obsoleto).
      Suerte!»

      • Ya lo he leído, pero yo no quiero leer la clave privada, solo quiero acceder al certificado para autentificarse.
        Desde mi equipo accedo a los certificados de Windows sin problema, pero cuando lo ejecuta el servidor no puedo acceder.

        Gracias y perdón si me repito

        • Luis Alvarez dice:

          listo, igual creo que si lees desde el cliente lo que puedes consultar son los certificados ssl, mas no los certificados digitales del cliente que tiene en el navegador, hay un proyecto de la w3c para esta necesidad pero aun se esta a la espera de este.

          Si encuentras algun modo de hacer que funcione, no dudes en compartirlo

  63. Hans Cruz Buchelli dice:

    Hola como se podria firmar en formato CAdES?

  64. iris dice:

    Buenas tardes, para mandar el texto encriptado a un pdf, como podría hacerle. Quiero que el sello digital se muestre en un documento pero generado desde asp

  65. Felix dice:

    Hola Gustavo, al momento de correr el código tengo el siguiente problema, al llegar a la línea objSignedData.ComputeSignature(objSigner), emite el error de: la clave no existe. qué podría ser?, considerando que tengo un archivo .crt y un .key ambos en una carpeta y el archivo a firmar en otra.

  66. Jose Willians Navarro Yovera dice:

    Buenas tardes primero agradecerles por su aporte de conocimientos y ala vez consultarles si tienes procedimientos para visual basic .net, estaria muy agradecido

  67. Adolfo Guzmán dice:

    Como puedo reconocer un ejecutable con firma digital y comprobar que este no ha sido violado. Por favor si me pudieran orientar, donde puedo conseguir información en VB net

  68. Raul Monge dice:

    Buenas tardes hay alguna manera de contactarme con usted? Gustavo Cantero.

  69. Hugo dice:

    Hola Gustavo, buenas tardes. Antes que nada excelente artículo.
    Una duda, al igual que otro compañero, me sale el error en objSignedData.ComputeSignature(objSigner) error de: la clave no existe. Y revisando tu respuesta anterior, mi archivo .cer no tiene la llave privada, la tengo en otro archivo a parte con extensión .key. (con su respectivo password). Mi pregunta es, como puedo anexarlo a mi archivo .cer? He visto que tal vez pueda hacer una copia con certificate= certificate.CopyWithPrivateKey(key); pero desconozco como pasarle mi .key a dicho método. Te lo agradezco mucho!

  70. Ana dice:

    Buenas tardes, Muy interesante el artículo.
    Por favor, quisiera tu apoyo con un problema que no he podido resolver hasta ahora,
    Tengo un aplicación en VB.net que realiza la firma de archivos xml con el certificado digital, todo ha estado bien por mas de 2 años, pero ahora se ha puesto lento al momento de firmar el xml, justo la demora es en «objChain.Build(objCert)», antes no demoraba y ahora demora mas de 10 segundos en este punto.
    No se a que se debe. Espero puedas ayudarme, gracias.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.