I have some problems with RSACryptoServiceProvider on my machine. If a create a new instance:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
I already get an exception, under CspKeyContainerInfo property :
Exportable 'rsa.CspKeyContainerInfo.Exportable' threw an exception of type 'System.Security.Cryptography.CryptographicException' bool {System.Security.Cryptography.CryptographicException}
base {"Key does not exist.\r\n"} System.SystemException {System.Security.Cryptography.CryptographicException}
If I try the same code on the another PC, everything works fine. Are there some settings on my PC that I can check to see if both of them are configured properly and in what the configuration defers? Any clue on what may be the problem?
Thanks
EDIT:
It seem's that it only happens on framework 4.0. Any clue?
I managed to make it work.
CspParameters parms = new CspParameters();
parms.Flags = CspProviderFlags.NoFlags;
parms.KeyContainerName = Guid.NewGuid().ToString().ToUpperInvariant();
parms.ProviderType = ((Environment.OSVersion.Version.Major > 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor >= 1))) ? 0x18 : 1;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parms);
I do not know why, but it is just like that.
If anyone know why of this behavior, and comment is more then welcome.
Thanks
You can get the provider type in the windows registry:
\local_machine\software\Microsoft\Cryptography\Defaults\Provider\
Look for the provider you want and check the Type value...
Related
I am trying to sign some data using a certificate private key. The issue I'm finding is that the signature is different depending on if I'm executing it locally or on a server.
I'm using the following code as a test, running under the same user both locally and on the server:
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace TestSignature
{
class Program
{
static void Main(string[] args)
{
var key = SigningKeyFromCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, "thumbprint");
var alg = CryptoConfig.MapNameToOID("SHA256");
var data = Encoding.UTF8.GetBytes("test");
var sig = key.SignData(data, alg);
Console.WriteLine(Convert.ToBase64String(sig));
}
private static RSACryptoServiceProvider SigningKeyFromCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findType, string findValue)
{
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(findType, findValue, false);
if (certs?.Count > 0)
{
var cert = certs[0];
if (cert.HasPrivateKey)
{
// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
var parameters = new CspParameters(enhanced.ProviderType, enhanced.ProviderName, key.CspKeyContainerInfo.UniqueKeyContainerName);
return new RSACryptoServiceProvider(parameters);
}
else
{
throw new Exception($"No private key access to cert '{findValue}.'");
}
}
else
{
throw new Exception($"Cert '{findValue}' not found!");
}
}
}
}
Locally, I get the following signature:
YUjspKhLl7v3u5VQkh1PfHytMTpEtbAftxOA5v4lmph3B4ssVlZp7KedO5NW9K5L222Kz9Ik9/55NirS0cNCz/cDhEFRtD4daJ9qLRuM8oD5hCj6Jt9Vc6WeS2he+Cqfoylnv4V9plfi1xw8y7EyAf4C77BGkXOdyP5wyz2Xubo=
On the server, I get this one instead:
u1RUDwbBlUpOgNNkAjXhYEWfVLGpMOa0vEfm6PUkB4y9PYBk1lDmCAp+488ta+ipbTdSDLM9btRqsQfZ7JlIn/dIBw9t5K63Y7dcDcc7gDLE1+umLJ7EincMcdwUv3YQ0zCvzc9RrP0jKJManV1ptQNnODpMktGYAq1KmJb9aTY=
Any idea of what could be different? I would think, with the same certificate, the same code, and the same data, the signature should be the same.
(The example is written in C# 4.5.2.)
You have some code to reopen the CAPI key handle under PROV_RSA_AES:
// Force use of Enhanced RSA and AES Cryptographic Provider to allow use of SHA256.
var key = cert.PrivateKey as RSACryptoServiceProvider;
var enhanced = new RSACryptoServiceProvider().CspKeyContainerInfo;
var parameters = new CspParameters(
enhanced.ProviderType,
enhanced.ProviderName,
key.CspKeyContainerInfo.UniqueKeyContainerName);
return new RSACryptoServiceProvider(parameters);
But key.CspKeyContainerInfo.UniqueKeyContainerName isn't the name of the key (it's the name of the file on disk where the key lives), so you're opening a brand new key (you're also generating a new ephemeral key just to ask what the default provider is). Since it's a named key it persists, and subsequent application executions resolve to the same key -- but a different "same" key on each computer.
A more stable way of reopening the key is
var cspParameters = new CspParameters
{
KeyContainerName = foo.CspKeyContainerInfo.KeyContainerName,
Flags = CspProviderFlags.UseExistingKey,
};
(since the provider type and name aren't specified they will use the defaults, and by saying UseExistingKey you get an exception if you reference a key that doesn't exist).
That said, the easiest fix is to stop using RSACryptoServiceProvider. .NET Framework 4.6 (and .NET Core 1.0) have a(n extension) method on X509Certificate2, GetRSAPrivateKey(), it returns an RSA (which you should avoid casting) which is usually RSACng (on Windows), but may be RSACryptoServiceProvider if only CAPI had a driver required for a HSM, and may be some other RSA in the future. Since RSACng handles SHA-2 better there's almost never a need to "reopen" the return object (even if it's RSACryptoServiceProvider, and even if the type isn't PROV_RSA_AES (24), that doesn't mean the HSM will fail to do SHA-2).
I'm trying to generate a JWT token using Jose.JWT.encode(payload, secretKey, JwsAlgorithm.ES256, header) (see https://github.com/dvsekhvalnov/jose-jwt) to use with Apple's new token-based APNs system.
The JWT encode method requires the secretKey to be in CngKey format.
Here's my code converting the .p8 file from Apple to a CngKey object:
var privateKeyContent = System.IO.File.ReadAllText(authKeyPath);
var privateKey = privateKeyContent.Split('\n')[1];
//convert the private key to CngKey object and generate JWT
var secretKeyFile = Convert.FromBase64String(privateKey);
var secretKey = CngKey.Import(secretKeyFile, CngKeyBlobFormat.Pkcs8PrivateBlob);
However, on the last line, the following error is thrown.
System.Security.Cryptography.CryptographicException was unhandled by user code
HResult=-2146885630
Message=An error occurred during encode or decode operation.
Source=System.Core
StackTrace:
at System.Security.Cryptography.NCryptNative.ImportKey(SafeNCryptProviderHandle provider, Byte[] keyBlob, String format)
at System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, String curveName, CngKeyBlobFormat format, CngProvider provider)
at System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, CngKeyBlobFormat format)
at tokenauthapi.App_Start.TokenInitSendMessage.<send>d__0.MoveNext() in C:\token-push-prototype\token-auth-api\token-auth-api\App_Start\TokenInitSendMessage.cs:line 31
InnerException:
The input isn't in the wrong format as there's a separate error for that (that appears when I change the blob type).
This code is running in a .NET WebApi v4.6.
I've searched high and low but haven't been able to decipher what this error is referring to. Any help would be greatly appreciated. Thank you.
The security key (p8) provided by Apple for DeviceCheck also contained newlines. I used the following to get a valid CngKey:
var privateKeyContent = File.ReadAllText("pathToApplePrivateKey.p8");
var privateKeyList = privateKeyContent.Split('\n').ToList();
var privateKey = privateKeyList.Where((s, i) => i != 0 && i != privateKeyList.Count - 1)
.Aggregate((agg, s) => agg + s);
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
Turns out the .p8 file I was using had newlines in the middle of it for some reason. Possible that notepad added it (and saved it?). I was splitting by newlines to get the private key and therefore it was truncating the key. Once I removed the newlines it worked fine.
If you get the error occurred during encode or decode operation error, check whether your .p8 (or other) private key is malformed and is the right length.
I met the same issue. I use this:
var privateKey = privateKeyContent.Split('\n')[1];
Then I analyze token file downloaded from Apple. I found there are more \n in the file. I am not sure where this format is different or apple changed.
Then I use the following codes to load the token, works.
Actually, we can directly use this token string.
var privateKeyContent = System.IO.File.ReadAllText(authKeyPath);
var privateKeyList = privateKeyContent.Split('\n');
int upperIndex = privateKeyList.Length;
StringBuilder sb = new StringBuilder();
for(int i= 1; i< upperIndex - 1; i++ )
{
sb.Append(privateKeyList[i]);
Debug.WriteLine(privateKeyList[i]);
}
I have used https://github.com/jrnker/CSharp-easy-RSA-PEM for RSA implementation in my mvc project.
It's working fine in my local machine in IIS & also via visual studio but when I deploy my application on server, it gives me below exception.
"System.NullReferenceException: Object reference not set to an instance of an object.\r\n at CoreEntities.Classes.Utility.RSADecrypt(String input)\r\n at WebAPI.Attributes.ApiAuthorizeAttribute.Authorize(HttpActionContext actionContext)"
My code is :
public static string RSADecrypt(string input)
{
try
{
string priv = File.ReadAllText(HostingEnvironment.MapPath("~/Certificates/consumer.pem"));
RSACryptoServiceProvider privc = Crypto.DecodeRsaPrivateKey(priv);
return Crypto.DecryptString(input, privc);
}
catch (Exception ex)
{
throw ex;
}
}
I posted my issue on github also # https://github.com/jrnker/CSharp-easy-RSA-PEM/issues/8
After debugging a lot, I figured out that system is not creating an object of RSACryptoServiceProvider
CspParameters parms = new CspParameters();
parms.Flags = CspProviderFlags.NoFlags;
parms.KeyContainerName = Guid.NewGuid().ToString().ToUpperInvariant();
parms.ProviderType = ((Environment.OSVersion.Version.Major > 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor >= 1))) ? 0x18 : 1;
// Exception is comping in below line.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parms);
RSAParameters rsAparams = new RSAParameters();
Exception:- System.Security.Cryptography.CryptographicException: The system cannot find the file specified.\r\n\r\n at CoreEntities.Classes.Utility.RSADecrypt(String input)\r\n at WebAPI.Attributes.ApiAuthorizeAttribute.Authorize(HttpActionContext actionContext)
Can anyone please help...
#Downvoters, Kindly pay attention.
I found solution to this problem.
This problem is mainly due to the new security constraints that were included into windows server 2008 onwards.
In windows server 2008 a new user with name CryptoGraphic Operator will be created by default.
If your application is using RSACryptoServiceProvider and when you decide to host your application on windows server 2008 IIS7 follow below steps
the account under which the respective application pool of the virtual directory that you create is running should be added in to CryptoGraphic Operator user.
Open IIS7 --> ApplicationPools --> YourAppPool -->RighClikck --> Advanced Settings ---> Load User Profile set this value to true.
This solved my problem.
Ref:- https://social.msdn.microsoft.com/Forums/vstudio/en-US/ec93922a-fd1e-4225-b5cf-1472ebb3acd1/systemsecuritycryptographycryptographicexception-the-system-cannot-find-the-file-specified?forum=netfxbcl
I solved the problem by changing the process Like below and no need to maniupulate the web server (IIS, NGINX or any other). I also tested that on linux, works fine.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xmlPrivateKey);
string toBeSigned= "string";
byte[] signMain = rsa.SignData(Encoding.UTF8.GetBytes(toBeSigned), new SHA1CryptoServiceProvider());
string signature = Convert.ToBase64String(signMain);
It's weird. I had this method to encrypt a string:
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Assert, Unrestricted = true)]
public static string Encrypt(this string stringToEncrypt, string key) {
var cspp = new CspParameters {
KeyContainerName = key,
Flags = CspProviderFlags.UseMachineKeyStore
};
var rsa = new RSACryptoServiceProvider(cspp) {
PersistKeyInCsp = true
};
var bytes = rsa.Encrypt(System.Text.Encoding.UTF8.GetBytes(stringToEncrypt), true);
return BitConverter.ToString(bytes);
}
And this was my client:
private const string EncryptionKey = "pezhman";
static Random random = new Random();
public static int CreateSalt() {
return random.Next(1000, 9999);
}
public void EncryptSomething() {
var salt = CreateSalt();
var plainText = salt + "," + DateTime.Now;
var encryptionSaltKey = EncryptionKey + DateTime.Now.Date;
// here im calling encryptor:
var encryptedValue = plainText.Encrypt(encryptionSaltKey);
}
I was using this in an ASP.NET MVC 4 application. It was working perfectly; but suddenly it stopped working. Actually, in local, I have no problem and it works. But, when I publish my code to the server, I get this error:
System.Security.Cryptography.CryptographicException: Object already
exists.
Do you have any idea what's happening here? I know I can grant access to the key to everyone. What I'm asking is, what just happened at the server? What is changed? What kind of changes can cause the problem?
What I'm asking is, what just happened at the server? What is changed? What kind of changes can cause the problem?
One possibility is the recently released Windows secuirty update MS14-059, although I can't explain the error message you are getting.
Basically, that update completely uninstalls MVC 4.0.0.0 and replaces it with 4.0.0.1 on your server, and it has caused grief for many people with broken builds. Since cryptography might depend on something very specific to the version number of the DLL, you might want to start there. You can prove or disprove this theory by testing your application on a machine without the above security patch installed to see if it starts working again.
I want to protect my RSA private key with a password (who wouldn't) but the following C# fails:
SecureString pw = new SecureString();
pw.AppendChar('x');
CspParameters prms = new CspParameters();
prms.KeyPassword = pw;
RSACryptoServiceProvider crypto = new RSACryptoServiceProvider(prms);
byte[] encrypted = crypto.Encrypt(Encoding.ASCII.GetBytes("encryptme"), true);
...with the CryptographicException: "Invalid type specified". If I take the KeyPassword assignment out it works fine.
What am I, or Microsoft, doing wrong?
Setting CspParameters.KeyPassword is equivalent to calling CryptSetProvParam with PP_KEYEXCHANGE_PIN (or PP_SIGNATURE_PIN). This flag is not supported by the default Microsoft crypto-service-provider (it is intended for use with smartcard-based CSPs).
You might want to try setting
prms.Flags = CspProviderFlags.UseUserProtectedKey;
or alternatively generating a non-persistent key-pair, exporting it and encrypting it with a key derived from a password yourself.