web-dev-qa-db-fra.com

Comment signer un fichier en utilisant RSA et SHA256 avec .NET?

Mon application va prendre un ensemble de fichiers et les signer. (Je n'essaie pas de signer une assemblée.) Il y a un fichier .p12 dont je récupère la clé privée.

C’est le code que j’essayais d’utiliser, mais j’obtiens 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);

Selon cette réponse cela ne peut pas être fait (la RSACryptoServiceProvider ne supporte pas SHA-256), mais j'espérais que cela pourrait être possible en utilisant une autre bibliothèque, comme Bouncy Castle.

Je suis novice dans ce domaine et je trouve que Bouncy Castle est très déroutant. Je porte une application Java en C # et je dois utiliser le même type de cryptage pour signer les fichiers. Je suis donc coincé avec RSA + SHA256.

Comment puis-je faire cela en utilisant Bouncy Castle, OpenSSL.NET, Security.Cryptography ou une autre bibliothèque tierce dont je n'ai pas entendu parler? Je suppose que si cela peut être fait en Java, alors cela peut être fait en C #.

METTRE À JOUR:

c'est ce que j'ai obtenu du lien dans la réponse 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);

Le problème est que je n'obtiens pas les mêmes résultats que ceux obtenus avec l'outil d'origine. Autant que je sache, d'après la lecture du code, CryptoServiceProvider qui effectue la signature n'utilise pas la clé PrivateKey du fichier de magasin de clés. Est-ce exact?

41
scott

RSA + SHA256 peut et va fonctionner ...

Votre dernier exemple may ne fonctionnera pas tout le temps, il devrait utiliser l'OID de l'algorithme de hachage plutôt que son nom. Selon votre premier exemple, ceci est obtenu à partir d'un appel à [CryptoConfig.MapNameToOID] (AlgorithmName) 1 où AlgorithmName est ce que vous fournissez (c'est-à-dire "SHA256").

Tout d’abord, vous aurez besoin du certificat avec la clé privée. Je lis normalement le mien à partir du magasin LocalMachine ou CurrentUser en utilisant un fichier de clé publique (.cer) pour identifier la clé privée, puis énumérer les certificats et les faire correspondre au hachage ...

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;
}

Quoi que vous fassiez, une fois que vous avez obtenu un certificat avec une clé privée, vous devez le reconstruire. Cela peut être nécessaire en raison de la manière dont le certificat crée sa clé privée, mais je ne sais pas trop pourquoi. Quoi qu'il en soit, nous le faisons d'abord en exportant la clé, puis en la réimportant en utilisant le format intermédiaire de votre choix, le plus simple est 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));

Une fois que cela est fait, nous pouvons maintenant signer une donnée comme suit:

//Create some data to sign
byte[] data = new byte[1024];

//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));

Enfin, la vérification peut être effectuée directement avec la clé publique du certificat sans nécessiter de reconstruction comme nous l'avons fait avec la clé privée:

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

L'utilisation de privateKey.toXMLString (true) ou de privateKey.exportParameters (true) ne peut pas être utilisée dans un environnement sécurisé, car votre clé privée doit être exportable, ce qui n'est PAS une bonne pratique.

Une meilleure solution consiste à charger explicitement le fournisseur de chiffrement "amélioré" en tant que tel:

// 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
BKibler

Voici comment j'ai traité ce problème:

 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
user3658415

Lorsque vous utilisez un certificat pour obtenir votre RSACryptoServiceProvider, le fournisseur CryptoAPI sous-jacent importe vraiment. Par défaut, lorsque vous créez un certificat avec «makecert», c'est «RSA-FULL» qui ne prend en charge que les hachages SHA1 pour la signature. Vous avez besoin du nouveau "RSA-AES" qui prend en charge SHA2.

Ainsi, vous pouvez créer votre certificat avec une option supplémentaire: -sp "Fournisseur de cryptographie RSA et AES Microsoft amélioré" (ou un équivalent -sy 24) et votre code fonctionnerait alors sans tout le matériel nécessaire.

5
Kastorskij

Voici comment j'ai signé une chaîne sans avoir à modifier le certificat (auprès d'un fournisseur cryptographique Microsoft Enhanced RSA et AES).

        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);
4
AdmiralThrawn

Selon ce blog it devrait travailler avec FX 3.5 (voir la remarque ci-dessous). Cependant, il est important de rappeler que la majeure partie de la cryptographie .NET est basée sur CryptoAPI (même si CNG est de plus en plus exposé dans les versions FX les plus récentes). 

Le point clé est que la prise en charge de l'algorithme CryptoAPI dépend du fournisseur de services de cryptographie utilisé {Crypto Service Provider} _ et varie légèrement entre les versions de Windows pourrait ne pas fonctionner sous Windows 2000).

Lisez les commentaires (de l'entrée de blog) pour afficher une solution de contournement possible dans laquelle vous spécifiez le CSP AES (au lieu du nom par défaut) lors de la création de votre instance RSACCryptoServiceProvider. Cela semble fonctionner pour certaines personnes, YMMV. 

Remarque: ceci est source de confusion pour de nombreuses personnes car tous les frameworks .NET publiés incluent une implémentation managed de SHA256 qui ne peut pas être utilisée par CryptoAPI. FWIW Mono ne souffre pas de tels problèmes ;-)

3
poupou

Utilisation peut utiliser cela sur des cadres plus récents.

    public byte[] GetSignature(byte[] inputData)
    {
        using (var rsa = this.signingCertificate.GetRSAPrivateKey())
        {
            return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    public bool ValidateSignature(byte[] inputData, byte[] signature)
    {
        using (var rsa = this.signingCertificate.GetRSAPublicKey())
        {
            return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

La signingCertificate ci-dessus est un X509Certificate2 avec une clé privée. Cette méthode ne nécessite pas l'importation de clés existantes et fonctionne dans un environnement sécurisé.

1
Vinay Chandra

J'ai remarqué des problèmes similaires dans .NET avec la mauvaise clé privée utilisée (ou était-ce une erreur totale? Je ne me souviens pas) lorsque le certificat que je travaille ne se trouve pas dans le magasin de certificats utilisateur/ordinateur. L'installer dans le stocké a corrigé le problème pour mon scénario et les choses ont commencé à fonctionner correctement - vous pouvez peut-être essayer.

0
Sander