Intro
Je travaille sur la conversion d'une bibliothèque Java en .Net.
La bibliothèque est une implémentation du déchiffrement polymorphe de pseudonymes et sera utilisée aux Pays-Bas pour déchiffrer les "BSNk" dans le domaine des services européens d’identification électronique eIDAS.
J'ai déjà converti la majeure partie de la bibliothèque et travaillé avec l'auteur de la version Java pour vérifier les résultats ..___ La prochaine étape consiste à rendre la bibliothèque .Net réellement utilisable par les entreprises néerlandaises, et c'est là que je me suis retrouvé coincé 2 dernières semaines.
Les algorithmes utilisent une courbe elliptique dans un fichier PEM comme l'une des parties du calcul. Mais les clients (les utilisateurs de la bibliothèque) le recevront sous la forme d’un fichier p7 et d’un fichier p8 que vous pourrez convertir/extraire/décoder (?) En données PEM.
Question
Comment puis-je obtenir des fichiers p7 + p8 en une chaîne PEM en C #?
De préférence, en n'utilisant que System.Security.Cryptography.Pkcs, mais j'utilise actuellement BouncyCastle dans d'autres parties (car la version Java l'a fait). mais (pour moi) des erreurs incompréhensibles de cela. Je n'ai pas beaucoup d'expérience en cryptographie, mais j'ai beaucoup appris ces dernières semaines.
Si je le comprends bien, je l’expliquerais par le fait que le fichier p7 est l’enveloppe du message PEM et que l’enveloppe est signée/cryptée à l’aide de la clé privée du fichier p8?
Code
public static string ConvertToPem(string p7File, string p8File)
{
var p7Data = File.ReadAllBytes(p7File);
var p8Data = File.ReadAllBytes(p8File);
// Java version gets the private key like this:
// KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(bytesArray));
var privateKey = PrivateKeyFactory.CreateKey(p8Data);
var parser = new CmsEnvelopedDataParser(p7Data);
var recipients = parser.GetRecipientInfos().GetRecipients().OfType<RecipientInformation>();
var recipientInformation = recipients.First();
//Java version gets the message like this:
//final byte[] message = keyInfo.getContent(new JceKeyTransEnvelopedRecipient(key).setProvider("BC"));
var keyInfo = (KeyTransRecipientInformation)recipientInformation;
var message = keyInfo.GetContent(privateKey);
return Encoding.ASCII.GetString(message);
}
Mise à jour 8-10-2018 Suivant un conseil de l'auteur de la bibliothèque Java, j'ai essayé d'éviter le problème de la conversion automatique en PEM et de simplement utiliser openssl pour le déchiffrer. Malheureusement, la commande openssl pour décrypter les fichiers échoue également! Tant sur Windows que sur Linux. Ce qui est étrange, c’est que cela se fait avec les mêmes fichiers qui fonctionnent parfaitement bien dans la bibliothèque Java. Le p8 est-il corrompu? Est-ce que, d'une manière ou d'une autre, il est uniquement compatible avec Java JceKeyTransEnvelopedRecipient ???
openssl cms -decrypt -inform DER -in dv_keys_ID_D_oin.p7 -inkey privatep8.key -out id.pem
(J'ai aussi essayé d'utiliser PEM au lieu de DER mais en vain. Les fichiers sont dans le dépôt GitHub)
Mise à jour 9-10-2018 Merci à Carl qui a découvert la cause du fichier p8 apparemment corrompu. Au lieu de le déchiffrer directement à l'aide de cms openssl, nous avons d'abord dû convertir le fichier binaire DER p8 en un fichier PEM codé en base64.
openssl pkcs8 -inform der -outform pem -in private.p8 -out private-p8.pem -topk8 -nocrypt
Nous pourrions également le faire en c # en lisant les octets du fichier p8, en les convertissant au format Base64 et en ajoutant l'en-tête/le pied de page BEGIN/END PRIVATE KEY autour de celui-ci.
Ressources
Vous pouvez voir que ce code est utilisé et échoue en tant que test unitaire dans mon projet. Le projet comprend également les fichiers p7, p8 et PEM correspondants à tester.
La version Java peut être trouvée ici: https://github.com/BramvanPelt/PPDecryption
Ma version en cours de travail peut être trouvée ici: https://github.com/MartijnKooij/PolymorphicPseudonymisation
Finalement, j'ai réussi à décrypter le message. il semble que les API BouncyCastle ignorent la directive SHA-256 OAEP et s'en tiennent à SHA-1 OAEP, ce qui entraîne une exception de remplissage. De plus, les API Microsoft tirent profit de X509Certificate2
qui ne prend en charge que RsaCryptoServiceProvider
avec le support SHA-1 OAEP dans la mesure de mes connaissances. Il faut la version plus récente RsaCng
pour la prise en charge de SHA-256 OAEP. Je pense que nous devons créer un ticket avec corefx ( https://github.com/dotnet/corefx ) ainsi que bc-csharp ( https://github.com/bcgit/bc-csharp ).
Le code c # suivant décryptera le message. en utilisant les API Microsoft:
// Read the RSA private key:
var p8Data = File.ReadAllBytes(@"resources\private.p8");
CngKey key = CngKey.Import(p8Data, CngKeyBlobFormat.Pkcs8PrivateBlob);
var rsaprovider = new RSACng(key);
// Process the enveloped CMS structure:
var p7Data = File.ReadAllBytes(@"resources\p7\ID-4.p7");
var envelopedCms = new System.Security.Cryptography.Pkcs.EnvelopedCms();
envelopedCms.Decode(p7Data);
var recipients = envelopedCms.RecipientInfos;
var firstRecipient = recipients[0];
// Decrypt the AES-256 CBC session key; take note of enforcing OAEP SHA-256:
var result = rsaprovider.Decrypt(firstRecipient.EncryptedKey, RSAEncryptionPadding.OaepSHA256);
// Build out the AES-256 CBC decryption:
RijndaelManaged alg = new RijndaelManaged();
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = result;
// I used an ASN.1 parser (https://lapo.it/asn1js/) to grab the AES IV from the PKCS#7 file.
// I could not find an API call to get this from the enveloped CMS object:
string hexstring = "919D287AAB62B672D6912E72D5DA29CD";
var iv = StringToByteArray(hexstring);
alg.IV = iv;
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.PKCS7;
// Strangely both BouncyCastle as well as the Microsoft API report 406 bytes;
// whereas https://lapo.it/asn1js/ reports only 400 bytes.
// The 406 bytes version results in an System.Security.Cryptography.CryptographicException
// with the message "Length of the data to decrypt is invalid.", so we strip it to 400 bytes:
byte[] content = new byte[400];
Array.Copy(envelopedCms.ContentInfo.Content, content, 400);
string decrypted = null;
ICryptoTransform decryptor = alg.CreateDecryptor(alg.Key, alg.IV);
using (var memoryStream = new MemoryStream(content)) {
using (var cryptoStream = new CryptoStream(memoryStream, alg.CreateDecryptor(alg.Key, alg.IV), CryptoStreamMode.Read)) {
decrypted = new StreamReader(cryptoStream).ReadToEnd();
}
}
L'implémentation de StringToByteArray
est la suivante:
public static byte[] StringToByteArray(String hex) {
NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Vous devriez pouvoir atteindre vos objectifs avec .NET 4.7.2:
using (CngKey key = CngKey.Import(p8Data, CngKeyBlobFormat.Pkcs8PrivateBlob))
{
// The export policy needs to be redefined because CopyWithPrivateKey
// needs to export/re-import ephemeral keys
key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
using (RSA rsa = new RSACng(key))
using (X509Certificate2 cert = new X509Certificate2(certData))
using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(rsa))
{
EnvelopedCms cms = new EnvelopedCms();
cms.Decode(p7Data);
cms.Decrypt(new X509Certificate2Collection(certWithKey));
// I get here reliably with your reference documents
}
}