I want load the PEM using .net framework (not .netcore)
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHs........................................................CAAw
DAYI........................................................gZAf
Y/Iu........................................................X7DZ
ZKoE........................................................OYQQ
3ZST........................................................A2E=
-----END ENCRYPTED PRIVATE KEY-----
I tried to use the following code using BouncyCastle, but it throw PemException:
"problem creating ENCRYPTED private key: Org.BouncyCastle.Crypto.InvalidCipherTextException: pad block corrupted"
class Passowrd : IPasswordFinder
{
private string v;
public Passowrd(string v)
{
this.v = v;
}
public char[] GetPassword()
{
return v.ToCharArray();
}
}
var pemReader = new PemReader(new StringReader(privateKeyText), new Passowrd("PASSWORD"));
var pemObj = pemReader.ReadObject(); // this line throw PemException
However, I load the exact same PEM file using .netcore3.1 by the following code:
var ecdsa = ECDsa.Create();
ecdsa.ImportEncryptedPkcs8PrivateKey(passSpan, privateKeyBytes, out _);
You can read the PEM either using
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(new StringReader(privateKeyText), new Passowrd("PASSWORD"));
or
var pemReader = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(new StringReader(privateKeyText));
then read content like
var pemObject = pemReader.ReadPemObject();
The problem seems to be related to the encryption of the key: The key cannot be read with the PemReader if the option -v2prf hmacWithSHA1 is not set when the key is encrypted.
The option -v2prf exists since V1.1.0 and specifies the PRF (pseudo-random function) algorithm used with PKCS#5 v2.0 or the key derivation function PBKDF2.
Case 1: Creating a new key
The following two statements create and encrypt the key:
openssl ecparam -name secp256r1 -genkey -noout -out <out-path>
openssl pkcs8 -topk8 -v2 aes256 -v2prf hmacWithSHA1 -in <in-path> -out <out-path> // aes256 as of OpenSSL 1.1.0 inclusive
A key generated in this way can be successfully read with the PemReader. However, if the option -v2prf hmacWithSHA1 is missing, the exception is thrown.
Case 2: Fixing an already generated key
The following statement decrypts an already encrypted key:
openssl pkcs8 -topk8 -nocrypt -in <in-path> -out <out-path>
If the key is subsequently encrypted with the -v2prf hmacWithSHA1 option set (see above), the key can be read successfully with the PemReader. This way I was able to fix the posted key so that it could be read with the PemReader!
A possible (unfortunately only superficial) explanation can be found in the documentation of openssl pkcs8. There it can be read about the -v2prf option:
Some implementations may not support custom PRF algorithms and may require the hmacWithSHA1 option to work.
Interestingly, the critical keys can be read without problems using ECDsa#ImportEncryptedPkcs8PrivateKey from .NET Core 3.x. For a complete explanation a look into the source code would probably be necessary.
Related
I really don't understand how certificates work and can't find much information on how to create a X509Certificate2 object in .NET Framework. I need the certificate object so that I can add it to an Http handler. I have three items: a cert.pem file, a key.pem file and a password that I provided when I created the files. The files where generated from running this command:
> "C:\Program Files\Git\usr\bin\openssl.exe" req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
The console then asked me to provide a "PEM pass phrase" and I entered it. Now I was able to create a X509Certificate2 object in .NET 6, but cannot find a way to do the same in .NET Framework.
This code works in .NET 6, but I have no idea how to do the same thing in .NET Framework 4.6.1.
public static X509Certificate2 CreateCertificate(string publicCert, string privateCert, string password = #"P#ssw0rd1")
{
byte[] publicPemBytes = Encoding.UTF8.GetBytes(publicCert);
using var publicX509 = new X509Certificate2(publicPemBytes);
var privateKeyBlocks = privateCert.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
RSA rsa = RSA.Create();
if (privateKeyBlocks[0] == "BEGIN ENCRYPTED PRIVATE KEY")
{
rsa.ImportFromEncryptedPem(privateCert, password);
}
X509Certificate2 keyPair = publicX509.CopyWithPrivateKey(rsa);
return keyPair;
}
The variable publicCert has the raw value of the cert.pem file (the one that start with -----BEGIN CERTIFICATE-----. The variable privateCert has the raw value of the key.pem file (the one that start with -----BEGIN ENCRYPTED PRIVATE KEY-----.
I am able to load an unencrypted PEM file with RSA.ImportFromPem() with no issues, but I am not able to load a PEM file when it's password encrypted.
Here is the code I am attempting to use.
var pwRSA = RSA.Create();
pwRSA.ImportFromEncryptedPem(pwPemKey, "password1");
I am getting the following exception:
HResult=0x80070057
Message=No supported key formats were found. Check that the input represents the contents of a PEM-encoded key file, not the path to such a file. (Parameter 'input')
Source=System.Security.Cryptography.Algorithms
StackTrace:
at Internal.Cryptography.PemKeyImportHelpers.ImportEncryptedPem[TPass](ReadOnlySpan`1 input, ReadOnlySpan`1 password, ImportEncryptedKeyAction`1 importAction)
at System.Security.Cryptography.RSA.ImportFromEncryptedPem(ReadOnlySpan`1 input, ReadOnlySpan`1 password)
at RSAEncryptionTest.Program.Main(String[] args) in C:\source\tests\RSAEncryptionTest\Program.cs:line 57
Given this is just a test, I don't mind sharing the private key.
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,F7922A7F105CC716E9A19297BAC0BE49
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
-----END RSA PRIVATE KEY-----
Any help is greatly appreciated.
As mentioned by Topaco above, the key is to convert the private key into PKCS#8 format. Since no one else included it here, here's the OpenSSL command I used to accomplish this:
openssl pkcs8 -inform PEM -topk8 -in private_key.pem -out private_key2.pem
After doing this I was able to import private_key2.pem using ImportFromEncryptedPem
For the communication between a c# dotnet-core application (windows and linux) and Chef.io API I need to be able to create the same signature within c# dotnet core as this call of openssl executable:
openssl rsautl -in encryption.txt -sign -inkey chefprivatekey.pem | openssl enc -base64
The first implementation (dotnet framework) was based on this
https://github.com/mattberther/dotnet-chef-api:
byte[] input = Encoding.UTF8.GetBytes(canonicalHeader);
var pemReader = new PemReader(new StringReader(privateKey));
AsymmetricKeyParameter key = ((AsymmetricCipherKeyPair)pemReader.ReadObject()).Private;
ISigner signer = new RsaDigestSigner(new NullDigest());
signer.Init(true, key);
signer.BlockUpdate(input, 0, input.Length);
_signature = Convert.ToBase64String(signer.GenerateSignature());
The nuget package dotnet-chef-api contains also an old version of BouncyCastle.Crypto.dll. As long as I use this dll everyting works fine but if I do an update of the dll to a newer verion by using a nuget-package for this, nothing is working any more. As a workaround I have Implemented a system-call of openssl executable to get the correct signature for the API. This works fine. But I need to get rid of the system-calls.
What I have done now is to compare the system call result of openssl exe with the results I get from any signing algorythms like BouncyCastle lib. I have not been able to get the same result as the above mentioned call of openssl-exe. The implementation needs to be dotnet-core for Windows and Linux.
As long as the the results between openssl exe and any c# implementation are not the same I don't neet to test the chef-api.
I was able to create System.Security.Cryptography.RSACryptoServiceProvider from the pem file folloging this example: How to load the RSA public key from file in C#.
with this Code I am alble to get an object of type RSACryptoServiceProvider from the pem file.
RSACryptoServiceProvider provider = PemKeyUtils.GetRSAProviderFromPemFile(pemFile);
var trial1 = Convert.ToBase64String(provider.SignData(Convert.FromBase64String("test"), new SHA1CryptoServiceProvider()));
The content of "trial1" is the same as "_signature" but not the same as from openssl.exe
What I am doing wrong?
"openssl rsautl" by default uses PKCS#1 v1.5 could this be a problem?
Found a solution on my own. I had a look into the Unittests of BouncyCastle and got now this as a working solition:
public string Sign(byte[] input, string privateKeyPath)
{
asymmetricKeyParameter AsymmetricKeyParameterFromPrivateKeyInPemFile
// Sign the hash
IBufferedCipher c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
c.Init(true, asymmetricKeyParameter);
var outBytes = c.DoFinal(input);
return Convert.ToBase64String(outBytes);
}
public AsymmetricKeyParameter AsymmetricKeyParameterFromPrivateKeyInPemFile(string privateKeyPath)
{
using (TextReader privateKeyTextReader = new StringReader(File.ReadAllText(privateKeyPath)))
{
PemReader pr = new PemReader(privateKeyTextReader);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
return keyPair.Private;
}
}
I am using:
Windows 10 (Version 1709, OS Build 17025.1000)
.net framework 4.7
VS 2017 (version: 15.3.5)
Here is what I did:
Got a self signed ECC certificate using OpenSSL and steps outlined in the script at https://gist.github.com/sidshetye/4759690 with modifications:
a) Used NIST/P-256 curve over a 256 bit prime field
b) Used SHA-256
Load the certificate from file (generated in previous step) into X509Certificate2 object
Imported the PFX file into windows trust store (for testing). This is successful.
Inspection of the imported certificate shows Public Key field as 'ECC (256 Bits)' and Public key parameters as 'ECDSA_P256'.
Next tried to figure out how to encrypt with this certificate.
I am stuck at the last step because all the examples that use X509Certificate2 object predominantly use only RSA and I am using ECC certificate. For RSA certificate, there is a GetRSAPublicKey extention method on X509Certificate2 and RSA class has Encrypt method. However there is no such method for ECC certificates.
Next, I stumbled on this post (Load a Certificate Using X509Certificate2 with ECC Public Key) and tried following (even though it appeared bizarre as to why ECC cert public key is being coerced into RSA type):
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key
I got following exception: The certificate key algorithm is not supported.
Next I stumbled on this post (Importing ECC-based certificate from the Windows Certificate Store into CngKey) which basically tried to create CNGKey type and instantiate ECDsaCng with it. However even if I can do it with ECDiffieHellmanCng, there is no Encrypt method on it.
So I am not really sure how can I proceed further to use ECC X509 certificate's public key to encrypt data.
###Background
Asymmetric algorithms have three different purposes (that I know of)
Encryption
RSA is the only "standard" algorithm that can do this directly.
Signature
RSA
DSA
ECDSA
ElGamal Signature
Key Agreement
Diffie-Hellman (DH)
ECDH
ElGamal encryption (the asymmetric startup phase)
MQV
ECMQV
Because RSA encryption is space limited, and was hard for computers in the '90s, RSA encryption's primary use was in "Key Transfer", which is to say that the "encrypted message" was just the symmetric encryption key for DES/3DES (AES not yet having been invented) - https://www.rfc-editor.org/rfc/rfc2313#section-8.
Key agreement (or transfer) schemes always have to be combined with a protocol/scheme to result in an encryption operation. Such schemes include
TLS (nee SSL)
CMS or S/MIME encrypted-data
IES (Integrated Encryption Scheme)
ECIES (Elliptic Curve Integrated Encryption Scheme)
ElGamal encryption (holistically)
PGP encryption
So what you probably want is ECIES.
ECIES.Net
Currently (.NET Framework 4.7.1, .NET Core 2.0) there's no support to get an ECDiffieHellman object from a certificate in .NET.
Game over, right? Well, probably not. Unless a certificate carrying an ECDH key explicitly uses the id-ecDH algorithm identifier (vs the more standard id-ecc one) it can be opened as ECDSA. Then, you can coerce that object into being ECDH:
using (ECDsa ecdsa = cert.GetECDsaPublicKey())
{
return ECDiffieHellman.Create(ecdsa.ExportParameters(false));
}
(a similar thing can be done for a private key, if the key is exportable, otherwise complex things are required, but you shouldn't need it)
Let's go ahead and carve off the recipient public object:
ECDiffieHellmanPublicKey recipientPublic = GetECDHFromCertificate(cert).PublicKey;
ECCurve curve = recipientPublic.ExportParameters().Curve;
So now we turn to http://www.secg.org/sec1-v2.pdf section 5.1 (Elliptic Curve Integrated Encryption Scheme)
###Setup
Choose ANSI-X9.63-KDF with SHA-2-256 as the hash function.
Choose HMAC–SHA-256–256.
Choose AES–256 in CBC mode.
Choose Elliptic Curve Diffie-Hellman Primitive.
You already chose secp256r1.
Hard-coded. Done.
Point compression's annoying, choose not to use it.
I'm omitting SharedInfo. That probably makes me a bad person.
Not using XOR, N/A.
###Encrypt
Make an ephemeral key on the right curve.
ECDiffieHellman ephem = ECDiffieHellman.Create(curve);
We decided no.
ECParameters ephemPublicParams = ephem.ExportParameters(false);
int pointLen = ephemPublicParams.Q.X.Length;
byte[] rBar = new byte[pointLen * 2 + 1];
rBar[0] = 0x04;
Buffer.BlockCopy(ephemPublicParams.Q.X, 0, rBar, 1, pointLen);
Buffer.BlockCopy(ephemPublicParams.Q.Y, 0, rBar, 1 + pointLen, pointLen);
Can't directly do this, moving on.
Can't directly do this, moving on.
Since we're in control here, we'll just do 3, 4, 5, and 6 as one thing.
KDF time.
// This is why we picked AES 256, HMAC-SHA-2-256(-256) and SHA-2-256,
// the KDF is dead simple.
byte[] ek = ephem.DeriveKeyFromHash(
recipientPublic,
HashAlgorithmName.SHA256,
null,
new byte[] { 0, 0, 0, 1 });
byte[] mk = ephem.DeriveKeyFromHash(
recipientPublic,
HashAlgorithmName.SHA256,
null,
new byte[] { 0, 0, 0, 2 });
Encrypt stuff.
byte[] em;
// ECIES uses AES with the all zero IV. Since the key is never reused,
// there's not risk in that.
using (Aes aes = Aes.Create())
using (ICryptoTransform encryptor = aes.CreateEncryptor(ek, new byte[16]))
{
if (!encryptor.CanTransformMultipleBlocks)
{
throw new InvalidOperationException();
}
em = encryptor.TransformFinalBlock(message, 0, message.Length);
}
MAC it
byte[] d;
using (HMAC hmac = new HMACSHA256(mk))
{
d = hmac.ComputeHash(em);
}
Finish
// Either
return Tuple.Create(rBar, em, d);
// Or
return rBar.Concat(em).Concat(d).ToArray();
###Decrypt
Left as an exercise to the reader.
For getting ECDiffieHellman private key from certificate, use the following method:
Install NuGet package Security.Cryptography (CLR Security). (The package is under MIT license.)
Use the following extension method to get the CngKey instance:
CngKey cngKey = certificate.GetCngPrivateKey();
(Note: The extension method certificate.GetECDsaPrivateKey(), natively supported in .NET, returns an ECDsaCng instance; there is no extension method to return ECDiffieHellmanCng.)
The cngKey instance can be used to create either an ECDsaCng or an ECDiffieHellmanCng instance:
var sa = new ECDsaCng(cngKey);
var sa = new ECDiffieHellmanCng(cngKey);
I have created a self-signed-certificate (pfx) for testing purposes.
I'm able to export ist public key via :
X509Certificate2 cer = new X509Certificate2();
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificateCollection = store.Certificates.Find(...);
var cert = certificateCollection[0] ;
Console.WriteLine(Convert.ToBase64String(cert.Export( X509ContentType.Cert ), Base64FormattingOptions.InsertLineBreaks));
Result :
MIIDFTCCAf2gAw...........eFUpBB9C0/UNRmD7EAg==
This is consistent with openssl command :
$ openssl pkcs12 -in domain.name.pfx -clcerts -nokeys -out domain.name.crt
Result :
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAw........9C0/UNRmD7EAg==
-----END CERTIFICATE-----
However , when I export the private key via C# :
Convert.ToBase64String(cert.Export( X509ContentType.Pfx ), Base64FormattingOptions.InsertLineBreaks)
I get :
MIIDFTCCAf2gAw............OVeFUpBB9C0/UNRmD7EAg==
While with openssl command :
$ openssl pkcs12 -in domain.name.pfx -nocerts -nodes -out domain.name.key
I get :
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w.........6HNjF2h7uuFdvbH2VAVg=
-----END PRIVATE KEY-----
Question:
Why do I get different results in the private keys ? And how can I fix my C# code to yield the same result as openssl's result ?
(NB - I'm not talking about -----begin/end keys----- boundries , but the actual value.
Additional info , the PFX file
The X509ContentType.Cert representation is the same because the certificate is a static structure. If you export it in a loop you'll always get the same answer.
The X509ContentType.Pfx representation is different because there are random salts in the PFX structure. One for each certificate, one for each private key, and one final one. If you export the same public+private pair as a PFX in a loop it will be different every time (288 bits of random data).
The end of the base64 data is different because it contains the last salt and the MAC over the rest of the data (including the encrypted cert salt and the encrypted key salt). It also contains an integer work factor, which Windows chooses as 2000 and OpenSSL chooses as 2048; further differentiating the OpenSSL and Windows exports.
So what you are seeing is by design. If you need a stable export you'll have to do something else. If you were just concerned that they're different, it's almost certainly fine.
Why do I get different results in the private keys ?
You are trying to do two separate things. With openssl you are using pfx file that contains private key and certificate(s). In c# you are using some representation of certificate with private key and you are doing export to a pfx file!
And how can I fix my C# code to yield the same result as openssl's result ?
C# has no friendly function that could export only private key (in openssl-like way) from X509Certificate2. As #darksquirell42 mentioned in comments you could use X509Certificate2.PrivateKey property, then cast it to RSACryptoServiceProvider and do ToXmlString(true). But this will give you private key parameters in a xml structure. Then you would have to take each parameter and construct an ASN.1 structure PKCS#1 or (IMHO better) PKCS#8. But C# has by default no friendly methods to work with ASN.1 objects.
OR you could use some crypto library to do it for you like BouncyCastle. This method from BouncyCastle could be helpful.