Как сгенерировать уникальный открытый и закрытый ключи через RSA

Я создаю собственную корзину, в которой номера CC и дата истечения срока хранения будут храниться в базе данных до обработки (а затем удаления). Мне нужно зашифровать эти данные (очевидно).

Я хочу использовать класс RSACryptoServiceProvider.

Вот мой код для создания ключей.

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...
}

Теперь планируется сохранить секретный ключ xml на USB-накопителе, подключенном к цепочке ключей менеджеров.

Каждый раз, когда менеджер покидает компанию, я хочу иметь возможность сгенерировать новые открытые и закрытые ключи (и повторно зашифровать все текущие сохраненные номера CC с новым открытым ключом).

Моя проблема в том, что ключи, сгенерированные этим кодом, всегда одинаковы. Как мне каждый раз генерировать уникальный набор ключей?

ОБНОВЛЕНИЕ. Мой тестовый код приведен ниже:
примечание: параметр "privatekey" - это исходный закрытый ключ. Чтобы изменить ключи, мне нужно убедиться, что закрытый ключ действителен.

В 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));
}

В 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);
}

person David Murdoch    schedule 20.08.2009    source источник
comment
Я в основном вызываю функцию AssignNewKey () со страницы .net, а затем проверяю новый publicPrivateKeyXML на мою предыдущую версию. Я обновлю вопрос выше, добавив в него свой тестовый код.   -  person David Murdoch    schedule 20.08.2009
comment
Это немного косвенно, но понимаете ли вы, что для хранения номеров кредитных карт вам необходимо, чтобы ваша система была совместима с PCI? См. stackoverflow.com/questions/4300863/   -  person Art    schedule 03.05.2012
comment
да, именно это и вызвало этот вопрос. хотя в итоге мы использовали внешнего поставщика платежей.   -  person David Murdoch    schedule 03.05.2012


Ответы (3)


Когда вы используете такой код:

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

.NET (на самом деле Windows) навсегда хранит ваш ключ в постоянном контейнере ключей. Контейнер генерируется случайным образом .NET.

Это означает:

  1. Любой случайный ключ RSA / DSA, который вы КОГДА-ЛИБО сгенерировали с целью защиты данных, создания настраиваемого сертификата X.509 и т. Д., Мог быть раскрыт без вашего ведома в файловой системе Windows. Доступно всем, у кого есть доступ к вашей учетной записи.

  2. Ваш диск медленно заполняется данными. Обычно это не вызывает большого беспокойства, но это зависит от вашего приложения (например, оно может генерировать сотни ключей каждую минуту).

Чтобы решить эти проблемы:

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

ВСЕГДА

person coder5    schedule 30.04.2011
comment
У меня точно такая же потребность, как и у ОП; открытый ключ хранится в БД, закрытый - в защищенном хранилище на флэш-накопителе. Итак, если я использовал ваш пример кода, но первая строка была rsa.FromXMLString(pubKey), то ни сгенерированный, ни загруженный ключ не сохраняется в хранилище? - person KeithS; 19.12.2012
comment
Выполняет ли инициализация RSACryptoServiceProvider с помощью CspParameters () {Flags = CspProviderFlags.CreateEphemeralKey}) то же самое? - person Lilith River; 22.02.2013
comment
Есть ли способ установить PersistKeyInCsp перед созданием объекта rsa? - person Nayef; 03.02.2015
comment
Это неправда. Я проверил, что в этом конкретном примере выше rsa.PersistKeyInCsp уже ложно. Дополнительные сведения см. В разделе «Примечания»: msdn.microsoft.com/en-us/library/ - person Jason White; 16.02.2015
comment
@JasonWhite, я не уверен, как вы это проверили и какую версию .NET вы используете. Я использую .NET 4.0, и rsa.PersistKeyInCsp действительно правда. в разделе примечаний в вашей ссылке упоминается, указан ли контейнер, и он указан в примере cspParams.KeyContainerName = CONTAINER_NAME; - person AaA; 27.02.2015
comment
Для меня это неправда. .Net 4.6.x using (RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider(2048)) { var x = myRSA.PersistKeyInCsp; ... x = ложь; - person Elton; 13.06.2016
comment
Согласно msdn: для свойства PersistKeyInCsp автоматически устанавливается значение true, когда вы указываете имя контейнера ключей в поле KeyContainerName ... msdn.microsoft.com/en-us/library/ - person Rosdi Kasim; 15.04.2017
comment
В дополнение к выдержке @ RosdiKasim, приведенной выше: свойство PersistKeyInCsp не действует, если объект RSACryptoServiceProvider создается с именем контейнера ключей null. - person Dan; 17.01.2020

Конструктор RSACryptoServiceProvider(CspParameters) создает пару ключей, которая хранится в хранилище ключей на локальном компьютере. Если у вас уже есть пара ключей с указанным именем, она использует существующую пару ключей.

Похоже, вы не заинтересованы в хранении ключа на машине.

Так что используйте конструктор RSACryptoServiceProvider(Int32):

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...
}

РЕДАКТИРОВАТЬ:

В качестве альтернативы попробуйте установить для PersistKeyInCsp значение 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...
}
person Rasmus Faber    schedule 20.08.2009
comment
Верный; Думаю, меня не интересует хранение ключа на машине. Если я использую конструктор RSACryptoServiceProvider (Int32), следующий код дает мне сообщение. Система не может найти указанный файл. ошибка. RSA rsa = новый RSACryptoServiceProvider (2048); rsa.ToXmlString (истина); - person David Murdoch; 20.08.2009
comment
Поскольку я запускаю это в asp.net, может быть проблема? - person David Murdoch; 20.08.2009
comment
Да, проблема, вероятно, в том, что сетевая служба не может генерировать ключи в пользовательском хранилище. - person Rasmus Faber; 21.08.2009
comment
Все, что мне нужно, - это открытый ключ (хранящийся на сервере), закрытый ключ (хранящийся в диспетчере) и метод безопасного шифрования и дешифрования некоторых данных с использованием этих ключей. Я не хочу, чтобы что-либо хранилось на машине по умолчанию. Почему это так сложно? Можете ли вы предложить какие-либо альтернативы? - person David Murdoch; 21.08.2009
comment
Вы видели мою правку? Попробуйте установить для PersistKeyInCsp значение false. - person Rasmus Faber; 21.08.2009
comment
По какой-то причине это не сработало. Думаю, я нашел альтернативное решение, над которым сейчас работаю. - person David Murdoch; 21.08.2009
comment
@Rasmus Faber, пользователь имеет хешированный md5 и файл открытого ключа - как конечный пользователь может проверить (расшифровать) сообщение с помощью файла, содержащего открытый ключ? ( есть онлайн-инструмент или что-то в этом роде?) - person Royi Namir; 01.11.2011
comment
@RoyiNamir Чтобы расшифровать сообщение, вам НУЖЕН закрытый ключ. Вы генерируете пару ключей, вы даете открытый ключ и держите закрытый ключ при себе, и будьте очень осторожны, чтобы никто об этом не узнал; вы даже можете захотеть зашифровать закрытый ключ паролем на случай, если он попадет в другие руки. Открытый ключ в алгоритме асимметричного шифрования используется для шифрования; и закрытый ключ для расшифровки. Весь смысл безопасности алгоритма заключается в том, что с вычислительной точки зрения очень дорого (то есть очень сложно, почти невозможно) получить закрытый ключ из информации об открытом ключе. - person jbatista; 07.02.2013

В итоге я создал новое имя KeyContainer на основе текущего DateTime (DateTime.Now.Ticks.ToString ()) всякий раз, когда мне нужно создать новый ключ и сохранить имя контейнера и открытый ключ в базе данных. Кроме того, всякий раз, когда я создаю новый ключ, я делаю следующее:

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.
}

перед созданием нового ключа.

person David Murdoch    schedule 21.08.2009
comment
было бы хорошо, если бы вы опубликовали полное решение, так как я не могу понять, что делается в комментариях - person ShaneKm; 12.06.2013
comment
правильно ли я читаю ваш код? у вас есть переменная с именем privateKey, но в ваших комментариях предлагается создать новый открытый ключ? - person barrypicker; 05.05.2016
comment
Прошло почти 7 лет с тех пор, как я написал это ... но я думаю, что метод AssignNewKey был предназначен для создания нового ключа public и private, сохранения ключа public в базе данных и возврата ключа private в качестве Строка XML. - person David Murdoch; 05.05.2016