¿Cómo puedo firmar un archivo usando RSA y SHA256 con .NET?

Mi aplicación tomará un conjunto de archivos y los firmará. (No estoy intentando firmar un ensamblado). Hay un archivo .p12 del que obtengo la clave privada.

Este es el código que estaba tratando de usar, pero obtengo una System.Security.Cryptography.CryptographicException "Invalid algorithm specified." .

 X509Certificate pXCert = new X509Certificate2(@"keyStore.p12", "password"); RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey; string id = CryptoConfig.MapNameToOID("SHA256"); return csp.SignData(File.ReadAllBytes(filePath), id); 

Según esta respuesta , no se puede hacer (el RSACryptoServiceProvider no es compatible con SHA-256), pero esperaba que fuera posible utilizar una biblioteca diferente, como Bouncy Castle.

Soy nuevo en esto y creo que Bouncy Castle es muy confuso. Estoy portando una aplicación Java a C # y tengo que usar el mismo tipo de encriptación para firmar los archivos, así que estoy atascado con RSA + SHA256.

¿Cómo puedo hacer esto usando Bouncy Castle, OpenSSL.NET, Security.Cryptography u otra biblioteca de terceros de la que no haya oído hablar? Supongo que, si se puede hacer en Java, entonces se puede hacer en C #.

ACTUALIZAR:

esto es lo que obtuve del enlace en el libro de poupou

  X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password"); RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey; CspParameters cspParam = new CspParameters(); cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName; cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2; RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam); aescsp.PersistKeyInCsp = false; byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256"); bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed); 

El problema es que no obtengo los mismos resultados que obtuve con la herramienta original. Por lo que puedo decir al leer el código, CryptoServiceProvider que hace la firma real no utiliza la clave privada desde el archivo de la tienda de claves. ¿Es eso correcto?

RSA + SHA256 puede y va a funcionar …

Es posible que su ejemplo posterior no funcione todo el tiempo, debería usar el OID del algoritmo hash, en lugar de su nombre. Según su primer ejemplo, esto se obtiene de una llamada a [CryptoConfig.MapNameToOID] (AlgorithmName) 1 donde AlgorithmName es lo que está proporcionando (es decir, “SHA256”).

Lo primero que necesitará es el certificado con la clave privada. Normalmente leo los míos de la tienda LocalMachine o CurrentUser usando un archivo de clave pública (.cer) para identificar la clave privada, y luego enumero los certificados y los cotejo en el hash …

 X509Certificate2 publicCert = new X509Certificate2(@"C:\mycertificate.cer"); //Fetch private key from the local machine store X509Certificate2 privateCert = null; X509Store store = new X509Store(StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); foreach( X509Certificate2 cert in store.Certificates) { if (cert.GetCertHashString() == publicCert.GetCertHashString()) privateCert = cert; } 

Sin importar cómo llegues, una vez que hayas obtenido un certificado con una clave privada, tenemos que reconstruirlo. Esto puede ser necesario debido a la forma en que el certificado crea su clave privada, pero no estoy seguro de por qué. De todas formas, hacemos esto exportando primero la clave y luego reimportándola usando el formato intermedio que prefiera, lo más fácil es xml:

 //Round-trip the key to XML and back, there might be a better way but this works RSACryptoServiceProvider key = new RSACryptoServiceProvider(); key.FromXmlString(privateCert.PrivateKey.ToXmlString(true)); 

Una vez hecho esto, ahora podemos firmar un dato de la siguiente manera:

 //Create some data to sign byte[] data = new byte[1024]; //Sign the data byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256")); 

Por último, la verificación se puede hacer directamente con la clave pública del certificado sin necesidad de la reconstrucción como lo hicimos con la clave privada:

 key = (RSACryptoServiceProvider)publicCert.PublicKey.Key; if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig)) throw new CryptographicException(); 

El uso de privateKey.toXMLString (true) o privateKey.exportParameters (true) no se puede utilizar en un entorno seguro, ya que requieren que su clave privada sea exportable, lo cual NO es una buena práctica.

Una mejor solución es cargar explícitamente el proveedor de cifrado “Mejorado” como tal:

 // Find my openssl-generated cert from the registry var store = new X509Store(StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true); var certificate = certificates[0]; store.Close(); // Note that this will return a Basic crypto provider, with only SHA-1 support var privKey = (RSACryptoServiceProvider)certificate.PrivateKey; // Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo; var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName); privKey = new RSACryptoServiceProvider(cspparams); 

Así es como me enfrenté a ese problema:

  X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable); // This instance can not sign and verify with SHA256: RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey; // This one can: RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider(); privateKey1.ImportParameters(privateKey.ExportParameters(true)); byte[] data = Encoding.UTF8.GetBytes("Data to be signed"); byte[] signature = privateKey1.SignData(data, "SHA256"); bool isValid = privateKey1.VerifyData(data, "SHA256", signature); 

Me decidí a cambiar el archivo de clave para especificar el proveedor de servicios de cifrado apropiado , evitando por completo el problema en .NET.

Entonces, cuando creo un archivo PFX a partir de una clave privada PEM y un certificado público CRT, lo hago de la siguiente manera:

 openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx 

La parte clave es -CSP “Microsoft Enhanced RSA and AES Cryptographic Provider” .

( -inkey especifica el archivo de clave privada y -in especifica el certificado público para incorporar.)

Es posible que necesite modificar esto para los formatos de archivo que tenga a mano. Los ejemplos de línea de comando en esta página pueden ayudar con eso: https://www.sslshopper.com/ssl-converter.html

Encontré esta solución aquí: http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/

Cuando utiliza un certificado para obtener su RSACryptoServiceProvider, realmente importa cuál es el proveedor de CryptoAPI subyacente. De forma predeterminada, cuando crea un certificado con ‘makecert’, es “RSA-FULL”, que solo admite hashes SHA1 para la firma. Necesita el nuevo “RSA-AES” que es compatible con SHA2.

Por lo tanto, puede crear su certificado con una opción adicional: -sp “Microsoft Enhanced RSA y AES Cryptographic Provider” (o un equivalente -sy 24) y luego su código funcionaría sin los elementos clave de malabarismo.

Según este blog , debería funcionar con FX 3.5 (ver nota a continuación). Sin embargo, es importante recordar que la mayor parte de la criptografía .NET se basa en CryptoAPI (incluso si el GNC está siendo cada vez más expuesto en las últimas versiones de FX).

El punto clave es que el soporte del algoritmo CryptoAPI depende del proveedor de servicios criptográficos (CSP) que se utiliza y que varía un poco entre las versiones de Windows (es decir, lo que funciona en Windows 7 podría no funcionar en Windows 2000).

Lea los comentarios (de la entrada del blog) para ver una posible solución alternativa en la que especifique el AES CSP (en lugar del predeterminado) al crear su instancia de RSACCryptoServiceProvider . Eso parece funcionar para algunas personas, YMMV.

Nota: esto es confuso para muchas personas porque todos los frameworks .NET lanzados incluyen una implementación administrada de SHA256 que no puede ser utilizada por CryptoAPI. FWIW Mono no sufre de tales problemas 😉

Así es como firmé una cadena sin tener que modificar el certificado (a un Microsoft Enhanced RSA y AES Cryptographic provider).

  byte[] certificate = File.ReadAllBytes(@"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx"); X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable); string stringToBeSigned = "This is a string to be signed"; SHA256Managed shHash = new SHA256Managed(); byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned)); var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider; RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider(); defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true)); byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256"); string signature = Convert.ToBase64String(signedHashValue); Console.WriteLine("Signature : {0}", signature); RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider; bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue); Console.WriteLine("Verification result : {0}", verify); 

He notado problemas similares en .NET con la clave privada incorrecta que se usa (¿o fue un error rotundo? No lo recuerdo) cuando el certificado con el que estoy trabajando no está en el almacén de certificados de usuario / computadora. Al instalarlo en el sistema almacenado solucioné el problema para mi escenario y las cosas comenzaron a funcionar como se esperaba, tal vez puedas intentarlo.