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


Mi solicitud tomará un conjunto de archivos y los firmará. (No estoy tratando de firmar una asamblea.) Hay una .archivo p12 del que obtengo la clave privada.

Este es el código que estaba tratando de usar, pero obtengo un 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);

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

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

¿Cómo puedo hacer esto usando Bouncy Castle, OpenSSL.NET, Seguridad.Criptografía, u otra biblioteca de terceros de la que no he oído hablar? Estoy asumiendo, si se puede hacer en Java entonces se puede hacer en C#.

ACTUALIZACIÓN:

Esto es lo que obtuve del enlace en anwser 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 estoy obteniendo los mismos resultados que Conseguí con la herramienta original. Por lo que puedo decir de la lectura del código del CryptoServiceProvider que hace la firma real no está utilizando el PrivateKey de archivo de la tienda de claves. ¿Es correcto?

Author: Community, 2011-09-16

8 answers

RSA + SHA256 puede y funcionará...

Su ejemplo posterior puede no funcionar todo el tiempo, debe 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] (Algorítmname)1 donde AlgorithmName es lo que está proporcionando (es decir, "SHA256").

Lo primero que va a necesitar es el certificado con la clave privada. Normalmente leo el mío de la tienda LocalMachine o CurrentUser por usando un archivo de clave pública (.cer) para identificar la clave privada, y luego enumerar los certificados y coincidir 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 Embargo allí, una vez que haya obtenido un certificado con una clave privada que debemos reconstruir. Esto puede ser necesario debido a la forma en que el certificado crea su clave privada, pero no estoy muy seguro de por qué. De todos modos, hacemos esto primero exportando la clave y luego reimportándola usando cualquier formato intermedio que desee, la 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 fragmento de datos 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 hicimos con la clave privada:

key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
    throw new CryptographicException();
 47
Author: csharptest.net,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-07 20:55:01

El uso de PrivateKey.toXMLString (true) o PrivateKey.ExportParameters (true) no se pueden usar 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 criptomonedas "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);
 18
Author: BKibler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-24 12:41:53

Así es como lidié con 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);
 7
Author: user3658415,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-20 21:19:47

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

Así que 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 a incorporar.)

Puede necesitar para ajustar esto para los formatos de archivo que tiene a mano. Los ejemplos de la línea de comandos 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/

 7
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-28 09:56:34

Cuando usa un certificado para obtener su RSACryptoServiceProvider, realmente importa cuál es el proveedor subyacente de CryptoAPI. 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 soporte SHA2.

Por lo tanto, puede crear su certificado con una opción adicional: - sp" Microsoft Enhanced RSA and AES Cryptographic Provider " (o un equivalente-sy 24) y luego su código funcionaría sin la llave haciendo malabares.

 5
Author: Kastorskij,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-27 10:18:43

De acuerdo con este blog debería trabajar 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 CNG está siendo cada vez más expuesto en los últimos lanzamientos de FX).

La clave es que CryptoAPI El soporte del algoritmo depende del Proveedor de Servicios Crypto (CSP) que se utiliza y que varía un poco entre las versiones de Windows (es decir, lo que funciona en Windows 7 puede que no funcione en Windows 2000).

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

Nota: esto es confuso para muchas personas porque todos los.NET frameworks liberados incluyen una implementación administrada de SHA256 que no puede ser utilizada por CryptoAPI. FWIW Mono hace no sufrir de tales problemas; -)

 3
Author: poupou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-16 18:27:04

Así es como firmé una cadena sin tener que modificar el certificado (a un proveedor de cifrado RSA y AES mejorado de Microsoft).

        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);
 2
Author: AdmiralThrawn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-21 18:03:50

He notado problemas similares en. NET con la clave privada incorrecta que se utiliza (o fue errores de plano? No recuerdo) cuando el certificado con el que estoy trabajando no está en el almacén de certificados de usuario/equipo. Instalarlo en el almacenado solucionó el problema para mi escenario y las cosas comenzaron a funcionar como se esperaba, tal vez pueda intentarlo.

 0
Author: Sander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-19 14:36:50