Agregar firma digital o firmar digitalmente PDF en C#

Aspose.PDF for .NET soporta la funcionalidad de firmar digitalmente archivos PDF usando la clase SignatureField. También puede certificar un archivo PDF con un certificado PKCS12. Algo similar a Adding Signatures and Security in Adobe Acrobat.

Al firmar un documento PDF usando una firma, básicamente confirma su contenido “tal cual”. En consecuencia, cualquier otro cambio realizado después invalida la firma y, por lo tanto, sabrá si el documento fue alterado. Mientras que, certificar un documento primero le permite especificar los cambios que un usuario puede hacer al documento sin invalidar la certificación.

En otras palabras, el documento seguiría considerándose íntegro y el destinatario aún podría confiar en él. Para más detalles, visite Certifying and signing a PDF. En general, certificar un documento puede compararse con la firma de código de un ejecutable .NET.

El siguiente fragmento de código también funciona con la biblioteca Aspose.PDF.Drawing.

Funcionalidades de firma de Aspose.PDF for .NET

Podemos usar las siguientes clases y métodos para la firma de PDF

Para crear una firma digital basada en certificados PKCS12 (extensiones de archivo .p12, pfx), debe crear una instancia de la clase PdfFileSignature, pasando el objeto documento a ella. A continuación, debe especificar el método de firma digital deseado creando un objeto de una de las clases:

  • PKCS1.
  • PKCS7.
  • PKCS7Detached.

Puede establecer el algoritmo de resumen solo para PKCS7Detached. Para PKCS1 y PKCS7, el algoritmo de resumen siempre está configurado en SHA-1.

Luego, necesita usar el objeto de algoritmo de firma recibido en el método PdfFileSignature.Sign(). La firma digital se establecerá para el documento después de guardarlo.

Firmar PDF con firmas digitales

El ejemplo a continuación crea una firma PKCS7 no separada con el algoritmo de resumen SHA-1.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignDocument()
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();
    
    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "DigitallySign.pdf"))
    {
        // Instantiate PdfFileSignature object
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            // Create PKCS#7 object for sign
            var pkcs = new Aspose.Pdf.Forms.PKCS7(dataDir + "rsa_cert.pfx", "12345");
            // Sign PDF file
            signature.Sign(1, true, new System.Drawing.Rectangle(300, 100, 400, 200), pkcs);
            // Save PDF document
            signature.Save(dataDir + "DigitallySign_out.pdf");
        }
    }
}

El ejemplo a continuación crea una firma separada en formato PKCS7Detached con el algoritmo de resumen SHA-256. El algoritmo de clave depende de la clave del certificado. Se admiten DSA, RSA, ECDSA.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignDocument(string pfxFilePath, string password)
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "DigitallySign.pdf"))
    {
        // Instantiate PdfFileSignature object using the opened document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            // Create PKCS#7 detached object for sign
            var pkcs = new Aspose.Pdf.Forms.PKCS7Detached(pfxFilePath, password, Aspose.Pdf.DigestHashAlgorithm.Sha256);
            // Sign PDF file
            signature.Sign(1, true, new System.Drawing.Rectangle(300, 100, 400, 200),pkcs);
            // Save PDF document
            signature.Save(dataDir + "DigitallySign_out.pdf");
        }
    }
}

Puede verificar firmas usando el método PdfFileSignature.VerifySignature(). Anteriormente, se usaba el método GetSignNames() para obtener los nombres de las firmas. A partir de la versión 25.02, debe usarse el método GetSignatureNames(), que devuelve una lista de SignatureName. SignatureName evita colisiones al verificar firmas con los mismos nombres. También deben usarse los métodos que aceptan el tipo SignatureName en lugar de un nombre de firma como cadena.

Nota, el método PdfFileSignature.VerifySigned() está obsoleto.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void Verify()
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "signed_rsa.pdf"))
    {
        // Create an instance of PdfFileSignature for working with signatures in the document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {         
            // Get a list of signature names in the document
            var sigNames = signature.GetSignatureNames();

            // Loop through all signature names to verify each one
            foreach (var sigName in sigNames)
            {
                // Verify that the signature with the given name is valid
                if (!signature.VerifySignature(sigName))
                {
                    throw new Exception("Not verified");
                }
            }
        }
    }
}

Verificar firmas digitales con un certificado de clave pública externo

Puede usar un certificado externo que contenga la clave pública que corresponde a la clave privada utilizada para firmar el documento y verificar la firma.

Puede extraer un certificado de la firma y usarlo para verificar la firma.

Verificar firmas digitales con una comprobación de certificado

Al verificar una firma digital, puede comprobar la revocación del certificado de firma.

Desafortunadamente, Aspose.PDF no puede verificar la autenticidad de los certificados raíz o intermedios en la cadena de certificados.
Por lo tanto, solo se comprueba el estado de revocación del certificado de firma usando CRL y OCSP.

Para configurar la validación de certificados, puede usar el parámetro ValidationOptions.

La opción ValidationMode ofrece tres modos de validación:

  • None – el certificado no se verifica.
  • Strict – la revocación del certificado afecta el resultado del método Verify .
  • OnlyCheck – permite comprobar el certificado sin afectar el resultado de la verificación de la firma.

ValidationMethod especifica el método usado para comprobar el certificado:

  • Auto – selección automática del método. Se prefiere OCSP. El estado de revocación se determina por el método que realiza la verificación con éxito.
  • Ocsp – la revocación se verifica usando OCSP.
  • Crl – la revocación se verifica usando CRL.
  • All – se usan ambos métodos para verificar el certificado. Para que la validación pase, ambos métodos deben confirmar que el certificado no está revocado.

La opción CheckCertificateChain habilita la comprobación de la presencia de una cadena de certificados en la firma.
Si no se encuentra la cadena de certificados, el resultado de la verificación del certificado será Undefined.

El resultado de la verificación puede obtenerse a través de un parámetro de salida de tipo ValidationResult.
Los posibles estados son: Valid, Invalid y Undefined.
Undefined típicamente significa que el certificado no pudo validarse o que falta la cadena de certificados.

Configurar tanto CheckCertificateChain como ValidationMode = ValidationMode.Strict corresponde al comportamiento de Adobe Acrobat.
Si Adobe Acrobat no puede encontrar la cadena de certificados, no verifica el estado de revocación y la firma se considera inválida.

Añadir marca de tiempo a la firma digital

Cómo firmar digitalmente un PDF con estampillado de tiempo

Aspose.PDF for .NET admite la firma digital del PDF con un servidor de estampillado de tiempo o servicio web.

Para cumplir con este requisito, se ha añadido la clase TimestampSettings al espacio de nombres Aspose.PDF. Por favor, echa un vistazo al siguiente fragmento de código que obtiene el estampillado de tiempo y lo agrega al documento PDF:

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignWithTimeStampServer()
{    
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "SimpleResume.pdf"))
    {
        // Create an instance of PdfFileSignature for working with signatures in the document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            // Create TimestampSettings settings
            var timestampSettings = new Aspose.Pdf.TimestampSettings("https://freetsa.org/tsr", string.Empty); // User/Password can be omitted
            var pkcs = new Aspose.Pdf.Forms.PKCS7(timestampSettings);       
           
            System.Drawing.Rectangle rect = new System.Drawing.Rectangle(100, 100, 200, 100);
            // Create any of the three signature types
            signature.Sign(1, "Signature Reason", "Contact", "Location", true, rect, pkcs);
            // Save PDF document
            signature.Save(dataDir + "DigitallySignWithTimeStamp_out.pdf");
        }
    }
}

Puede incrustar una marca de tiempo en la firma digital que corresponde al certificado de su elección. También puede firmar la marca de tiempo como una firma independiente.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignWithEmbeddedTimestamp(string pfxFilePath, string password)
{    
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "SimpleResume.pdf"))
    {
        // Create an instance of PdfFileSignature for working with signatures in the document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            var pkcs = new Aspose.Pdf.Forms.PKCS7(pfxFilePath, password);
            // Create TimestampSettings settings
            var timestampSettings = new Aspose.Pdf.TimestampSettings("https://freetsa.org/tsr", string.Empty); // User/Password can be omitted
            pkcs.TimestampSettings = timestampSettings;
            System.Drawing.Rectangle rect = new System.Drawing.Rectangle(100, 100, 200, 100);
            // Create any of the three signature types
            signature.Sign(1, "Signature Reason", "Contact", "Location", true, rect, pkcs);
            // Save PDF document
            signature.Save(dataDir + "DigitallySignWithTimeStamp_out.pdf");
        }
    }
}

Firmar PDF con X509Certificate2 en formato base64

Este código firma el PDF usando un certificado externo, posiblemente interactuando con un servidor para recuperar el hash firmado e incrustar la firma en el documento PDF.

Pasos para firmar PDF:

  1. Crear una instancia de PdfFileSignature.
  2. Definir el hash de firma personalizado.
  3. Cargar el certificado.
  4. Firmar los datos.
  5. Vincular y firmar el PDF.
  6. Guardar el PDF firmado.
// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignWithBase64Certificate(string pfxFilePath, string password)
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    var base64Str = "Certificate in base64 format";
    using (var pdfSign = new Aspose.Pdf.Facades.PdfFileSignature())
    {
        var sign = new Aspose.Pdf.Forms.ExternalSignature(base64Str, false);// without Private Key
        sign.ShowProperties = false;
        // Create a delegate to external sign
        Aspose.Pdf.Forms.SignHash customSignHash = delegate (byte[] signableHash, Aspose.Pdf.DigestHashAlgorithm digestHashAlgorithm)
        {
            // Simulated Server Part (This will probably just be sending data and receiving a response)
            var signerCert = new X509Certificate2(pfxFilePath, password, X509KeyStorageFlags.Exportable);// must have Private Key
            var rsaCSP = new RSACryptoServiceProvider();
            var xmlString = signerCert.PrivateKey.ToXmlString(true);
            rsaCSP.FromXmlString(xmlString);
            byte[] signedData = rsaCSP.SignData(signableHash, CryptoConfig.MapNameToOID("SHA1"));
            return signedData;
        };
        sign.CustomSignHash = customSignHash;
        // Bind PDF document
        pdfSign.BindPdf(dataDir + "input.pdf");
        // Sign the file
        pdfSign.Sign(1, "second approval", "second_user@example.com", "Australia", false,
            new System.Drawing.Rectangle(200, 200, 200, 100),
            sign);
        // Save PDF document
        pdfSign.Save(dataDir + "SignWithBase64Certificate_out.pdf");
    }
}

Firmar un PDF con función de firma HASH

Usando una función de firma de hash personalizada, la autoridad de firma puede implementar estándares criptográficos específicos o políticas de seguridad internas que van más allá de los métodos de firma estándar, garantizando la integridad del documento. Este enfoque ayuda a verificar que el documento no ha sido alterado desde que se aplicó la firma y proporciona una firma digital legalmente vinculante con prueba verificable de la identidad del firmante usando el certificado PFX.

Este fragmento de código demuestra cómo firmar digitalmente un documento PDF usando un certificado PFX (PKCS#12) con una función de firma de hash personalizada en C#.

Veamos más de cerca el proceso de firma DPF:

  1. Definir rutas de archivos e información del certificado:
  • inputPdf: La ruta al documento PDF de entrada que necesita ser firmado.
  • inputP12: La ruta al archivo de certificado .p12 (PFX) utilizado para la firma.
  • inputPfxPassword: La contraseña del certificado PFX.
  • outputPdf: La ruta donde se guardará el PDF firmado.
  1. Proceso de firma:
  • Se crea un objeto PdfFileSignature y se vincula al PDF de entrada.
  • Se inicializa un objeto PKCS7 utilizando el certificado PFX y su contraseña. El método ‘CustomSignHash’ se asigna como la función de firma de hash personalizada.
  • Se llama al método Sign, especificando el número de página (1 en este caso), detalles de la firma (razón, cont, loc) y la posición (un rectángulo con coordenadas (0, 0, 500, 500)) para la firma.
  • El PDF firmado se guarda en la ruta de salida especificada.
  1. Firma de hash personalizada:
  • El método CustomSignHash acepta un arreglo de bytes signableHash (el hash a firmar).
  • Carga el mismo certificado PFX y recupera su clave privada.
  • La clave privada se usa para firmar el hash usando el RSACryptoServiceProvider y el algoritmo SHA-1.
  • Los datos firmados (arreglo de bytes) se devuelven para ser aplicados a la firma del PDF.

El algoritmo de resumen puede especificarse en el constructor PKCS7Detached. Se puede llamar a un servicio de terceros en el delegado CustomSignHash. El algoritmo de firma usado en CustomSignHash debe coincidir con el algoritmo de clave en el certificado pasado a PKCS7/PKCS7Detached.

El ejemplo a continuación crea una firma no separada con el algoritmo RSA y el algoritmo de resumen SHA-1. Si usa PKCS7Detached en lugar de PKCS7, puede usar ECDCA y establecer el algoritmo de resumen deseado.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignWithCertificate(string pfxFilePath, string password)
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    using (var sign = new Aspose.Pdf.Facades.PdfFileSignature())
    {   
        // Bind PDF document
        sign.BindPdf(dataDir + "input.pdf");
        // Create PKCS#7 object to sign
        var pkcs7 = new Aspose.Pdf.Forms.PKCS7(pfxFilePath, password);// You can use PKCS7Detached with digest algorithm argument
        // Set the delegate to external sign
        pkcs7.CustomSignHash = CustomSignHash;
        // Sign the file
        sign.Sign(1, "reason", "cont", "loc", false, new System.Drawing.Rectangle(0, 0, 500, 500), pkcs7);
        // Save PDF document
        sign.Save(dataDir + "SignWithCertificate_out.pdf");
    }

    // Custom hash signing function to generate a digital signature
    byte[] CustomSignHash(byte[] signableHash, Aspose.Pdf.DigestHashAlgorithm digestHashAlgorithm)
    {
        var inputP12 = "111.p12";
        var inputPfxPassword = "123456";
        X509Certificate2 signerCert = new X509Certificate2(inputP12, inputPfxPassword, X509KeyStorageFlags.Exportable);
        RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider();
        var xmlString = signerCert.PrivateKey.ToXmlString(true);
        rsaCSP.FromXmlString(xmlString);
        byte[] signedData = rsaCSP.SignData(signableHash, CryptoConfig.MapNameToOID("SHA1"));
        return signedData;
    }
}

Para crear una firma, se requiere una estimación preliminar de la longitud de la futura firma digital. Si usa SignHash para crear una firma digital, puede observar que el delegado se llama dos veces durante el proceso de guardado del documento. Si por alguna razón no puede permitirse dos llamadas, por ejemplo, si ocurre una solicitud de PIN durante la llamada, puede usar la opción AvoidEstimatingSignatureLength para las clases PKCS1, PKCS7, PKCS7Detached y ExternalSignature. Configurar esta opción evita el paso de estimación de la longitud de la firma estableciendo un valor fijo como la longitud esperada – DefaultSignatureLength. El valor predeterminado de la propiedad DefaultSignatureLength es 3000 bytes. La opción AvoidEstimatingSignatureLength solo funciona si el delegado SignHash está configurado en la propiedad CustomSignHash. Si la longitud de la firma resultante supera la longitud esperada especificada por la propiedad DefaultSignatureLength, recibirá una SignatureLengthMismatchException que indica la longitud real. Puede ajustar el valor del parámetro DefaultSignatureLength a su discreción.

// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void SignWithCertificate(string pfxFilePath, string password)
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

    using (var sign = new Aspose.Pdf.Facades.PdfFileSignature())
    {   
        // Bind PDF document
        sign.BindPdf(dataDir + "input.pdf");
        // Create PKCS#7 object to sign
        var pkcs7 = new Aspose.Pdf.Forms.PKCS7(pfxFilePath, password);// You can use PKCS7Detached with digest algorithm argument
        // Set the delegate to external sign
        pkcs7.CustomSignHash = CustomSignHash;
        // Set an option to avoiding twice SignHash calling.
        pkcs7.AvoidEstimatingSignatureLength = true;
        // Sign the file
        sign.Sign(1, "reason", "cont", "loc", false, new System.Drawing.Rectangle(0, 0, 500, 500), pkcs7);
        // Save PDF document
        sign.Save(dataDir + "SignWithCertificate_out.pdf");
    }

    // Custom hash signing function to generate a digital signature
    byte[] CustomSignHash(byte[] signableHash, Aspose.Pdf.DigestHashAlgorithm digestHashAlgorithm)
    {
        var inputP12 = "111.p12";
        var inputPfxPassword = "123456";
        X509Certificate2 signerCert = new X509Certificate2(inputP12, inputPfxPassword, X509KeyStorageFlags.Exportable);
        RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider();
        var xmlString = signerCert.PrivateKey.ToXmlString(true);
        rsaCSP.FromXmlString(xmlString);
        byte[] signedData = rsaCSP.SignData(signableHash, CryptoConfig.MapNameToOID("SHA1"));
        return signedData;
    }
}

Firmar documentos PDF utilizando ECDSA

Signing PDF documents using ECDSA (Elliptic Curve Digital Signature Algorithm) involves utilizing elliptic curve cryptography to generate signatures. This offers high security and efficiency, especially for mobile and resource-constrained environments. This approach ensures that your PDF documents are digitally signed with the security advantages of elliptic curve cryptography.

Aspose.PDF soporta la creación y verificación de firmas digitales basadas en ECDSA. Las siguientes curvas elípticas son compatibles para la creación y verificación de firmas digitales:

  • P-192(secp192r1).
  • P-256(secp256r1).
  • P-384(secp384r1).
  • P-521(secp521r1).
  • brainpoolP192r1.
  • brainpoolP256r1.
  • brainpoolP384r1.
  • brainpoolP512r1.

El algoritmo de resumen predeterminado es SHA2 con una longitud dependiente del tamaño de la clave ECDSA. Puede especificar el algoritmo de resumen en el constructor PKCS7Detached.

Las firmas digitales ECDSA con los siguientes algoritmos de resumen pueden firmarse: SHA-256, SHA-384, SHA3–512, SHA3–256, SHA3–384, SHA3–512. Las firmas digitales ECDSA con los siguientes algoritmos de resumen pueden verificarse: SHA-256, SHA-384, SHA3–512, SHA3–256, SHA3–384, SHA3–512.

Puede comprobar la firma y la verificación creando un PFX(output.pfx) certificado en OpenSSL.

openssl ecparam -genkey -name brainpoolP512r1 -out private.key
openssl ec -in private.key -pubout -out public.pem
openssl req -new -x509 -days 365 -key private.key -out certificate.crt -subj "/C=PL/ST=Silesia/L=Katowice/O=My2 Organization/CN=example2.com"
openssl pkcs12 -export -out output.pfx -inkey private.key -in certificate.crt

Nombres de curva disponibles para firma y verificación en Aspose.PDF (the list of curves in OpenSSL can be obtained with the command ‘openssl ecparam -list_curves’): prime256v1, secp384r1, secp521r1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1.

Para firmar un documento PDF utilizando ECDSA, los pasos generales en C# serían:

  1. Necesitará un certificado ECDSA en formato PFX o P12. Estos certificados contienen tanto las públicas como privadas necesarias para la firma.
  2. Usando una biblioteca Aspose.PDF, vincula el documento a un manejador de firma.
  3. Utilice la clave privada ECDSA para firmar el hash del contenido del documento.
  4. Coloque la firma generada dentro del archivo PDF junto con metadatos como la razón para firmar, ubicación y datos de contacto.
// For complete examples and data files, visit https://github.com/aspose-pdf/Aspose.PDF-for-.NET
private static void VerifyEcda()
{
   // The path to the documents directory
   var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures();

   // Open PDF document
   using (var document = new Aspose.Pdf.Document(dataDir + "signed_ecdsa.pdf"))
    {
        // Create an instance of PdfFileSignature for working with signatures in the document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            // Check if the document contains any digital signatures
            if (!signature.ContainsSignature())
            {
                throw new Exception("Not contains signature");
            }

            // Get a list of signature names in the document
            var sigNames = signature.GetSignatureNames();

            // Loop through all signature names to verify each one
            foreach (var sigName in sigNames)
            {
                // Verify that the signature with the given name is valid
                if (!signature.VerifySignature(sigName))
                {
                    throw new Exception("Not verified");
                }
            }
        }
    }
}

private static void SignEcdsa(string pfxFilePath, string password)
{
    // The path to the documents directory
    var dataDir = RunExamples.GetDataDir_AsposePdf_SecuritySignatures(); 

    // Open PDF document
    using (var document = new Aspose.Pdf.Document(dataDir + "input.pdf"))
    {
        // Create an instance of PdfFileSignature to sign the document
        using (var signature = new Aspose.Pdf.Facades.PdfFileSignature(document))
        {
            // Create a PKCS7Detached object using the provided certificate and password
            var pkcs = new Aspose.Pdf.Forms.PKCS7Detached(cert, "12345", Aspose.Pdf.DigestHashAlgorithm.Sha256);

            // Sign the first page of the document, setting the signature's appearance at the specified location
            signature.Sign(1, true, new System.Drawing.Rectangle(300, 100, 400, 200), pkcs);

            // Save PDF document
            signature.Save(dataDir + "SignEcdsa_out.pdf");
        }
    }
}