如何通过RSA生成唯一的公钥和私钥

71

我正在构建一个自定义购物车,其中信用卡号和过期日期将存储在数据库中直到处理完成(然后删除)。 我需要加密这些数据(显然)。

我想使用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));
}

在Cryptography.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);
}

我基本上是从一个 .net 页面调用 AssignNewKey() 函数,然后检查新的 "publicPrivateKeyXML" 是否与之前的版本相同。我将更新上面的问题以包括我的测试代码。 - David Murdoch
2
这有点离题,但您是否意识到为了存储信用卡号码,您需要使您的系统符合PCI标准?请参见https://dev59.com/um855IYBdhLWcg3wg0nC - Art
是的,那确实是引发这个问题的原因。不过最终我们还是决定使用外部支付提供商。 - David Murdoch
AssignParameter 中出现了“_Provider type does not match registered value_”错误。 - Yola
3个回答

139

当您使用以下代码时:

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

始终如一


2
我和楼主有完全相同的需求;公钥存储在数据库中,私钥存储在安全存储器(如U盘)中。因此,如果我使用您的示例代码,但第一行是rsa.FromXMLString(pubKey),那么生成的密钥和加载的密钥都不会被持久化到存储器中? - KeithS
使用CspParameters() { Flags = CspProviderFlags.CreateEphemeralKey }来初始化RSACryptoServiceProvider可以实现相同的功能吗? - Lilith River
这是不正确的。我验证了上面这个特定示例中,rsa.PersistKeyInCsp 已经是 false。请参阅此处的备注部分以获取更多详细信息:https://msdn.microsoft.com/zh-cn/library/system.security.cryptography.rsacryptoserviceprovider.persistkeyincsp(v=vs.110).aspx - Jason White
1
对我来说,它是假的。.Net 4.6.xusing (RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider(2048)) { var x = myRSA.PersistKeyInCsp; ... x = false; - Elton
3
根据 MSDN 文档:当您在 KeyContainerName 字段中指定密钥容器名称时,“PersistKeyInCsp 属性会自动设置为 true…”https://msdn.microsoft.com/zh-cn/library/system.security.cryptography.rsacryptoserviceprovider.persistkeyincsp(v=vs.110).aspx - Rosdi Kasim
显示剩余5条评论

29

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

是的,问题可能是因为“网络服务”无法在用户存储中生成密钥。 - Rasmus Faber
我想要的只是一个公钥(存储在服务器上)、一个私钥(由管理员存储)以及使用这些密钥安全地加密和解密某些数据的方法。我不希望默认情况下在机器上存储任何东西。为什么这么难?你有什么替代方案可以建议吗? - David Murdoch
你看到我做的修改了吗?试着将PersistKeyInCsp设置为false。 - Rasmus Faber
@Rasmus Faber,一个用户已经使用哈希MD5和一个公钥文件 - 最终用户如何使用包含公钥的文件检查(解密)消息?(是否有在线工具或其他东西?) - Royi Namir
要解密一条消息,你需要私钥。你生成一对密钥,将公钥提供给他人,自己保留私钥,并非常小心地确保没有人知道它;你甚至可能想用密码加密私钥,以防它落入他人手中。在非对称加密算法中,公钥用于加密,而私钥用于解密。算法安全的整个重点在于从公钥信息中获取私钥是计算上非常昂贵的(即非常困难,几乎不可能)。 - jbatista
显示剩余3条评论

8

我最终做的是,每当需要创建新密钥并将容器名称和公钥保存到数据库时,基于当前日期时间(DateTime.Now.Ticks.ToString())创建一个新的KeyContainer名称。此外,每当我创建新密钥时,我会执行以下操作:

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

在创建新密钥之前。


2
如果您能发布完整的解决方案,那就太好了,因为我无法理解注释中所做的事情。 - ShaneKm
我是否正确地阅读了你的代码?你有一个名为“privateKey”的变量,但是你的注释表明你正在创建一个新的公钥? - barrypicker
距离我写下这段代码已经将近7年了……但是我认为AssignNewKey方法的目的是创建一个新的publicprivate密钥,将public密钥存储在数据库中,同时将private密钥作为XML字符串返回。 - David Murdoch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接