existe-t-il un moyen pratique d'exporter des clés privées/publiques à partir d'un certificat .p12 au format PEM à l'aide de .NET Core ? Sans manipuler avec des octets à bas niveau? J'ai cherché sur Google pendant des heures et presque rien n'est utilisable dans .net ou ce n'est documenté nulle part.
Ayons un X509Certificate2
var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far
Et maintenant, je dois exporter les clés sous forme de deux clés PEM distinctes. J'ai déjà essayé PemWriter
dans BouncyCastle mais les types ne sont pas compatibles avec System.Security.Cryptography de Core .. pas de chance.
--- Modifier ---
Un autre mot, je trouve un moyen d'écrire ceci:
$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts
Est-ce que quelqu'un a une idée?
Merci ...
La réponse est quelque part entre "non" et "pas vraiment".
Je vais supposer que vous ne voulez pas que le gunk de sortie p12 soit au sommet de public.pub
et private.key
.
public.pub
est simplement le certificat. L'utilitaire de ligne de commande openssl
préfère les données codées PEM. Nous allons donc écrire un certificat codé PEM (remarque: il s'agit d'un certificat et non d'une clé publique. Il contient une clé publique, mais n'en est pas un). :
using (var cert = new X509Certificate2(someBytes, pass))
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN CERTIFICATE-----");
builder.AppendLine(
Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END CERTIFICATE-----");
return builder.ToString();
}
La clé privée est plus difficile. En supposant que la clé soit exportable (ce qui n'est pas le cas si vous utilisez Windows ou macOS, car vous n'avez pas déclaré X509KeyStorageFlags.Exportable
), vous pouvez obtenir les paramètres avec privateKey.ExportParameters(true)
. Mais maintenant, vous devez l'écrire.
Une clé privée RSA est écrite dans un fichier codé PEM dont la balise est "RSA PRIVATE KEY" et dont la charge utile est ASN.1 ( UIT-T X.680 ) RSAPrivateKey (PKCS # 1/ RFC3447 ), généralement codée en DER ( UIT-T X.690 ) - bien qu’elle ne soit pas signée, il n’ya pas de restriction DER particulière, mais de nombreux lecteurs peuvent supposer DER.
Ou bien, il peut s'agir d'une clé privée PKCS # 8 ( RFC 5208 ) PrivateKeyInfo (balise: "CLE PRIVÉE") ou d'une clé EncryptedPrivateKeyInfo (balise: "CLE cryptée privée"). Puisque EncryptedPrivateKeyInfo enveloppe PrivateKeyInfo, qui encapsule RSAPrivateKey, nous allons commencer par là.
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Maintenant ignorez la partie sur otherPrimeInfos. exponent1
est DP, exponent2
est DQ et coefficient
est InverseQ.
Travaillons avec une clé RSA 384 bits pré-publiée .
La RFC 3447 dit que nous voulons la version = 0. Tout le reste vient de la structure.
// SEQUENCE (RSAPrivateKey)
30 xa [ya [za]]
// INTEGER (Version=0)
02 01
00
// INTEGER (modulus)
// Since the most significant bit if the most significant content byte is set,
// add a padding 00 byte.
02 31
00
DA CC 22 D8 6E 67 15 75 03 2E 31 F2 06 DC FC 19
2C 65 E2 D5 10 89 E5 11 2D 09 6F 28 82 AF DB 5B
78 CD B6 57 2F D2 F6 1D B3 90 47 22 32 E3 D9 F5
// INTEGER publicExponent
02 03
01 00 01
// INTEGER (privateExponent)
// high bit isn't set, so no padding byte
02 30
DA CC 22 D8 6E 67 15 75 03 2E 31 F2 06 DC FC 19
2C 65 E2 D5 10 89 E5 11 2D 09 6F 28 82 AF DB 5B
78 CD B6 57 2F D2 F6 1D B3 90 47 22 32 E3 D9 F5
// INTEGER (prime1)
// high bit is set, pad.
02 19
00
FA DB D7 F8 A1 8B 3A 75 A4 F6 DF AE E3 42 6F D0
FF 8B AC 74 B6 72 2D EF
// INTEGER (prime2)
// high bit is set, pad.
02 19
00
DF 48 14 4A 6D 88 A7 80 14 4F CE A6 6B DC DA 50
D6 07 1C 54 E5 D0 DA 5B
// INTEGER (exponent1)
// no padding
02 18
24 FF BB D0 DD F2 AD 02 A0 FC 10 6D B8 F3 19 8E
D7 C2 00 03 8E CD 34 5D
// INTEGER (exponent2)
// padding required
02 19
00
85 DF 73 BB 04 5D 91 00 6C 2D 45 9B E6 C4 2E 69
95 4A 02 24 AC FE 42 4D
// INTEGER (coefficient)
// no padding
02 18
1A 3A 76 9C 21 26 2B 84 CA 9C A9 62 0F 98 D2 F4
3E AC CC D4 87 9A 6F FD
Maintenant, nous comptons le nombre d'octets entrés dans la structure RSAPrivateKey. Je compte 0xF2 (242). Comme il est supérieur à 0x7F, nous devons utiliser un codage de longueur sur plusieurs octets: 81 F2
.
Alors maintenant, avec le tableau d'octets 30 81 F2 02 01 00 ... 9A 6F FD
, vous pouvez le convertir en multi-lignes Base64 et l'envelopper dans une armure PEM "RSA PRIVATE KEY". Mais vous voulez peut-être un PKCS # 8.
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
Faisons-le à nouveau ... La RFC dit que nous voulons aussi la version = 0 ici. AlgorithmIdentifier peut être trouvé dans RFC5280 .
// SEQUENCE (PrivateKeyInfo)
30 xa [ya [za]]
// INTEGER (Version=0)
02 01
00
// SEQUENCE (PrivateKeyAlgorithmIdentifier / AlgorithmIdentifier)
30 xb [yb [zb]]
// OBJECT IDENTIFIER id-rsaEncryption (1.2.840.113549.1.1.1)
06 09 2A 86 48 86 F7 0D 01 01 01
// NULL (per RFC 3447 A.1)
05 00
// OCTET STRING (aka byte[]) (PrivateKey)
04 81 F5
[the previous value here,
note the length here is F5 because of the tag and length bytes of the payload]
Remplir les longueurs:
La série "b" est 13 (0x0D), car elle ne contient que des objets de longueur prédéterminée.
La série "a" est maintenant (2 + 1) + (2 + 13) + (3 + 0xF5) = 266 (0x010A).
30 82 01 0A 02 01 00 30 0D ...
Maintenant, vous pouvez utiliser PEM comme "clé privée".
Le chiffrer? C'est un jeu de balle complètement différent.
J'ai trouvé une solution qui fonctionne bien. Je ne pouvais pas trouver un exemple EXACT montrant comment passer du magasin de certificats au fichier pem dans Windows. Certes, cela peut ne pas fonctionner avec certains certificats, mais si vous travaillez avec un certificat que vous avez créé vous-même (par exemple, si vous avez simplement besoin d'une sécurité entre deux machines que l'utilisateur final ne verra pas), c'est un bon moyen de revenons à pem/pk (style linux).
J'ai utilisé les utilitaires trouvés à http://www.bouncycastle.org/csharp/
X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2 caCert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "3C97BF2632ACAB5E35B48CB94927C4A7D20BBEBA", true)[0];
RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)caCert.PrivateKey;
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(pkey);
using (TextWriter tw = new StreamWriter("C:\\private.pem"))
{
PemWriter pw = new PemWriter(tw);
pw.WriteObject(keyPair.Private);
tw.Flush();
}