I'm trying to decrypt a EnvelopedCms that was encrypted using a non-default AlgorithmIdentifier like this:
ContentInfo contentInfo = new ContentInfo(data);
EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, new AlgorithmIdentifier(new System.Security.Cryptography.Oid("2.16.840.1.101.3.4.1.42")));
CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, certificates);
envelopedCms.Encrypt(recipients);
byte[] encryptedData = envelopedCms.Encode();
The encryption works as expected. Now when I try to decrypt the envelopedCms using something like this:
EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedData );
envelopedCms.Decrypt(certificates);
byte[] decryptedData = envelopedCms.ContentInfo.Content;
I notice that a.) the access to the certificate takes quite long (longer then when using the default AlgorithmIdentifier) and b.) I get this error message:
System.Security.Cryptography.CryptographicException: Access was denied because of a security violation.
Which, looking at the source where this fails, is probably not the issue. Can anyone get the decrypt code above working [with a smartcard]?
//EDIT1
Please note that this issue only occures if the certificate used is placed on a smartcard AND if a AlgorithmIdentifier other then the default one (3DES) was specified, as in the example code. Everything works fine if either the default AlgorithmIdentifier is used or the certificate is NOT placed on a smartcard. It doesn't seem like a SC issue per se, since it's working with the default AlgorithmIdentifier. It's rather the combination of a SC and the AES AlgorithmIdentifier used that's causing the issue but I was unable to find a working solution.
//EDIT2
A complete example demonstrating the issue, read comments for details:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Security.Cryptography.Pkcs;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
// Select the (smartcard) certificate to use it for encryption
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificate Select", "Select your smartcard certificate", X509SelectionFlag.MultiSelection);
// Output which certificate will be used
Console.WriteLine("Using Certificate:");
int i = 0;
foreach (X509Certificate2 x509 in scollection)
{
byte[] rawdata = x509.RawData;
Console.WriteLine("---------------------------------------------------------------------");
Console.WriteLine("1.\tFull DN: {0}", x509.Subject);
Console.WriteLine("\tThumbprint: {0}", x509.Thumbprint);
Console.WriteLine("---------------------------------------------------------------------");
i++;
}
store.Close();
// Wait
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
// Create data for encryption
string message = "THIS IS OUR SECRET MESSAGE";
byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Encrypt
Console.WriteLine("Encrypting message...");
// ContentInfo contentInfo = new ContentInfo(data); // will use default ContentInfo Oid, which is "DATA"
// Explicitly use ContentInfo Oid 1.2.840.113549.1.7.1, "DATA", which is the default.
ContentInfo contentInfo = new ContentInfo(new System.Security.Cryptography.Oid("1.2.840.113549.1.7.1"), data);
// If using OID 1.2.840.113549.3.7 (the default one used if empty constructor is used) or 1.2.840.113549.1.9.16.3.6 everything works
// If using OID 2.16.840.1.101.3.4.1.42 (AES CBC) it breaks
AlgorithmIdentifier encryptionAlgorithm = new AlgorithmIdentifier(new System.Security.Cryptography.Oid("1.2.840.113549.3.7"));
// EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // this will use default encryption algorithm (3DES)
EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, encryptionAlgorithm);
Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName);
Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.Value);
CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, scollection);
/*Console.WriteLine("Receipientinfo count: " + encryptionEnvelopedCms.RecipientInfos.Count.ToString());
foreach (var i in encryptionEnvelopedCms.RecipientInfos)
{
Console.Write("RecipientInfo Encryption Oid: " + i.KeyEncryptionAlgorithm.Oid);
}
*/
envelopedCms.Encrypt(recipients);
byte[] encryptedData = envelopedCms.Encode();
Console.WriteLine("Message encrypted!");
// Decrypt
envelopedCms.Decode(encryptedData);
Console.WriteLine("Decryption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName);
Console.WriteLine("Decryption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.Value);
// Next line will fail if both conditions are true:
// 1. A non-default AlgorithmIdentifier was used for encryption, in our case AES
// 2. The private key required for decryption is placed on a smartcard that requires a manual action, such as entering a PIN code, before releasing the private key
// Note that everything works just fine when the default AlgorithmIdentifier is used (3DES) or the private key is available in the X509Store
envelopedCms.Decrypt(scollection);
byte[] decryptedData = envelopedCms.ContentInfo.Content;
Console.WriteLine("Message decrypted!");
Console.WriteLine("Decrypted message: " + System.Text.Encoding.ASCII.GetString(decryptedData));
Console.WriteLine("Press any key to exit.");
Console.ReadKey(true);
}
}
}
Although my answer may lead to some incomplete tangents, I believe that it will get you the same assertion that I have come to. The fact is that I use a X509Store allows me to locate the certificates that my machine has. I then pass the collection into the CmsReceipientCollection with a X509Certificate2Collection that is found from my store.Certificates. This method takes 128ms to execute. HTH!
[TestMethod]
public void TestEnvelopedCMS()
{
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
byte[] data = new byte[256];
//lets change data before we encrypt
data[2] = 1;
ContentInfo contentInfo = new ContentInfo(data);
EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, new AlgorithmIdentifier(new System.Security.Cryptography.Oid("2.16.840.1.101.3.4.1.42")));
CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, fcollection);
envelopedCms.Encrypt(recipients);
byte[] encryptedData = envelopedCms.Encode();
//lets decrypt now
envelopedCms.Decode(encryptedData);
envelopedCms.Decrypt(fcollection);
byte[] decryptedData = envelopedCms.ContentInfo.Content;
//grab index from byte[]
var item = decryptedData.Skip(2).Take(1).FirstOrDefault();
var item2 = data.Skip(2).Take(1).FirstOrDefault();
Assert.IsTrue(item == item2);
}
Okay so finally I found the reason for why this is not working. It's really dependent on the SC I'm using (Yubikey 4). In my case I created my RSA keys using openssl and then transfered them to the SC using the official Yubico PIV Manager / PIV Tool. This seems to be not supported yet with the official SC driver from Yubico (YubiKey Smart Card Minidriver (YKMD)). The official driver seems however to be the only one that supports all the advanced features of the Yubikey and currently it seems to be required if you want to use AES as encryption algorithm. I was using the OpenSC driver before that will work just fine for 3DES but will fail for more advanced functions. Thus, if someone runs into this issue with the Yubikey:
make sure you're using the official driver (YubiKey Smart Card Minidriver (YKMD)) instead of the Windows base driver or the OpenSC driver
For the official driver to work you have to import your certificates using certutil on Windows, like shown in this article.
If you get a error along the line "NTE_BAD_KEYSET" while trying to import using certutil this is probably because you initialized the PIV function using the Yubico tools (PIV tool and/or PIV manager). This is not supported as well in this case, thus, you'll have to reset your Yubikey PIV config first (basically enter the wrong PIN x times, then the wrong PUK x times and then you can reset the PIV config -all this is done using the PIV tool from Yubico as shown here at the bottom of the page)
Now you can set your custom PIN, PUK, Management-Key and so on using the Yubico tools. It seems as "only" the init of the PIV config is not allowed to be done with this tools. Also note that you'll find more details like "how to set the touch policy" (turned off by default, which kinda su***) in the SC deployment guide from Yubico.
Related
I have a service that needs to encrypt and decrypt a connection text. I don't want to hard-code the password, so I've decided to use a certificate, but I want to be able to add it to the store the first time the settings UI (a separate configuration app) is opened.
I have code that checks for an existing certificate, and adds a new one if the certificate is not found.
private static void GenerateNewSecurityCertificate()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var request = new CertificateRequest($"cn={CertificateName}", ecdsa, HashAlgorithmName.SHA512);
var cert = request.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(50));
File.WriteAllBytes("c:\\temp\\EncryptionCert.pfx", cert.Export(X509ContentType.Pfx, _certificatePassword));
}
private static void AddCertificateToStore()
{
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
using (var cert = new X509Certificate2("c:\\temp\\EncryptionCert.pfx", _certificatePassword,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet))
{
store.Add(cert);
}
}
}
I run into the issue when I try to retrieve the public key
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
using (var cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateName, false)[0])
{
var publicKey = cert.GetRSAPublicKey();
encryptedKey = publicKey.Encrypt(aesKey.Key, RSAEncryptionPadding.OaepSHA512);
}
}
I get a null returned when I call cert.GetRSAPublicKey(). Does anyone see what I might be doing wrong?
Update:
I updated GenerateNewSecurityCertificate to read
var rsa = RSA.Create();
var request = new CertificateRequest($"cn={CertificateName}", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
I am able to get the public key now, but get an Exception reading
"The parameter is incorrect"
at System.Security.Cryptography.NCryptNative.EncryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptEncryptor`1 encryptor)
on my call to publicKey.Encrypt.
Since you apparently encrypt an AES key, it can be assumed that the (maximum) length of your data is 32 bytes (AES-256). The error message can be reproduced, e.g. if the message is too large for the key used.
For RSA, the length of a message must not exceed the key length. In fact, it is even smaller, because a part of the allowed length is reserved for padding. For OAEP this part depends on the digest used. For SHA512 the reserved part (130 bytes) is so large that a 1024 bits (128 bytes) key is not sufficient, with a 2048 bits (256 bytes) key the message may still be 126 bytes large, see here.
You create an RSA key with
var rsa = RSA.Create();
which uses a default value for the key length, s. RSA.Create(). This default value also depends on the .NET version. Under .NET Framework 4.8 a 1024 bits key is created on my machine (nowadays too short), under .NET Core 3.1 a 2048 bits key is created. You can check the key length with rsa.KeySize.
Probably a key that is too short for your purposes is generated in your environment. It is always better not to rely on defaults, but to specify the values explicitly. The Create method has an overload that makes this possible, see RSA.Create(Int32). You should use this overload and create a key of (at least 2048 bits).
Alternatively, the bug could theoretically be eliminated with another digest (e.g. SHA256). Independent of this, a key with 1024 bits should not be used nowadays (12/2020) for security reasons, the length should be at least 2048 bits (see here).
I was following this great tutorial on digitally signing/verifying data with .NET. I modified that example code to use SHA256 and hit the "Invalid algorithm specified" exception, which led me to this SO question about signing data with SHA256 in .NET 4.0.
One of the answers from that post helped me figure out how to properly generate the digital signature by explicitly loading a SHA256-capable crypto provider without relying on an exportable private key (see the code towards the bottom of the following method where the RSACryptoServiceProvider is constructed):
static string mKeyContainerName;
static byte[] SignText(string text, string publicCertPath)
{
// Access Personal (MY) certificate store of current user
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// Load the certificate we'll use to verify the signature from a file.
X509Certificate2 publicCert = new X509Certificate2(publicCertPath);
publicCert.Verify();
string publicHash = publicCert.GetCertHashString();
// Find the certificate we'll use to sign
X509Certificate2 privateCert = null;
foreach(X509Certificate2 cert in store.Certificates)
{
if(cert.GetCertHashString() == publicHash)
{
// We found it. Get its associated private key
privateCert = cert;
break;
}
}
store.Close();
if(privateCert == null)
{
throw new Exception("No valid private cert was found");
}
// Hash the string
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
SHA256Managed sha256 = new SHA256Managed();
byte[] hash = sha256.ComputeHash(data);
// The basic crypto provider only supports SHA-1.
// Force Enhanced RSA and AES Cryptographic Provider which supports SHA-256.
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
var cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}
It may be worth noting that I am using a self-signed cert from makecert.exe. According to another answer in that same post, these problems wouldn't occur if I included the proper -sp or -sy flags in makercert.exe. However, even after specifying either of these flags (currently using -sy 24) I still have to perform the workaround.
This implementation differs slightly from the accepted answer in that post (again, as our private key is not exportable). But that answer does indicate that the verification can be done without explicitly loading a SHA256-capable crypto provider. Thus, I should have been able to do this before returning in the method above:
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
mKeyContainerName = csp.CspKeyContainerInfo.KeyContainerName;
var cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
throw new CryptographicException();
return signature;
However, this won't verify (BTW, I've tried both SignData/VerifyData and SignHash/VerifyHash). The only way I can get it to verify is if I again explicitly load a SHA256-capable crypto provider. Unfortunately, the KeyContainerName member of the CspKeyContainerInfo constructed from a public certificate is always null. Thus, the only way I can get the data to verify is if I cache (or hard-code) the private key's KeyContainerName. Hence the reason for the mKeyContainerName field in the above method and snippet below:
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
cspparams = new CspParameters
(
enhCsp.ProviderType, enhCsp.ProviderName, mKeyContainerName
);
csp = new RSACryptoServiceProvider(cspparams);
if (!csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature))
throw new CryptographicException();
This does verify, but I don't like having to hard-code the private key's KeyContainerName. The private key won't be available on the machine that is doing the verification.
Does anyone know of a better way to accomplish this? Thanks!
I stumbled on my own answer quite by accident. Turns out using a self-signed cert created with makecert.exe is the culprit here. If I use a cert created with OpenSSL or a commercial cert, I no longer have to explicitly load a SHA256-capable crypto provider. Thus I don't have to hard code the container name in the CspParameters object used to instantiate the RSACryptoServiceProvider. This signing code now works as expected:
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)privateCert.PrivateKey;
// Sign the hash
byte[] signature = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
The same is true on the verify side:
// Test to verify the signed hash with public cert
csp = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
I never found out what was different about the cert generated by makecert.exe, but it was not important for me as we are signing with a commercial cert now.
I have a task at hand that requires deriving key material using the key derivation function described in NIST SP 800-56A, section 5.8.1. I'm not an expert in Cryptography so please excuse me if the question is naive. Here's what I've done so far:
I have the other party's public key and my private key
Now I try to generate the shared secret using ECDH 1.3.132.1.12 using C# (.NET 4) ECDiffieHellmanCng class like so:
// The GetCngKey method reads the private key from a certificate in my Personal certificate store
CngKey cngPrivateKey = GetCngKey();
ECDiffieHellmanCng ecDiffieHellmanCng = new ECDiffieHellmanCng(cngPrivateKey);
ecDiffieHellmanCng.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256;
ecDiffieHellmanCng.KeyDerivationFunction = ?? // What do I set here
Finally do this:
ecDiffieHellmanCng.DeriveKeyMaterial(otherPartyPublicKey:);
Where/how do I set the other parameters Algorithm ID, Party U Info, Party V Info?
EDIT
I am open to using other libraries like Bouncy Castle (provided they can be called from .NET)
TL;DR; I haven't found a way to derive the symmetric key using KDF described in NIST SP 800-56A, section 5.8.1 using built-in classes in .NET 4.0 alone
The good news (for me :-)) is that it IS possible in .NET 4.0 using the lovely BouncyCastle library (NuGet: Install-Package BouncyCastle-Ext -Version "1.7.0"). Here's how:
STEP 1: Get other party's public key
Depending on your scenario, this may be read from a certificate or come to you as part of the message containing the encrypted data. Once you have the Base64 encoded public-key, read it into a Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters object like so:
var publicKeyBytes = Convert.FromBase64String(base64PubKeyStr);
ECPublicKeyParameters otherPartyPublicKey = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
STEP 2: Read your private-key
This would most-commonly involve reading the private key from a PFX/P12 certificate. The windows account running the code should have access to the PFX/P12 and additionally, if the certificate is imported into a certificate store, you'll need to grant permissions via the All Tasks -> manage private key menu in certmgr.msc
using (StreamReader reader = new StreamReader(path))
{
var fs = reader.BaseStream;
string password = "<password for the PFX>";
Pkcs12Store store = new Pkcs12Store(fs, passWord.ToCharArray());
foreach (string n in store.Aliases)
{
if (store.IsKeyEntry(n))
{
AsymmetricKeyEntry asymmetricKey = store.GetKey(n);
if (asymmetricKey.Key.IsPrivate)
{
ECPrivateKeyParameters privateKey = asymmetricKey.Key as ECPrivateKeyParameters;
}
}
}
}
STEP 3: Compute the shared secret
IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
aKeyAgree.Init(privateKey);
BigInteger sharedSecret = aKeyAgree.CalculateAgreement(otherPartyPublicKey);
byte[] sharedSecretBytes = sharedSecret.ToByteArray();
STEP 4: Prepare information required to compute symmetric key:
byte[] algorithmId = Encoding.ASCII.GetBytes(("<prependString/Hex>" + "id-aes256-GCM"));
byte[] partyUInfo = Encoding.ASCII.GetBytes("<as-per-agreement>");
byte[] partyVInfo = <as-per-agreement>;
MemoryStream stream = new MemoryStream(algorithmId.Length + partyUInfo.Length + partyVInfo.Length);
var sr = new BinaryWriter(stream);
sr.Write(algorithmId);
sr.Flush();
sr.Write(partyUInfo);
sr.Flush();
sr.Write(partyVInfo);
sr.Flush();
stream.Position = 0;
byte[] keyCalculationInfo = stream.GetBuffer();
STEP 5: Derive the symmetric key
// NOTE: Use the digest/Hash function as per your agreement with the other party
IDigest digest = new Sha256Digest();
byte[] symmetricKey = new byte[digest.GetDigestSize()];
digest.Update((byte)(1 >> 24));
digest.Update((byte)(1 >> 16));
digest.Update((byte)(1 >> 8));
digest.Update((byte)1);
digest.BlockUpdate(sharedSecret, 0, sharedSecret.Length);
digest.BlockUpdate(keyCalculationInfo, 0, keyCalculationInfo.Length);
digest.DoFinal(symmetricKey, 0);
Now you have the symmetric key ready to do the decryption. To perform decryption using AES, BouncyCastle IWrapper can be used. Obtain an IWrapper using Org.BouncyCastle.Security.WrapperUtilities by calling WrapperUtilities.GetWrapper("AES//") e.g. "AES/CBC/PKCS7". This will also depend on the agreement between the two communicating parties.
Initialize the cipher (IWrapper) with symmetric key and initialization vector (IV) and call the Unwrap method to get plain-text bytes. Finally, convert to string literal using the character encoding used (e.g. UTF8/ASCII/Unicode)
I am staring at this for quite a while and thanks to the MSDN documentation I cannot really figure out what's going. Basically I am loading a PFX file from the disc into a X509Certificate2 and trying to encrypt a string using the public key and decrypt using the private key.
Why am I puzzled: the encryption/decryption works when I pass the reference to the RSACryptoServiceProvider itself:
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
But if the export and pass around the RSAParameter:
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
...it throws a "Key not valid for use in specified state." exception while trying to export the private key to RSAParameter. Please note that the cert the PFX is generated from is marked exportable (i.e. I used the pe flag while creating the cert). Any idea what is causing the exception?
static void Main(string[] args)
{
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
x.FriendlyName = "My test Cert";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
try
{
store.Add(x);
}
finally
{
store.Close();
}
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
}
private static byte[] EncryptRSA(string data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
publicKey.ImportParameters(rsaParameters);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
RSACryptoServiceProvider privateKey = new RSACryptoServiceProvider();
privateKey.ImportParameters(rsaParameters);
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
private static byte[] EncryptRSA(string data, RSACryptoServiceProvider publicKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSACryptoServiceProvider privateKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
Just to clarify in the code above the bold part is throwing:
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider)**.ExportParameters(true)**);
I believe that the issue may be that the key is not marked as exportable. There is another constructor for X509Certificate2 that takes an X509KeyStorageFlags enum. Try replacing the line:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
With this:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test", X509KeyStorageFlags.Exportable);
For the issue I encountered a code change was not an option as the same library was installed and working elsewhere.
Iridium's answer lead me to look making the key exportable and I was able to this as part of the MMC Certificate Import Wizard.
Hope this helps someone else. Thanks heaps
I've met some similar issue, and X509KeyStorageFlags.Exportable solved my problem.
I'm not exactly an expert in these things, but I did a quick google, and found this:
http://social.msdn.microsoft.com/Forums/en/clr/thread/4e3ada0a-bcaf-4c67-bdef-a6b15f5bfdce
"if you have more than 245 bytes in your byte array that you pass to your RSACryptoServiceProvider.Encrypt(byte[] rgb, bool fOAEP) method then it will throw an exception."
For others that end up here through Google, but don't use any X509Certificate2, if you call ToXmlString on RSACryptoServiceProvider but you've only loaded a public key, you will get this message as well. The fix is this (note the last line):
var rsaAlg = new RSACryptoServiceProvider();
rsaAlg.ImportParameters(rsaParameters);
var xml = rsaAlg.ToXmlString(!rsaAlg.PublicOnly);
AFAIK this should work and you're likely hitting a bug/some limitations. Here's some questions that may help you figure out where's the issue.
How did you create the PKCS#12 (PFX) file ? I've seen some keys that CryptoAPI does not like (uncommon RSA parameters). Can you use another tool (just to be sure) ?
Can you export the PrivateKey instance to XML, e.g. ToXmlString(true), then load (import) it back this way ?
Old versions of the framework had some issues when importing a key that was a different size than the current instance (default to 1024 bits). What's the size of your RSA public key in your certificate ?
Also note that this is not how you should encrypt data using RSA. The size of the raw encryption is limited wrt the public key being used. Looping over this limit would only give you really bad performance.
The trick is to use a symmetric algorithm (like AES) with a totally random key and then encrypt this key (wrap) using the RSA public key. You can find C# code to do so in my old blog entry on the subject.
Old post, but maybe can help someone.
If you are using a self signed certificate and make the login with a different user, you have to delete the old certificate from storage and then recreate it. I've had the same issue with opc ua software
I need to send confidential data to a server over a TCP connection. I have done a lot of researching and I understand the theoretical part. Based on what I have researched I want to do the following:
Note there is a server and a client: (we assume that public keys of either the client or server can be obtain by anyone)
client creates his public and private key. He is able to encrypt with his private key and decrypt with his public key.
server creates his public and private keys. private key is used to decrypt messages and public key is used to encrypt messages. (note is the other way around as with the client)
the client get's the server's public key. client then will be able to encrypt messages with that key and the only one that will be able to decrypt that message would be the server's private key.
since the server needs to be certain that the message comes from that specific client then the client will encrypt his name (signature) with his private key.
so the client message will contain: data to be send, client's public key, client name encrypted with the client's private key.
the client will encrypt the message with the public key from the server. client will then send that message to the server.
the server will decrypt the message it just received with his private key.
once the message is decrypted it will contain the data (info), encrypted signature, public key from client.
finally, the server will decrypt the client signature with the public key that was contained on the message to verify that the message is from that client.
OK so this is how asymmetric cryptography works. I have also researched about the classes that enable you to create this key pairs with the .NET framework. The classes that I researched that enable you do create this public and private key pairs are:
System.Security.Cryptography.DES
System.Security.Cryptography.DSACryptoServiceProvider
System.Security.Cryptography.ECDsa
System.Security.Cryptography.ECDsaCng
System.Security.Cryptography.ECDiffieHellman
System.Security.Cryptography.ECDiffieHellmanCng
System.Security.Cryptography.RSA
System.Security.Cryptography.RSACryptoServiceProvider
so now my problems comes on how do I use one of this classes to do it with C#? I understand how the theoretical part works but how do I do what I just described with code. I have researched for some examples but I am having a hard time understanding them.
here is one example that I found that I believe does what I described:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Example
{
class Program
{
static CngKey aliceKey;
static CngKey bobKey;
static byte[] alicePubKeyBlob;
static byte[] bobPubKeyBlob;
static void Main()
{
CreateKeys();
byte[] encrytpedData = AliceSendsData("secret message");
BobReceivesData(encrytpedData);
Console.Read();
}
private static void CreateKeys()
{
aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
}
private static byte[] AliceSendsData(string message)
{
Console.WriteLine("Alice sends message: {0}", message);
byte[] rawData = Encoding.UTF8.GetBytes(message);
byte[] encryptedData = null;
using (var aliceAlgorithm = new ECDiffieHellmanCng(aliceKey))
using (CngKey bobPubKey = CngKey.Import(bobPubKeyBlob,
CngKeyBlobFormat.EccPublicBlob))
{
byte[] symmKey = aliceAlgorithm.DeriveKeyMaterial(bobPubKey);
Console.WriteLine("Alice creates this symmetric key with " +
"Bobs public key information: {0}",
Convert.ToBase64String(symmKey));
using (var aes = new AesCryptoServiceProvider())
{
aes.Key = symmKey;
aes.GenerateIV();
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (MemoryStream ms = new MemoryStream())
{
// create CryptoStream and encrypt data to send
var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
// write initialization vector not encrypted
ms.Write(aes.IV, 0, aes.IV.Length);
cs.Write(rawData, 0, rawData.Length);
cs.Close();
encryptedData = ms.ToArray();
}
aes.Clear();
}
}
Console.WriteLine("Alice: message is encrypted: {0}",
Convert.ToBase64String(encryptedData)); ;
Console.WriteLine();
return encryptedData;
}
private static void BobReceivesData(byte[] encryptedData)
{
Console.WriteLine("Bob receives encrypted data");
byte[] rawData = null;
var aes = new AesCryptoServiceProvider();
int nBytes = aes.BlockSize >> 3;
byte[] iv = new byte[nBytes];
for (int i = 0; i < iv.Length; i++)
iv[i] = encryptedData[i];
using (var bobAlgorithm = new ECDiffieHellmanCng(bobKey))
using (CngKey alicePubKey = CngKey.Import(alicePubKeyBlob,
CngKeyBlobFormat.EccPublicBlob))
{
byte[] symmKey = bobAlgorithm.DeriveKeyMaterial(alicePubKey);
Console.WriteLine("Bob creates this symmetric key with " +
"Alices public key information: {0}",
Convert.ToBase64String(symmKey));
aes.Key = symmKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor())
using (MemoryStream ms = new MemoryStream())
{
var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
cs.Write(encryptedData, nBytes, encryptedData.Length - nBytes);
cs.Close();
rawData = ms.ToArray();
Console.WriteLine("Bob decrypts message to: {0}",
Encoding.UTF8.GetString(rawData));
}
aes.Clear();
}
}
}
}
In this program I believe the client is Alice and the server is Bob. I have to split this program into two parts. I am having a hard time understanding it and if I give it a try most likely I will make it work. Anyways how can I split this program into a server side code and client side code. I know how to send bytes between server and client. But I don't want to make it work without understanding what is going on. maybe you guys can show me an easier example.
EDIT
I managed to separate the code: here is the server code (the ip address of my computer happened to be 192.168.0.120) :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;
namespace ServerListener
{
class Program
{
static TcpListener server;
//static CngKey aliceKey;
static CngKey bobKey;
static byte[] alicePubKeyBlob;
static byte[] bobPubKeyBlob;
static void Main(string[] args)
{
CreateKeys();
IPAddress ipAddress = IPAddress.Parse("192.168.0.120");
server = new TcpListener(ipAddress, 54540);
server.Start();
var client = server.AcceptTcpClient();
var stream = client.GetStream();
alicePubKeyBlob = new byte[bobPubKeyBlob.Length];
stream.Read(alicePubKeyBlob, 0, alicePubKeyBlob.Length);
stream.Write(bobPubKeyBlob, 0, bobPubKeyBlob.Length);
byte[] encrytpedData = new byte[32];
stream.Read(encrytpedData, 0, encrytpedData.Length);
BobReceivesData(encrytpedData);
}
private static void CreateKeys()
{
//aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
//alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
}
private static void BobReceivesData(byte[] encryptedData)
{
Console.WriteLine("Bob receives encrypted data");
byte[] rawData = null;
var aes = new AesCryptoServiceProvider();
int nBytes = aes.BlockSize >> 3;
byte[] iv = new byte[nBytes];
for (int i = 0; i < iv.Length; i++)
iv[i] = encryptedData[i];
using (var bobAlgorithm = new ECDiffieHellmanCng(bobKey))
using (CngKey alicePubKey = CngKey.Import(alicePubKeyBlob,
CngKeyBlobFormat.EccPublicBlob))
{
byte[] symmKey = bobAlgorithm.DeriveKeyMaterial(alicePubKey);
Console.WriteLine("Bob creates this symmetric key with " +
"Alices public key information: {0}",
Convert.ToBase64String(symmKey));
aes.Key = symmKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor())
using (MemoryStream ms = new MemoryStream())
{
var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
cs.Write(encryptedData, nBytes, encryptedData.Length - nBytes);
cs.Close();
rawData = ms.ToArray();
Console.WriteLine("Bob decrypts message to: {0}",
Encoding.UTF8.GetString(rawData));
}
aes.Clear();
}
}
}
}
and here is the client code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;
namespace ClientAlice
{
class Program
{
static CngKey aliceKey;
//static CngKey bobKey;
static byte[] alicePubKeyBlob;
static byte[] bobPubKeyBlob;
static void Main(string[] args)
{
CreateKeys();
bobPubKeyBlob = new byte[alicePubKeyBlob.Length];
TcpClient alice = new TcpClient("192.168.0.120", 54540);
var stream = alice.GetStream();
stream.Write(alicePubKeyBlob, 0, alicePubKeyBlob.Length);
stream.Read(bobPubKeyBlob, 0, bobPubKeyBlob.Length);
byte[] encrytpedData = AliceSendsData(":)");
stream.Write(encrytpedData, 0, encrytpedData.Length);
}
private static void CreateKeys()
{
aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
//bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
//bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
}
private static byte[] AliceSendsData(string message)
{
Console.WriteLine("Alice sends message: {0}", message);
byte[] rawData = Encoding.UTF8.GetBytes(message);
byte[] encryptedData = null;
using (var aliceAlgorithm = new ECDiffieHellmanCng(aliceKey))
using (CngKey bobPubKey = CngKey.Import(bobPubKeyBlob,
CngKeyBlobFormat.EccPublicBlob))
{
byte[] symmKey = aliceAlgorithm.DeriveKeyMaterial(bobPubKey);
Console.WriteLine("Alice creates this symmetric key with " +
"Bobs public key information: {0}",
Convert.ToBase64String(symmKey));
using (var aes = new AesCryptoServiceProvider())
{
aes.Key = symmKey;
aes.GenerateIV();
using (ICryptoTransform encryptor = aes.CreateEncryptor())
using (MemoryStream ms = new MemoryStream())
{
// create CryptoStream and encrypt data to send
var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
// write initialization vector not encrypted
ms.Write(aes.IV, 0, aes.IV.Length);
cs.Write(rawData, 0, rawData.Length);
cs.Close();
encryptedData = ms.ToArray();
}
aes.Clear();
}
}
Console.WriteLine("Alice: message is encrypted: {0}",
Convert.ToBase64String(encryptedData)); ;
Console.WriteLine();
return encryptedData;
}
}
}
I thinks it is pretty secure. Every time it sends a different byte array although sending the same info!
As you note, you are a beginner at crypto. If this is a fun toy project to learn about crypto, great. If this is real production code you are going to implement it insecurely. You should be using off-the-shelf tools like SSL/HTTPS/whatever to solve this problem rather than doing it wrong yourself.
I'll take this opportunity to point out areas where your sketch is fatally weak.
3) the client get's the server's public key.
OK. How? This is the most important step. The security of the entire system relies upon this step, and you have completely glossed over how it works. How does the client obtain the public key of the server? What stops an evil person from calling up the client and saying "hey client, I'm the server. Here's my public key!" And now the client is encrypting messages that can only be decrypted by the evildoer. The evildoer has the real server's public key, so the evildoer re-encrypts the message with the real public key and sends it on. Your whole system is thereby compromised. The public key cryptosystem is only secure if there is a secure key exchange mechanism. (And a reasonable question then is: if you have a secure key exchange mechanism, why not simply use it to exchange the message in the first place?)
4) since the server needs to be certain that the message comes from that specific client then the client will encrypt his name (signature) with his private key.
The client should encrypt a hash of the entire message as the signature, not just a part of the message. That way the server has evidence that the whole message was from the client.
6) the client will encrypt the message with the public key from the server. client will then send that message to the server.
This is extremely inefficient. Better is for the server and client to agree upon a key to a symmetric cryptosystem. The key can be transmitted between the server and the client using the public key cryptosystem. The server and client now have a shared secret key that they can use for this communication session.
9) lastly, the server will decrypt the client signature with the public key that was contained on the message to verify that the message is from that client.
How on earth does that help anything? I want to send you a message. You want to know who it comes from. So I send you a photocopy of my drivers license, so you can compare the signature on the license with the signature on the message. How do you know I sent you my drivers license and not a photocopy of someone else's? This doesn't solve the client authentication problem at all. Again, you need to solve the key distribution problem. The system depends on there being a secure key distribution infrastructure, which you have not specified.
Posting as an answer since it would be too long for a comment - it isn't specifically answering your question though.
As mentionned in the comment by driis, you should really rely on existing solutions which are regarded as being secure. That said, your protocol does have security issues:
Communication is usually two-way, you however only seem to address one-way communication (client to server). This doesn't make much sense, since you say that you're going to use TCP, which is a two-way protocol in itself.
Steps 4 and 5 are buggy: since you send the public key of the client inside the message, anyone could create a pair and encrypt the client identification using this pair. From your description the server has no forward knowledge of the client's keys, which makes this signature do nothing but ensure the integrity of the message - specifically is does not in any way make the client identification trustworthy.
For proper identification, you do have additional prerequisites; the server has to know the client's public key in advance or it has to be able to trust the client's claim to be himself by using a trusted 3rd party. This is what certificates and the certificate trust chains are about: if that client presents a certificate issued by the 3rd party X and the server trusts X, then he can assume that the client is who he pretends to be.
SSL basically supports two modes:
Either only the server identity is verified and any client can communicate with it; the client's identity is not verified, only that (after the connection has been negotiated) it always is the same client which communicates to the server. This is the typical usage for online shopping etc. - you (as the client) trust the server and create a trusted connection, but the server does not know who you are.
Two-way authentification can be done as well by using client certificates. The server has to know and trust either the client certificate directly or the issuer of the client certificate in order to negotiate the connection successfully. In this scenario, the server does indeed know who the client is, but the prerequisite as mentioned above has to be met.