is there any convenient way to export private/public keys from .p12 certificate in PEM format using .NET Core? Without manipulating with bytes at low level? I googled for hours and almost nothing is usable in .net core or it isn't documented anywhere..
Let's have an X509Certificate2
var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far
And now I need to export the keys as two separate PEM keys. I already tried PemWriter in BouncyCastle but the types are not compatibile with System.Security.Cryptography from Core... no luck.
In other words, I'm finding a way how to write this:
$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts
Does anybody have an idea?
Thanks
Update (2021-01-12): For .NET 5 this is pretty easy. .NET Core 3.0 can even get most of the way there. The original answer was written when .NET Core 1.1 was the newest version of .NET Core. It explains what these new methods are doing under the covers.
.NET 5+:
byte[] certificateBytes = cert.RawData;
char[] certificatePem = PemEncoding.Write("CERTIFICATE", certificateBytes);
AsymmetricAlgorithm key = cert.GetRSAPrivateKey() ?? cert.GetECDsaPrivateKey();
byte[] pubKeyBytes = key.ExportSubjectPublicKeyInfo();
byte[] privKeyBytes = key.ExportPkcs8PrivateKey();
char[] pubKeyPem = PemEncoding.Write("PUBLIC KEY", pubKeyBytes);
char[] privKeyPem = PemEncoding.Write("PRIVATE KEY", privKeyBytes);
new string(char[]) can turn those char arrays into System.String instances, if desired.
For encrypted PKCS#8 it's still easy, but you have to make some choices for how to encrypt it:
byte[] encryptedPrivKeyBytes = key.ExportEncryptedPkcs8PrivateKey(
password,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 100_000));
.NET Core 3.0, .NET Core 3.1:
This is the same as the .NET 5 answer, except the PemEncoding class doesn't exist yet. But that's OK, there's a start for a PEM-ifier in the older answer (though "CERTIFICATE" and cert.RawData) would need to come from parameters).
.NET Core 3.0 was the release where the extra key format export and import methods were added.
.NET Core 2.0, .NET Core 2.1:
The same as the original answer, except you don't need to write a DER encoder. You can use the System.Formats.Asn1 NuGet package.
Original answer (.NET Core 1.1 was the newest option):
The answer is somewhere between "no" and "not really".
I'm going to assume that you don't want the p12 output gunk at the top of public.pub and private.key.
public.pub is just the certificate. The openssl commandline utility prefers PEM encoded data, so we'll write a PEM encoded certificate (note, this is a certificate, not a public key. It contains a public key, but isn't itself one):
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();
}
The private key is harder. Assuming the key is exportable (which, if you're on Windows or macOS, it isn't, because you didn't assert X509KeyStorageFlags.Exportable) you can get the parameters with privateKey.ExportParameters(true). But now you have to write that down.
An RSA private key gets written into a PEM encoded file whose tag is "RSA PRIVATE KEY" and whose payload is the ASN.1 (ITU-T X.680) RSAPrivateKey (PKCS#1 / RFC3447) structure, usually DER-encoded (ITU-T X.690) -- though since it isn't signed there's not a particular DER restriction, but many readers may be assuming DER.
Or, it can be a PKCS#8 (RFC 5208) PrivateKeyInfo (tag: "PRIVATE KEY"), or EncryptedPrivateKeyInfo (tag: "ENCRYPTED PRIVATE KEY"). Since EncryptedPrivateKeyInfo wraps PrivateKeyInfo, which encapsulates RSAPrivateKey, we'll just start there.
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
}
Now ignore the part about otherPrimeInfos. exponent1 is DP, exponent2 is DQ, and coefficient is InverseQ.
Let's work with a pre-published 384-bit RSA key.
RFC 3447 says we want Version=0. Everything else comes from the 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
Now we count up the number of bytes that went into the RSAPrivateKey structure. I count 0xF2 (242). Since that's bigger than 0x7F we need to use multi-byte length encoding: 81 F2.
So now with the byte array 30 81 F2 02 01 00 ... 9A 6F FD you could convert that to multi-line Base64 and wrap it in "RSA PRIVATE KEY" PEM armor. But maybe you want a PKCS#8.
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
So, let's do it again... The RFC says we want version=0 here, too. AlgorithmIdentifier can be found in 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]
Backfill the lengths:
The "b" series is 13 (0x0D), since it only contains things of pre-determined length.
The "a" series is now (2 + 1) + (2 + 13) + (3 + 0xF5) = 266 (0x010A).
30 82 01 0A 02 01 00 30 0D ...
Now you can PEM that as "PRIVATE KEY".
Encrypting it? That's a whole different ballgame.
I figured out a solution that works well. I could not find an EXACT example of how to go from certificate store to pem file in windows. Granted, this may not work for some certificates, but if you are working with one you have created yourself (for example, if you just need security between two machines you control that the end user won't see) this is a good way of going back to pem / pk (linux style).
I utilized the utilities found at 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();
}
X509certificate2 -> Private, Public and Cert pems...I just discovered that you can do it in 5 or 6 lines of layman's code!
There is a free package called Chilkat (It has some chill branding). It has some very intuitive Certificate Classes Here is some example code on how to create a self signed pfx formatted certificate and export it to PEM! So that is taking a X509Certificate2 instance with a certificate and associated public key and the privatekey that signed it, and then exporting it as three separate Pem files. One for the certificate(includes public key), one for the public key, and one for the private key. Very easy (took a week of reading to figure this out, haha). And then checkout https://github.com/patrickpr/YAOG for a nice OpenSSL windows Gui for viewing/creating certs (as seen in the result screenshot).
using Chilkat;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace CertPractice
{
static public class CertificateUtilityExample
{
public static X509Certificate2 GenerateSelfSignedCertificate()
{
string secp256r1Oid = "1.2.840.10045.3.1.7"; //oid for prime256v1(7) other identifier: secp256r1
string subjectName = "Self-Signed-Cert-Example";
var ecdsa = ECDsa.Create(ECCurve.CreateFromValue(secp256r1Oid));
var certRequest = new CertificateRequest($"CN={subjectName}", ecdsa, HashAlgorithmName.SHA256);
//add extensions to the request (just as an example)
//add keyUsage
certRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));
X509Certificate2 generatedCert = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddYears(10)); // generate the cert and sign!
//----------------end certificate generation, ie start here if you already have an X509Certificate2 instance----------------
X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); //has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request...
Chilkat.Cert chilkatVersionOfPfxGeneratedCert = new Chilkat.Cert(); // now use Chilcat Cert to get pems
chilkatVersionOfPfxGeneratedCert.LoadPfxData(generatedCert.Export(X509ContentType.Pfx), null); // export as binary pfx to load into a Chilkat Cert
PrivateKey privateKey = chilkatVersionOfPfxGeneratedCert.ExportPrivateKey(); // get the private key
privateKey.SavePemFile(#"filepath"); //save the private key to a pem file
Chilkat.PublicKey publicKey = chilkatVersionOfPfxGeneratedCert.ExportPublicKey(); //get the public key
publicKey.SavePemFile(true, #"filepath"); //save the public key
chilkatVersionOfPfxGeneratedCert.ExportCertPemFile(#"filepath"); //save the public Cert to pem file
return pfxGeneratedCert;
}
}
Based on #bartonjs knowledge (his answer), I have written a small class that should be easy to use.
So there is now also a complete example, without having to use external dlls/nuget packages
The only changes I had to make was:
I had to add this "X509KeyStorageFlags.Exportable" to the StorageFlags when creating the X509Certificate2 instance, so that the method "ExportPkcs8PrivateKey()" does not fail.
With my class it is possible to convert Let's Encrypt certificates from PFX format to PEM format with certificate & private key.
How to use my class
var certificateLogic = new CertificateLogic("fileName.pfx", "privateKeyOfPfx");
certificateLogic.LoadCertificate();
certificateLogic.GenerateSaveCertificatePem();
certificateLogic.GenereateSavePrivateKeyPem();
My Code behind that class
public class CertificateLogic {
private readonly FileInfo CertificateFile;
private readonly SecureString CertificatePassword;
public X509Certificate2 Certificate { get; private set; }
public CertificateLogic(FileInfo certificationFile, string password) {
if (!certificationFile.Exists) {
throw new FileNotFoundException(certificationFile.FullName);
}
CertificateFile = certificationFile;
CertificatePassword = ConvertPassword(password);
}
public CertificateLogic(string certificationFullFileName, string password) {
var certificateFile = new FileInfo(certificationFullFileName);
if (certificateFile == null || !certificateFile.Exists) {
throw new FileNotFoundException(certificationFullFileName);
}
CertificateFile = certificateFile;
CertificatePassword = ConvertPassword(password);
}
private static SecureString ConvertPassword(string password) {
var secure = new SecureString();
foreach (char c in password) {
secure.AppendChar(c);
}
return secure;
}
public void LoadCertificate() {
LoadCertificate(X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
}
public void LoadCertificate(X509KeyStorageFlags keyStorageFlags) {
Certificate = new X509Certificate2(CertificateFile.FullName, CertificatePassword, keyStorageFlags);
}
public byte[] GenerateCertificatePem() {
var certData = Certificate.RawData;
var newPemData = PemEncoding.Write("CERTIFICATE", certData);
return newPemData.Select(c => (byte)c).ToArray();
}
public byte[] GeneratePrivateKeyPem() {
var privateCertKey = Certificate.GetRSAPrivateKey();
var privateCertKeyBytes = privateCertKey.ExportPkcs8PrivateKey();
char[] newPemData = PemEncoding.Write("PRIVATE KEY", privateCertKeyBytes);
return newPemData.Select(c => (byte)c).ToArray();
}
public FileInfo GenerateSaveCertificatePem() {
var newData = GenerateCertificatePem();
var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
var newCertPemFile = new FileInfo($#"{CertificateFile.DirectoryName}\{oldFile} Certificate.pem");
return SaveNewCertificate(newCertPemFile, newData);
}
public FileInfo GenereateSavePrivateKeyPem() {
var newData = GeneratePrivateKeyPem();
var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
var newPrivateKeyPemFile = new FileInfo($#"{CertificateFile.DirectoryName}\{oldFile} PrivateKey.pem");
return SaveNewCertificate(newPrivateKeyPemFile, newData);
}
public FileInfo SaveNewCertificate(FileInfo newFile, byte[] data) {
File.WriteAllBytes(newFile.FullName, data);
return newFile;
}
}
Related
Can anyone explain how to make a TDES MAC in OpenSSL command line?
I am trying to duplicate some functionality of a working C# program in C for the OpenSSL API, and am having trouble duplicating the .Net MACTripleDES.ComputeHash function in openssl. Here is an example with bogus data and key:
using (MACTripleDES hmac = new MACTripleDES(Utilities.HexStringToByteArray("112233445566778899aabbccddeeff00")))
{
// Compute the hash of the input file.
byte[] hashValue = hmac.ComputeHash(Utilities.HexStringToByteArray("001000000000000000000000000000008000000000000000"));
string signature = Utilities.ByteArrayToHexString(hashValue);
PrintToFeedback("Bogus Signature = " + signature);
}
The result is "Bogus Signature = A056D11063084B3E" My new C program has to provide the same hash of that data in order to interoperate with its wider environment. But the way to do this in openSSL eludes me. This shows that the openssl data starts out the same as the C# data:
cmd>od -tx1 bsigin
0000000 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000020 80 00 00 00 00 00 00 00
stringified, 001000000000000000000000000000008000000000000000 MATCHes the c# string.
cmd>openssl dgst -md5 -mac hmac -macopt hexkey:112233445566778899aabbccddeeff00 bsigin
HMAC-MD5(bsigin)= 7071d693451da3f2608531ee43c1bb8a
That data is too long, and my expected data is not a substring. Same for -sha1 etc. I tried encrypting and making the digest separately, no good. MS does not say what kind of hash it does, and I can't find documentation of how to set up a MAC with TDES in openssl.
So I'm hoping someone here knows enough about both platforms to give me a decent hint.
Command line answer:
cmd>openssl enc -des-ede-cbc -K 112233445566778899aabbccddeeff00 -iv 0000000000000000 -in bsigin -out bsigout
cmd>od -tx1 bsigout
0000000 7c de 93 c6 5f b4 03 21 aa c0 89 b8 ae f3 da 5d
0000020 a0 56 d1 10 63 08 4b 3e 4c 03 41 d6 dd 9e e4 32
^^^^^^^^^^^^^^^^^^^^^^^
That is, the command line form returns 32 bytes, and bytes 16..23 contain the hmac.
API answer:
DES_key_schedule SchKey1,SchKey2;
DES_cblock iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
DES_set_key((C_Block *)Key1, &SchKey1);
DES_set_key((C_Block *)Key2, &SchKey2);
DES_ede3_cbc_encrypt( (unsigned char*)input_data, (unsigned char*)cipher, inputLength, &SchKey1, &SchKey2, &SchKey1, &iv, DES_ENCRYPT);
Where Key1 is the Lkey or left 8 bytes of the 16 byte TDES key, and Key2 is the Rkey or right 8 bytes of the 16 byte TDES key. This call only populates 24 bytes of cipher, as opposed to the 32 byte return of the command line version. You still take bytes 16..23. Hopefully the supporting declarations are intuitive.
is there any convenient way to export private/public keys from .p12 certificate in PEM format using .NET Core? Without manipulating with bytes at low level? I googled for hours and almost nothing is usable in .net core or it isn't documented anywhere..
Let's have an X509Certificate2
var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far
And now I need to export the keys as two separate PEM keys. I already tried PemWriter in BouncyCastle but the types are not compatibile with System.Security.Cryptography from Core... no luck.
In other words, I'm finding a way how to write this:
$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts
Does anybody have an idea?
Thanks
Update (2021-01-12): For .NET 5 this is pretty easy. .NET Core 3.0 can even get most of the way there. The original answer was written when .NET Core 1.1 was the newest version of .NET Core. It explains what these new methods are doing under the covers.
.NET 5+:
byte[] certificateBytes = cert.RawData;
char[] certificatePem = PemEncoding.Write("CERTIFICATE", certificateBytes);
AsymmetricAlgorithm key = cert.GetRSAPrivateKey() ?? cert.GetECDsaPrivateKey();
byte[] pubKeyBytes = key.ExportSubjectPublicKeyInfo();
byte[] privKeyBytes = key.ExportPkcs8PrivateKey();
char[] pubKeyPem = PemEncoding.Write("PUBLIC KEY", pubKeyBytes);
char[] privKeyPem = PemEncoding.Write("PRIVATE KEY", privKeyBytes);
new string(char[]) can turn those char arrays into System.String instances, if desired.
For encrypted PKCS#8 it's still easy, but you have to make some choices for how to encrypt it:
byte[] encryptedPrivKeyBytes = key.ExportEncryptedPkcs8PrivateKey(
password,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 100_000));
.NET Core 3.0, .NET Core 3.1:
This is the same as the .NET 5 answer, except the PemEncoding class doesn't exist yet. But that's OK, there's a start for a PEM-ifier in the older answer (though "CERTIFICATE" and cert.RawData) would need to come from parameters).
.NET Core 3.0 was the release where the extra key format export and import methods were added.
.NET Core 2.0, .NET Core 2.1:
The same as the original answer, except you don't need to write a DER encoder. You can use the System.Formats.Asn1 NuGet package.
Original answer (.NET Core 1.1 was the newest option):
The answer is somewhere between "no" and "not really".
I'm going to assume that you don't want the p12 output gunk at the top of public.pub and private.key.
public.pub is just the certificate. The openssl commandline utility prefers PEM encoded data, so we'll write a PEM encoded certificate (note, this is a certificate, not a public key. It contains a public key, but isn't itself one):
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();
}
The private key is harder. Assuming the key is exportable (which, if you're on Windows or macOS, it isn't, because you didn't assert X509KeyStorageFlags.Exportable) you can get the parameters with privateKey.ExportParameters(true). But now you have to write that down.
An RSA private key gets written into a PEM encoded file whose tag is "RSA PRIVATE KEY" and whose payload is the ASN.1 (ITU-T X.680) RSAPrivateKey (PKCS#1 / RFC3447) structure, usually DER-encoded (ITU-T X.690) -- though since it isn't signed there's not a particular DER restriction, but many readers may be assuming DER.
Or, it can be a PKCS#8 (RFC 5208) PrivateKeyInfo (tag: "PRIVATE KEY"), or EncryptedPrivateKeyInfo (tag: "ENCRYPTED PRIVATE KEY"). Since EncryptedPrivateKeyInfo wraps PrivateKeyInfo, which encapsulates RSAPrivateKey, we'll just start there.
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
}
Now ignore the part about otherPrimeInfos. exponent1 is DP, exponent2 is DQ, and coefficient is InverseQ.
Let's work with a pre-published 384-bit RSA key.
RFC 3447 says we want Version=0. Everything else comes from the 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
Now we count up the number of bytes that went into the RSAPrivateKey structure. I count 0xF2 (242). Since that's bigger than 0x7F we need to use multi-byte length encoding: 81 F2.
So now with the byte array 30 81 F2 02 01 00 ... 9A 6F FD you could convert that to multi-line Base64 and wrap it in "RSA PRIVATE KEY" PEM armor. But maybe you want a PKCS#8.
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
So, let's do it again... The RFC says we want version=0 here, too. AlgorithmIdentifier can be found in 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]
Backfill the lengths:
The "b" series is 13 (0x0D), since it only contains things of pre-determined length.
The "a" series is now (2 + 1) + (2 + 13) + (3 + 0xF5) = 266 (0x010A).
30 82 01 0A 02 01 00 30 0D ...
Now you can PEM that as "PRIVATE KEY".
Encrypting it? That's a whole different ballgame.
I figured out a solution that works well. I could not find an EXACT example of how to go from certificate store to pem file in windows. Granted, this may not work for some certificates, but if you are working with one you have created yourself (for example, if you just need security between two machines you control that the end user won't see) this is a good way of going back to pem / pk (linux style).
I utilized the utilities found at 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();
}
X509certificate2 -> Private, Public and Cert pems...I just discovered that you can do it in 5 or 6 lines of layman's code!
There is a free package called Chilkat (It has some chill branding). It has some very intuitive Certificate Classes Here is some example code on how to create a self signed pfx formatted certificate and export it to PEM! So that is taking a X509Certificate2 instance with a certificate and associated public key and the privatekey that signed it, and then exporting it as three separate Pem files. One for the certificate(includes public key), one for the public key, and one for the private key. Very easy (took a week of reading to figure this out, haha). And then checkout https://github.com/patrickpr/YAOG for a nice OpenSSL windows Gui for viewing/creating certs (as seen in the result screenshot).
using Chilkat;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace CertPractice
{
static public class CertificateUtilityExample
{
public static X509Certificate2 GenerateSelfSignedCertificate()
{
string secp256r1Oid = "1.2.840.10045.3.1.7"; //oid for prime256v1(7) other identifier: secp256r1
string subjectName = "Self-Signed-Cert-Example";
var ecdsa = ECDsa.Create(ECCurve.CreateFromValue(secp256r1Oid));
var certRequest = new CertificateRequest($"CN={subjectName}", ecdsa, HashAlgorithmName.SHA256);
//add extensions to the request (just as an example)
//add keyUsage
certRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));
X509Certificate2 generatedCert = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddYears(10)); // generate the cert and sign!
//----------------end certificate generation, ie start here if you already have an X509Certificate2 instance----------------
X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); //has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request...
Chilkat.Cert chilkatVersionOfPfxGeneratedCert = new Chilkat.Cert(); // now use Chilcat Cert to get pems
chilkatVersionOfPfxGeneratedCert.LoadPfxData(generatedCert.Export(X509ContentType.Pfx), null); // export as binary pfx to load into a Chilkat Cert
PrivateKey privateKey = chilkatVersionOfPfxGeneratedCert.ExportPrivateKey(); // get the private key
privateKey.SavePemFile(#"filepath"); //save the private key to a pem file
Chilkat.PublicKey publicKey = chilkatVersionOfPfxGeneratedCert.ExportPublicKey(); //get the public key
publicKey.SavePemFile(true, #"filepath"); //save the public key
chilkatVersionOfPfxGeneratedCert.ExportCertPemFile(#"filepath"); //save the public Cert to pem file
return pfxGeneratedCert;
}
}
Based on #bartonjs knowledge (his answer), I have written a small class that should be easy to use.
So there is now also a complete example, without having to use external dlls/nuget packages
The only changes I had to make was:
I had to add this "X509KeyStorageFlags.Exportable" to the StorageFlags when creating the X509Certificate2 instance, so that the method "ExportPkcs8PrivateKey()" does not fail.
With my class it is possible to convert Let's Encrypt certificates from PFX format to PEM format with certificate & private key.
How to use my class
var certificateLogic = new CertificateLogic("fileName.pfx", "privateKeyOfPfx");
certificateLogic.LoadCertificate();
certificateLogic.GenerateSaveCertificatePem();
certificateLogic.GenereateSavePrivateKeyPem();
My Code behind that class
public class CertificateLogic {
private readonly FileInfo CertificateFile;
private readonly SecureString CertificatePassword;
public X509Certificate2 Certificate { get; private set; }
public CertificateLogic(FileInfo certificationFile, string password) {
if (!certificationFile.Exists) {
throw new FileNotFoundException(certificationFile.FullName);
}
CertificateFile = certificationFile;
CertificatePassword = ConvertPassword(password);
}
public CertificateLogic(string certificationFullFileName, string password) {
var certificateFile = new FileInfo(certificationFullFileName);
if (certificateFile == null || !certificateFile.Exists) {
throw new FileNotFoundException(certificationFullFileName);
}
CertificateFile = certificateFile;
CertificatePassword = ConvertPassword(password);
}
private static SecureString ConvertPassword(string password) {
var secure = new SecureString();
foreach (char c in password) {
secure.AppendChar(c);
}
return secure;
}
public void LoadCertificate() {
LoadCertificate(X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
}
public void LoadCertificate(X509KeyStorageFlags keyStorageFlags) {
Certificate = new X509Certificate2(CertificateFile.FullName, CertificatePassword, keyStorageFlags);
}
public byte[] GenerateCertificatePem() {
var certData = Certificate.RawData;
var newPemData = PemEncoding.Write("CERTIFICATE", certData);
return newPemData.Select(c => (byte)c).ToArray();
}
public byte[] GeneratePrivateKeyPem() {
var privateCertKey = Certificate.GetRSAPrivateKey();
var privateCertKeyBytes = privateCertKey.ExportPkcs8PrivateKey();
char[] newPemData = PemEncoding.Write("PRIVATE KEY", privateCertKeyBytes);
return newPemData.Select(c => (byte)c).ToArray();
}
public FileInfo GenerateSaveCertificatePem() {
var newData = GenerateCertificatePem();
var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
var newCertPemFile = new FileInfo($#"{CertificateFile.DirectoryName}\{oldFile} Certificate.pem");
return SaveNewCertificate(newCertPemFile, newData);
}
public FileInfo GenereateSavePrivateKeyPem() {
var newData = GeneratePrivateKeyPem();
var oldFile = Path.GetFileNameWithoutExtension(CertificateFile.FullName);
var newPrivateKeyPemFile = new FileInfo($#"{CertificateFile.DirectoryName}\{oldFile} PrivateKey.pem");
return SaveNewCertificate(newPrivateKeyPemFile, newData);
}
public FileInfo SaveNewCertificate(FileInfo newFile, byte[] data) {
File.WriteAllBytes(newFile.FullName, data);
return newFile;
}
}
I have a encryption tool to encrypt the file, when I study the encrypted file, found it is writing name of .PEM inside the encrypted file.
I found encryption logic is commonly used as below,
it supporting encryption of any file, it means RSA keys can not be use for encryption so here
it is creating a key(K) and encrypt it with RSA public key and then using key(K) for encrypting the file.
I write C# Code as below, it is fine but for big file am getting some junk character in middle like,
aaaaaaaaaaaaaaaa
??M'yaaaaaaaaaa?
my decryption code is like:-
System.Security.Cryptography.TripleDESCryptoServiceProvider tripleDES = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
tripleDES.Key = result; // 16 byte of key
tripleDES.Mode = System.Security.Cryptography.CipherMode.CBC;
byte[] IV = { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 };
tripleDES.IV = IV;
tripleDES.Padding = System.Security.Cryptography.PaddingMode.Zeros;
System.Security.Cryptography.ICryptoTransform cTransform = tripleDES.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(enc_data, 0, enc_data.Length);
//string s = Convert.ToBase64String(resultArray);
string x = System.Text.Encoding.ASCII.GetString(resultArray);
System.IO.File.WriteAllText(#"D:\570_f.txt", x);
tripleDES.Clear();
1) -code is working fine almost but somewhere I found 8 byte of junk character replacing real text. [ main problem]
.................okokokookokok8bytejunkokokokokokokookko..............8bytjunkokokokokokokokokokoko............
2) - don't know what padding scheme is using during encryption process, I tried decryption with zero padding mode.
----testing with different length file-----
(A)
input_file |encrypted_file with_tool | decrypted_file_with_above_code
10224 byte | x | 10232 byte
ok data + last 8 hex byte 3F 00 00 00 00 00 00 00
(b)
input_file |encrypted_file with_tool | decrypted_file_with_above_code
10242 byte | x | 10248 byte
ok data + last 8 hex byte 0D 3F 3F 3F 3C 56 31 65
(C)
input_file |encrypted_file with_tool | decrypted_file_with_above_code
10258 byte | x | 10264 byte
ok data + last 24 hex byte
0A 3F 3F 14 4D 27 79 0F 61 61 61 61 61 61 61 61
61 61 3F 00 00 00 00 00
NOTE - FILE CONTAIN only character a (HEX VALUE = 61)
any advice here would be great to hear
Finally found this encyption tool is taking input in the form of block of n byte. For n byte block there was no padding while any block less than nbyte is padded by 80 followed by 00 to make it multiple of 8.
I try to decrypt in same way, devide full file in form of n byte block and then decrypt each block and save output in a buffer,
and at last convert full buffer into string and paste in a file.
I have been given a task to encrypt data that will be stored in our database and sent to one of our customers. I figured the best way to do this is to use asymetric encryption so that once we have encrypted it with our customer's public key nobody within but the customer (owner of the private key) will be able to decrypt it.
I would like to store our customer's public key, algorithm type (RSA or DSA), and expiration date in our database instead of managing their certificate. The question is how can I store and use their public key? I have created the following little program to test and I am running into problems.
class Program
{
static void Main(string[] args)
{
const string publicKeyString = "30 81 89 02 81 81 00 c2 6e 7e e8 78 66 3d 74 fd a7 57 21 24 2d c0 ee 53 59 54 14 db f5 cb 5e 8c 64 c8 73 d5 83 d7 12 57 3f e2 92 54 9a 87 94 18 71 04 c8 b5 92 44 27 78 e9 d3 de cb 5f f6 93 75 c0 46 6b 50 c7 45 a8 38 f9 a1 83 8e 26 51 5a 8c 22 95 8e 2b 4c 10 ea c6 85 ed 02 ed 66 81 ef a3 55 15 ad 64 33 d3 bd ca 75 db 35 44 49 54 ef 6a ca 2a d5 90 a7 9b be 03 40 62 16 fd be 39 fb b6 f0 6b f8 f1 00 c0 c5 02 03 01 00 01";
const string stringToEncrypt = "11111111111111111111";
var encoding = new UTF8Encoding();
var encryptedData = Encrypt(encoding.GetBytes(stringToEncrypt), encoding.GetBytes(publicKeyString));
Console.WriteLine("**** Encrypted String ****");
Console.WriteLine(encoding.GetString(encryptedData));
var decryptedData = Decrypt(encryptedData);
Console.WriteLine("**** Decrypted String ****");
Console.WriteLine(encoding.GetString(decryptedData));
Console.ReadKey();
}
static byte[] Encrypt(byte[] dataToEncrypt, byte[] publicKey)
{
var exponent = new byte[] { 1, 0, 1 };
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(new RSAParameters() { Modulus = publicKey, Exponent = exponent });
var encryptedData = rsa.Encrypt(dataToEncrypt, false);
return encryptedData;
}
static byte[] Decrypt(byte[] dataToDecrypt)
{
var cert = new X509Certificate2(#"C:\certs\BP_DEV_CERT_1024.p12", "password");
var rsa = (RSACryptoServiceProvider) cert.PrivateKey;
var decryptedData = rsa.Decrypt(dataToDecrypt, false);
return decryptedData;
}
}
When I run this program I get "The data to be decrypted exceeds the maximum for this modulus of 128 bytes." This leads me to believe the way I am prepare
the public key to be used is totally wrong.
So I guess I need to know a couple of things:
I can copy the public key from the certificate but how should I store it in the database?
How should I properly covert the public key string to a proper byte array?
Any other pointers that someone may have.
RSA and other asymmetric algorithms are not suitable for encrypting data in bulk. The maximum message length is a few bytes less than the key modulus. Of course, you could form the data into blocks and apply RSA encryption repeatedly, but this is still horribly slow. Instead, RSA is used to exchange encryption keys for a symmetric cipher.
I recommend you use S/MIME to encrypt your customers' data. It's a standard that has been widely reviewed for security, and you probably already have a library to support the protocol. Most email clients support S/MIME, so your customers probably already have the software that they need.
S/MIME (and PGP) work by generating a key for a symmetric cipher like AES—the "content encryption key". This is used to encrypt the message. Then that symmetric key is encrypted with the public RSA key—the "key encryption key"—of each recipient. The encrypted content encryption key is sent along with the cipher text to each recipient.
I'm writing an app to get a better understanding of DKIM. The spec says I retrieve a "ASN.1 DER-encoded" public key from the domain TXT record. I can seen the key on "s1024._domainkey.yahoo.com" = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfm".
How can I use this key from .net? The examples I've seen get the key from a X509Certificate2, or an XML file containing the RSAParameters.
CORRECTION: I copy/pasted the key above from the network-tools.com DNS tool, which must've cut it short. nslookup gives me the full key:
s1024._domainkey.yahoo.com text =
"k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfm"
"JiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bTxhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj+XcwIDAQAB; n=A 1024 bit key;"
So abelenky was on the right track with BASE64..
This is the base64-encoding of the DER-encoding of an ASN.1 PublicKeyInfo containing an RSA public key.
Here is a translation:
0 30 159: SEQUENCE {
3 30 13: SEQUENCE {
5 06 9: OBJECT IDENTIFIER '1 2 840 113549 1 1 1'
16 05 0: NULL
: }
18 03 141: BIT STRING 0 unused bits, encapsulates {
22 30 137: SEQUENCE {
25 02 129: INTEGER
: 00 EB 11 E7 B4 46 2E 09 BB 3F 90 7E 25 98 BA 2F
: C4 F5 41 92 5D AB BF D8 FF 0B 8E 74 C3 F1 5E 14
: 9E 7F B6 14 06 55 18 4D E4 2F 6D DB CD EA 14 2D
: 8B F8 3D E9 5E 07 78 1F 98 98 83 24 E2 94 DC DB
: 39 2F 82 89 01 45 07 8C 5C 03 79 BB 74 34 FF AC
: 04 AD 15 29 E4 C0 4C BD 98 AF F4 B7 6D 3F F1 87
: 2F B5 C6 D8 F8 46 47 55 ED F5 71 4E 7E 7A 2D BE
: 2E 75 49 F0 BB 12 B8 57 96 F9 3D D3 8A 8F FF 97
: 73
157 02 3: INTEGER 65537
: }
: }
: }
The OBJECT IDENTIFIER indicates that the following BIT STRING contains the encoding of an RSAPublicKey. The INTEGERs are the modulus and the public exponent.
You can decode the base64 with Convert.FromBase64String, but I don't think .NET has built-in functionality for parsing PublicKeyInfos, so you need to use a 3rd party tool like BouncyCastle.
For anyone interested in this matter I would suggest the System.Security.Cryptography.X509Certificates.PublicKey which can be used to read a DER encoded public key.
That string looks like its some sort of base-64 encoding.
If you convert that string from base-64 to a BLOB, it should then be in valid ASN.1 format.
Try the bouncycastle library, it provides great functionality for such cases.