Windows stores certificate's private keys as files, and you can use mmc.exe to give users read permissions on these keys. I need a way to do that programatically in NET6.
Microsoft have marked the PrivateKey property on the X509Certificate class as obsolete (since .NET 4.6) and the correct way is to use the extension methods provided.
However, the returned RSA key class does not contain a UniqueName property which I can then use to determine the filename of the private key, and thus grant a user read permission on it.
This question Grant user permission to the private key shows how it can be achieved using the obsolete property name.
Does anyone know how this can be achieved without using the PrivateKey property?
I had some luck with this:
// input: "X509Certificate2 cert"
RSACng rsa = cert.GetRSAPrivateKey() as RSACng;
string rsaKeyName = rsa.Key.UniqueName;
if (rsaKeyName == null)
{
RSACryptoServiceProvider rsaCSP = cert.GetRSAPrivateKey() as RSACryptoServiceProvider;
rsaKeyName = rsaCSP.CspKeyContainerInfo.KeyContainerName;
}
Related
I am making a Windows Service which at certain points in its execution requires logging into a server using a username and password so I have been faced with the security issue of where to store these login details.
Initially I was going to use the Windows Credential Manager, but due to the user access of Windows Services (I need to use the Local System account) it was not the best option. I have settled on storing an encryption key by using an RSACryptoServiceProvider, like so:
using rsa = new RSACryptoServiceProvider(new CspParameters
{
Flags = CspProviderFlags.UseMachineKeyStore,
KeyContainerName = "UniqueKeyId"
});
which I can later use to decrypt the usernames and passwords I store in their encrypted, base64 forms, like so:
"MyLoginInfo":
{
"Username" : "LettWH4VMbvEG/OUBXGLluyceEG8Gon1fOytQ0IoKys18KSCfz6lc8fVO6XxBJtnSzjR23OAoE9TxG9lIqSRdpDwGGaTLGa3bpTGxzKlq+3OoLRo4Hf+9VEn/GO/UEZnzAmalPLErQO87krPmJuWCDqTthtPmi2Kh9jbcavz7Ss=",
"Password" : "Pnq3KoPip2WHpnDQivc8b0VOMzFn0W/OtSIVSELSE8SNqJSiRHa/6Yt47ndpyZRe6hTSvz3RZeLxaeQ+X1QIm1VRxESzbgz3ZFFzxy6F2ZJAikygWBhzNnu3jywG6u1C7amN7IO9/dHu2T6Jw8n6U1MTYgN2bV4lmGNHJ2bwAnA="
}
So my question is, what is to stop this from being called by some external process:
using rsa = new RSACryptoServiceProvider(new CspParameters
{
Flags = CspProviderFlags.UseMachineKeyStore,
KeyContainerName = "UniqueKeyId"
});
rsa.PersistKeyInCsp = false;
rsa.Clear();
And my key being eradicated, making decryption impossible at a later time? I'm not sure it would likely ever happen but the possibility is bothersome. Should I make my key container name extremely unique, like a GUID or hash of some kind? No one would be able to "browse" through all the available keys, would they?
I have 2 approaches to do the same thing, but Azure has deprecated the one that works, and the other method doesn't work.
The approach that works, but is deprecated:
I store my PFX in Azure Key Vault Secrets. (when I create the secret I see a warning stating that this feature is deprecated)
and use the following code to retrieve it to create my certificate:
SecretBundle secret = await keyVaultClient.GetSecretAsync(keyVaultUrl, "MyCert-Secret");
X509Certificate2Collection exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(secret.Value));
X509Certificate2 certFromSecret = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);
credits to this answer
I'm able to use this certificate to host and access my application successfully.
The approach that doesn't work, but I should be using:
I store my certificate in the Azure Key vault Certificates
and use the following code to retrieve it and create the X509Certificate2:
CertificateBundle certificateBundle = await keyVaultClient.GetCertificateAsync(keyVaultUrl, "MyCert-Certificate");
X509Certificate2 certFromCertificate = new X509Certificate2(certificateBundle.Cer);
The problem with this approach is that the certificate does not contain the private key. i.e. certFromCertificate.HasPrivateKey is false.
My Questions
Why does certFromSecret have the PrivateKey, while certFromCertificate doesn't?
How can I retrieve a certificate from the key vault, where I can create a X509Certificate2 object to host my application in Kestrel with UseHttps.
The 2nd part of #Adrian's answer explains the concepts around the Azure KV Certificates very well, and I have changed my code as below to get the full certificate including the private keys:
SecretBundle secret = await kv.GetSecretAsync(keyVaultUrl, certName);
X509Certificate2 certificate =
new X509Certificate2(Convert.FromBase64String(secret.Value));
The trick was to use GetSecretAsync instead of GetCertificateAsync. Please refer to Adrian's SO answer to see why the secret had to be used to get the full certificate with Private keys.
Note that you should use "Certificate identifier" property (url with "/secrets/") from Azure certificate's property page.
The latest version of the SDK (Azure.Security.KeyVault.Certificates 4.2.0) now has the DownloadCertificateAsync method, which obtains the full cert (i.e. private key too) in a straightforward way.
The documentation states:
Because Cer contains only the public key, this method attempts to
download the managed secret that contains the full certificate.
X509Certificate2 cert = await certificateClient.DownloadCertificateAsync(certName);
Sometimes ago I've read an article about using asymmetric keys like public and private keys to send data securely. What I've understood was that the server has 1 key (private key) that it use to encrypt data and all clients use the second key (public key) to decrypt it.
Now how should I expect to receive the key and how should I work with?
If I receive a Certificate from the server, wouldn't it contain both public and private keys?!
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
var cert = GetCertificate(certStore);
var privatekey= cert.PrivateKey;
var publicKey= cert.PublicKey;
Is it possbile to remove the private key from the certificate? How? and how can I understand if the certificate has the private key?
First a little bit of clarification:
In Public-key cryptography the public key is used to encrypt the data and the private key is used (by the server) to decrypt the data.
The owner of the private key stores private key and only shares a public key.
Any certificate from a server should contain only a public key.
(It would be a big security issue if the certificate contains the private Key. You could decrypt the messages from any other user)
To check if the cerificate has a private key you can use the HasPrivateKey-Property
cert.HasPrivateKey;
And to get a certificate with only the public key you can use:
byte[] bytes = cert.Export(X509ContentType.Cert);
var publicCert = new X509Certificate2(bytes);
If I receive a Certificate from the server, wouldn't it contain both public and private keys?!
No, not necessarily.
There are two different ways of obtaining the certificate
PKCS#7/PEM - the file usually contains only the public part of the certificate
PKCS#12/PFX - the store contains certificates with private keys
Is it possbile to remove the private key from the certificate?
By exporting it to a format that lets you store only the public part of the certificate.
Open a web browser, navigate to any site that uses SSL.
Now click the lock icon and, from there, the certificate information. What you have at the client side (in the browser) is the certificate without the private key. You can save it and even import into the system cert store but still without the private key.
and how can I understand if the certificate has the private key?
If you load it with your C# code, accessing the HasPrivateKey property will be true only for certs with private keys available.
I am trying to figure out a way of authentication between two distributed services.
I don't want to have a shared secret distributed on every service host, because it would mean that once one host has been compromised, all hosts are compromised.
So my scenario is:
Host A knows the public key of Host B
Host A encodes and encryptes the jwt using Host B´s public key
Host B receives and decrypts the jwt using its private key, that it only knows itself.
The jose-jwt package:
https://github.com/dvsekhvalnov/jose-jwt
seems like a good option to me. Beside the signing of the jwt, it also supports encryption using private/public keys.
On the page there are the following examples for encoding and decoding a jwt:
Encode:
var publicKey=new X509Certificate2("my-key.p12", "password").PublicKey.Key as RSACryptoServiceProvider;
string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
Decode:
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string json = Jose.JWT.Decode(token,privateKey);
Now, here is what i don´t understand:
How can I create a .p12 certificate file that only contains the public key information (for the host/service A that encodes the jwt) ?
.. and how can I create a .p12 certificate file that contains both, the public and the private key information (for the host/service B that decodes the jwt) ?
From all the research that I have done, i get the impression that you can either only make a .p12 file that contains both, or one that contains only the public key. But it seems there is no way to create two .p12 files, one with both information and one with only the public key. What am I missing?
Thanks for your answers.
Normally a PKCS12/PFX is not used for public-only, but you can do it if you like.
Assuming that cert.HasPrivateKey is true: cert.Export(X509ContentType.Pkcs12, somePassword) will produce a byte[] that you can write to "publicAndPrivate.p12" (or whatever).
Normally for a public-only certificate you'll write it down just as the X.509 data, either DER-binary or PEM-DER encoded. .NET doesn't make PEM-DER easy, so we'll stick with DER-binary. You can get that data by either cert.RawData, or cert.Export(X509ContentType.Cert) (both will produce identical results, since this export form has no random data in it). (publicOnly.cer)
If you really want a PKCS12 blob which has just the public certificate:
using (X509Certificate2 publicOnly = new X509Certificate2(publicPrivate.RawData))
{
return publicOnly.Export(X509ContentType.Pkcs12, somePassword);
}
The resulting byte[] could then be publicOnly.p12.
I need to store "password like information" in a database field. I would like it to be encrypted but I need to decrypt it before using it. So I can not use a Hash/Salt solution.
Granted if an attacker made it that far into the database it may be too far gone but I figure this would at least stop the mistaken dump of the data.
How to encrypt a value store it into the database and decrypt the same value for use later?
Hashing is not an option (I use it on other parts actually).
Where to store the private key? Users would not supply anything.
This a C# solution so .NET specific stuff would be great. My question is very similar but I am looking for a .net based solution: Two-way encryption: I need to store passwords that can be retrieved
EDIT:
Hogan pretty much answered my question. I found examples out there and they ranged from very complicated to rather simple. It looks like AES is still good so I will be using that method. thank you for all your help.
One solution that does not involve private keys is using DPAPI.
You can use it from .NET via the ProtectedData class.
Here is an example:
public void Test()
{
var password = "somepassword";
var encrypted_password = EncryptPassword(password);
var decrypted_password = DecryptPassword(encrypted_password);
}
public string EncryptPassword(string password)
{
var data = Encoding.UTF8.GetBytes(password);
var encrypted_data = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encrypted_data);
}
public string DecryptPassword(string encrypted_password)
{
var encrypted_data = Convert.FromBase64String(encrypted_password);
var data = ProtectedData.Unprotect(encrypted_data, null, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(data);
}
Please note that DPAPI in this case depends on the current logged in user account. If you encrypt the password when your application is running as User1, then you can only decrypt the password running under the same user account. Please note that if you change the windows password for User1 in an incorrect way, then you will lose the ability to decrypt the password. See this question for details.
If you don't want use DPAPI, and prefer to have a private key. Then the best place to store such private key is in the user's key store. However, in order to store a private key in the local user store, you need to have a certificate for it. You can create a self signed certificate and store it with its corresponding private key into the local user certificate store.
You can access the user store in code using the X509Store class. You can use it to find the certificate (which is in C# a X509Certificate2 class) that you want to use and then use it to do encryption/decryption.
See this and this for more details.