web-dev-qa-db-fra.com

CryptographicException "Clé non valide pour une utilisation dans l'état spécifié." tout en essayant d'exporter des paramètres RSAP d'une clé privée X509

Je regarde cela depuis un bon moment et grâce à la documentation MSDN je ne peux pas vraiment comprendre ce qui se passe. Fondamentalement, je charge un fichier PFX du disque dans un X509Certificate2 Et j'essaie de crypter une chaîne à l'aide de la clé publique et de décrypter à l'aide de la clé privée.

Pourquoi suis-je perplexe: le cryptage/décryptage fonctionne lorsque je passe la référence au RSACryptoServiceProvider lui-même:

byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);

Mais si l'export et le tour du RSAParameter:

byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));

... il lance une "clé non valide pour une utilisation dans l'état spécifié." exception lors de la tentative d'exportation de la clé privée vers RSAParameter. Veuillez noter que le certificat à partir duquel le PFX est généré est marqué comme exportable (c'est-à-dire que j'ai utilisé le drapeau pe lors de la création du certificat). Une idée de ce qui cause l'exception?

 static void Main(string[] args)
    {
        X509Certificate2 x = new X509Certificate2(@"C:\temp\certs\1\test.pfx", "test");
        x.FriendlyName = "My test Cert";

        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadWrite);
        try
        {
            store.Add(x);
        }
        finally
        {
            store.Close();
        }

        byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
        string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);

        byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
        string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
    }

private static byte[] EncryptRSA(string data, RSAParameters rsaParameters)
{
    UnicodeEncoding bytConvertor = new UnicodeEncoding();
    byte[] plainData = bytConvertor.GetBytes(data);

    RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
    publicKey.ImportParameters(rsaParameters);
    return publicKey.Encrypt(plainData, true);
}

private static string DecryptRSA(byte[] data, RSAParameters rsaParameters)
{
    UnicodeEncoding bytConvertor = new UnicodeEncoding();

    RSACryptoServiceProvider privateKey = new RSACryptoServiceProvider();
    privateKey.ImportParameters(rsaParameters);

    byte[] deData = privateKey.Decrypt(data, true);
    return bytConvertor.GetString(deData);
}

private static byte[] EncryptRSA(string data, RSACryptoServiceProvider publicKey)
{
    UnicodeEncoding bytConvertor = new UnicodeEncoding();
    byte[] plainData = bytConvertor.GetBytes(data);

    return publicKey.Encrypt(plainData, true);
}

private static string DecryptRSA(byte[] data, RSACryptoServiceProvider privateKey)
{
    UnicodeEncoding bytConvertor = new UnicodeEncoding();

    byte[] deData = privateKey.Decrypt(data, true);
    return bytConvertor.GetString(deData);
}

Juste pour clarifier dans le code ci-dessus la partie en gras lance: string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider)**.ExportParameters(true)**);

52
kalrashi

Je pense que le problème peut être que la clé n'est pas marquée comme exportable. Il existe un autre constructeur pour X509Certificate2 qui prend une énumération X509KeyStorageFlags. Essayez de remplacer la ligne:

X509Certificate2 x = new X509Certificate2(@"C:\temp\certs\1\test.pfx", "test");

Avec ça:

X509Certificate2 x = new X509Certificate2(@"C:\temp\certs\1\test.pfx", "test", X509KeyStorageFlags.Exportable);
110
Iridium

Pour le problème que j'ai rencontré, un changement de code n'était pas une option car la même bibliothèque était installée et fonctionnait ailleurs.

La réponse d'Iridium m'a amené à chercher à rendre la clé exportable et j'ai pu le faire dans le cadre de l'assistant d'importation de certificat MMC.

J'espère que ceci aide quelqu'un d'autre. Merci des tas

Cert import wizard

13
will webster

J'ai rencontré un problème similaire et X509KeyStorageFlags.Exportable a résolu mon problème.

8
user1187372

Je ne suis pas exactement un expert dans ces choses, mais j'ai fait un rapide google, et j'ai trouvé ceci:

http://social.msdn.Microsoft.com/Forums/en/clr/thread/4e3ada0a-bcaf-4c67-bdef-a6b15f5bfdce

"si vous avez plus de 245 octets dans votre tableau d'octets que vous transmettez à votre méthode RSACryptoServiceProvider.Encrypt (octet [] rgb, bool fOAEP) alors il lèvera une exception."

3
Jeroen Jacobs

Pour d'autres qui se retrouvent ici via Google, mais n'utilisent aucun X509Certificate2, si vous appelez ToXmlString sur RSACryptoServiceProvider mais que vous n'avez chargé qu'une clé publique, vous obtiendrez également ce message. Le correctif est le suivant (notez la dernière ligne):

var rsaAlg = new RSACryptoServiceProvider();

rsaAlg.ImportParameters(rsaParameters);

var xml = rsaAlg.ToXmlString(!rsaAlg.PublicOnly);
2
Gijs

AFAIK cela devrait fonctionner et vous rencontrez probablement un bug/quelques limitations. Voici quelques questions qui peuvent vous aider à déterminer où se situe le problème.

  • Comment avez-vous créé le fichier PKCS # 12 (PFX)? J'ai vu des clés que CryptoAPI n'aime pas (paramètres RSA rares). Pouvez-vous utiliser un autre outil (juste pour être sûr)?

  • Pouvez-vous exporter l'instance PrivateKey en XML, par exemple ToXmlString(true), puis le charger (l'importer) de cette façon?

  • Les anciennes versions du framework avaient quelques problèmes lors de l'importation d'une clé d'une taille différente de l'instance actuelle (par défaut à 1024 bits). Quelle est la taille de votre clé publique RSA dans votre certificat?

Notez également que ce n'est pas la façon dont vous devez crypter les données à l'aide de RSA. La taille du chiffrement brut est limitée par rapport à la clé publique utilisée. Une boucle au-dessus de cette limite ne vous donnerait vraiment que de mauvaises performances.

L'astuce consiste à utiliser un algorithme symétrique (comme [~ # ~] aes [~ # ~] ) avec un totalement clé aléatoire, puis chiffrez cette clé (envelopper) à l'aide de la clé publique RSA. Vous pouvez trouver le code C # pour le faire dans mon ancien entrée de blog sur le sujet.

1
poupou