67

I am building a custom shopping cart where CC numbers and Exp date will be stored in a database until processing (then deleted). I need to encrypt this data (obviously).

I want to use the RSACryptoServiceProvider class.

Here is my code to create my keys.

public static void AssignNewKey(){
    const int PROVIDER_RSA_FULL = 1;
    const string CONTAINER_NAME = "KeyContainer";
    CspParameters cspParams;
    cspParams = new CspParameters(PROVIDER_RSA_FULL);
    cspParams.KeyContainerName = CONTAINER_NAME;
    cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
    cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
    rsa = new RSACryptoServiceProvider(cspParams);

    string publicPrivateKeyXML = rsa.ToXmlString(true);
    string publicOnlyKeyXML = rsa.ToXmlString(false);
    // do stuff with keys...
}

Now the plan is to store the private key xml on a USB drive attached to the managers key chain.

Whenever a manager leaves the company I want to be able to generate new public and private keys (and re-encrypt all currently stored CC numbers with the new public key).

My problem is that the keys generated by this code are always the same. How would I generate a unique set of keys every time?

UPDATE. My test code is below.:
note: the "privatekey" parameter here is the original private key. In order for the keys to be changed I need to verify that the private key is valid.

In Default.aspx.cs

public void DownloadNewPrivateKey_Click(object sender, EventArgs e)
{
    StreamReader reader = new StreamReader(fileUpload.FileContent);
    string privateKey = reader.ReadToEnd();
    Response.Clear();
    Response.ContentType = "text/xml";
    Response.End();
    Response.Write(ChangeKeysAndReturnNewPrivateKey(privateKey));
}

In Crytpography.cs:

public static privateKey;
public static publicKey;
public static RSACryptoServiceProvider rsa;

public static string ChangeKeysAndReturnNewPrivateKey(string _privatekey)
{

    string testData = "TestData";
    string testSalt = "salt";
    // encrypt the test data using the exisiting public key...
    string encryptedTestData = EncryptData(testData, testSalt);
    try
    {
        // try to decrypt the test data using the _privatekey provided by user...
        string decryptTestData = DecryptData(encryptedTestData, _privatekey, testSalt);
        // if the data is successfully decrypted assign new keys...
        if (decryptTestData == testData)
        {
            AssignNewKey();
            // "AssignNewKey()" should set "privateKey" to the newly created private key...
            return privateKey;
        }
        else
        {
            return string.Empty;
        }
    }
    catch (Exception ex)
    {
        return string.Empty;
    }
}
public static void AssignParameter(){
    const int PROVIDER_RSA_FULL = 1;
    const string CONTAINER_NAME = "KeyContainer";
    CspParameters cspParams;
    cspParams = new CspParameters(PROVIDER_RSA_FULL);
    cspParams.KeyContainerName = CONTAINER_NAME;
    cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
    cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
    rsa = new RSACryptoServiceProvider(cspParams);
}
public static void AssignNewKey()
{
    AssignParameter();

    using (SqlConnection myConn = new SqlConnection(Utilities.ConnectionString))
    {
        SqlCommand myCmd = myConn.CreateCommand();

        string publicPrivateKeyXML = rsa.ToXmlString(true);
        privateKey = publicPrivateKeyXML; // sets the public variable privateKey to the new private key.

        string publicOnlyKeyXML = rsa.ToXmlString(false);
        publicKey = publicOnlyKeyXML; // sets the public variable publicKey to the new public key.

        myCmd.CommandText = "UPDATE Settings SET PublicKey = @PublicKey";
        myCmd.Parameters.AddWithValue("@PublicKey", publicOnlyKeyXML);
        myConn.Open();

        myComm.ExecuteScalar();
    }
}
public static string EncryptData(string data2Encrypt, string salt)
{
    AssignParameter();

    using (SqlConnection myConn = new SqlConnection(Utilities.ConnectionString))
    {
        SqlCommand myCmd = myConn.CreateCommand();

        myCmd.CommandText = "SELECT TOP 1 PublicKey FROM Settings";

        myConn.Open();

        using (SqlDataReader sdr = myCmd.ExecuteReader())
        {
            if (sdr.HasRows)
            {
                DataTable dt = new DataTable();
                dt.Load(sdr);
                rsa.FromXmlString(dt.Rows[0]["PublicKey"].ToString());
            }
        }
    }

    //read plaintext, encrypt it to ciphertext
    byte[] plainbytes = System.Text.Encoding.UTF8.GetBytes(data2Encrypt + salt);
    byte[] cipherbytes = rsa.Encrypt(plainbytes, false);
    return Convert.ToBase64String(cipherbytes);
}
public static string DecryptData(string data2Decrypt, string privatekey, string salt)
{
    AssignParameter();

    byte[] getpassword = Convert.FromBase64String(data2Decrypt);

    string publicPrivateKeyXML = privatekey;
    rsa.FromXmlString(publicPrivateKeyXML);

    //read ciphertext, decrypt it to plaintext
    byte[] plain = rsa.Decrypt(getpassword, false);
    string dataAndSalt = System.Text.Encoding.UTF8.GetString(plain);
    return dataAndSalt.Substring(0, dataAndSalt.Length - salt.Length);
}
svick
  • 214,528
  • 47
  • 357
  • 477
David Murdoch
  • 82,194
  • 38
  • 141
  • 186
  • I am basically calling the AssignNewKey() function from a .net page and then checking the new "publicPrivateKeyXML" against my the previous version. I'll update the question above to include my test code. – David Murdoch Aug 20 '09 at 18:14
  • 2
    This is a bit tangential, but do you realise that in order to store credit card numbers you need your system to be PCI compliant? See http://stackoverflow.com/questions/4300863/storing-credit-card-number-pci – Art May 03 '12 at 05:23
  • yup, that is actually what sparked this question. though we ended up using an external payment provider in the end. – David Murdoch May 03 '12 at 15:16

3 Answers3

131

When you use a code like this:

using (var rsa = new RSACryptoServiceProvider(1024))
{
   // Do something with the key...
   // Encrypt, export, etc.
}

.NET (actually Windows) stores your key in a persistent key container forever. The container is randomly generated by .NET

This means:

  1. Any random RSA/DSA key you have EVER generated for the purpose of protecting data, creating custom X.509 certificate, etc. may have been exposed without your awareness in the Windows file system. Accessible by anyone who has access to your account.

  2. Your disk is being slowly filled with data. Normally not a big concern but it depends on your application (e.g. it might generates hundreds of keys every minute).

To resolve these issues:

using (var rsa = new RSACryptoServiceProvider(1024))
{
   try
   {
      // Do something with the key...
      // Encrypt, export, etc.
   }
   finally
   {
      rsa.PersistKeyInCsp = false;
   }
}

ALWAYS

CloudMeta
  • 39,869
  • 66
  • 178
  • 289
coder5
  • 1,319
  • 1
  • 8
  • 2
  • 2
    I have exactly the same need as the OP; the public key's going in a DB, the private key's going into secured storage on a thumb drive. So, if I used your example code, but the first line was `rsa.FromXMLString(pubKey)`, then neither the generated key nor the loaded one is persisted to a store? – KeithS Dec 19 '12 at 15:49
  • Does initializing the RSACryptoServiceProvider with CspParameters() { Flags = CspProviderFlags.CreateEphemeralKey }) accomplish the same thing? – Lilith River Feb 22 '13 at 12:17
  • Is there a way to set PersistKeyInCsp before creating the rsa object? – Nayef Feb 03 '15 at 05:07
  • This is not true. I verified that in this particular example above, rsa.PersistKeyInCsp is already false. See Remarks section here for more details: https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.persistkeyincsp%28v=vs.110%29.aspx – Jason White Feb 16 '15 at 17:42
  • @JasonWhite, I am not sure how did you checked it and which version of .NET you are using. I am using .NET 4.0 and `rsa.PersistKeyInCsp` is in fact true. as remark section in your link mentions "if a container specified" and it is specified in example `cspParams.KeyContainerName = CONTAINER_NAME;` – AaA Feb 27 '15 at 04:53
  • 1
    For me, it's false. .Net 4.6.x `using (RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider(2048)) { var x = myRSA.PersistKeyInCsp; ...` x = false; – Elton Jun 13 '16 at 18:58
  • 2
    According to msdn: "The PersistKeyInCsp property is automatically set to true when you specify a key container name in the KeyContainerName field..." https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.persistkeyincsp(v=vs.110).aspx – Rosdi Kasim Apr 15 '17 at 07:59
  • Further to @RosdiKasim's extract above: "The PersistKeyInCsp property has no effect if the `RSACryptoServiceProvider` object is created with a `null` key container name." – Dan Jan 17 '20 at 14:41
27

The RSACryptoServiceProvider(CspParameters) constructor creates a keypair which is stored in the keystore on the local machine. If you already have a keypair with the specified name, it uses the existing keypair.

It sounds as if you are not interested in having the key stored on the machine.

So use the RSACryptoServiceProvider(Int32) constructor:

public static void AssignNewKey(){
    RSA rsa = new RSACryptoServiceProvider(2048); // Generate a new 2048 bit RSA key

    string publicPrivateKeyXML = rsa.ToXmlString(true);
    string publicOnlyKeyXML = rsa.ToXmlString(false);
    // do stuff with keys...
}

EDIT:

Alternatively try setting the PersistKeyInCsp to false:

public static void AssignNewKey(){
    const int PROVIDER_RSA_FULL = 1;
    const string CONTAINER_NAME = "KeyContainer";
    CspParameters cspParams;
    cspParams = new CspParameters(PROVIDER_RSA_FULL);
    cspParams.KeyContainerName = CONTAINER_NAME;
    cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
    cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
    rsa = new RSACryptoServiceProvider(cspParams);

    rsa.PersistKeyInCsp = false;

    string publicPrivateKeyXML = rsa.ToXmlString(true);
    string publicOnlyKeyXML = rsa.ToXmlString(false);
    // do stuff with keys...
}
Rasmus Faber
  • 45,972
  • 20
  • 136
  • 184
  • Correct; I guess I am not interested in having the key stored on the machine. If I use the RSACryptoServiceProvider(Int32) constructor the following code gives me a "The system cannot find the file specified." error. RSA rsa = new RSACryptoServiceProvider(2048); rsa.ToXmlString(true); – David Murdoch Aug 20 '09 at 19:08
  • Since I'm running this in asp.net could that be the problem? – David Murdoch Aug 20 '09 at 19:23
  • Yes, the problem is probably because the "Network Service" cannot generate keys in the user store. – Rasmus Faber Aug 20 '09 at 20:18
  • All I want is a public key (stored on the server), a private key (stored with manager), and method to encrypt and decrypt some data securely using these keys. I don't want anything stored on the machine by default. Why is this so hard? Are there any alternatives you might suggest? – David Murdoch Aug 20 '09 at 20:54
  • Did you see the edit I made? Try setting PersistKeyInCsp to false. – Rasmus Faber Aug 21 '09 at 06:00
  • It didn't work for some reason. I think I've found an alternative solution I'm working on now. – David Murdoch Aug 21 '09 at 18:07
  • @Rasmus Faber , a User has **hashed md5** and a **Public key file** - How can an end user check ( decrypt) the msg with the file which contains the public Key?( is there online tool or something?) – Royi Namir Oct 31 '11 at 21:45
  • @RoyiNamir To decrypt a message, you NEED the private key. You generate a key pair, you give the public key and you keep the private key to yourself and be very careful that no one knows about it; you may even want to encrypt the private key with a password, in case if falls into other hands. The public key in an asymmetric encryption algorithm is used for encryption; and the private key for decryption. The whole point of the safety of the algorithm is that it's computationally very expensive (i.e. very hard, next to impossible) to obtain the private key from public key information. – jbatista Feb 06 '13 at 22:17
8

What I ended up doing is create a new KeyContainer name based off of the current DateTime (DateTime.Now.Ticks.ToString()) whenever I need to create a new key and save the container name and public key to the database. Also, whenever I create a new key I would do the following:

public static string ConvertToNewKey(string oldPrivateKey)
{

    // get the current container name from the database...

    rsa.PersistKeyInCsp = false;
    rsa.Clear();
    rsa = null;

    string privateKey = AssignNewKey(true); // create the new public key and container name and write them to the database...

       // re-encrypt existing data to use the new keys and write to database...

    return privateKey;
}
public static string AssignNewKey(bool ReturnPrivateKey){
     string containerName = DateTime.Now.Ticks.ToString();
     // create the new key...
     // saves container name and public key to database...
     // and returns Private Key XML.
}

before creating the new key.

David Murdoch
  • 82,194
  • 38
  • 141
  • 186
  • 1
    it would be nice if you posted complete solution as I can't figure out what's being done in the comments – ShaneKm Jun 12 '13 at 09:54
  • am I reading your code correctly? you have a variable called 'privateKey' but your comments suggest your creating a new public key? – barrypicker May 05 '16 at 18:48
  • It's been almost 7 years since I wrote this ... but I think the `AssignNewKey` method was intended to create a new `public` *and* `private` key, storing the `public` key in the database while returning the `private` key as an XML string. – David Murdoch May 05 '16 at 20:17