在C#中对大数据进行RSA加密

10

这是我的第一篇文章,希望我没有漏掉任何重要的事情。 我正在使用C#进行一个项目,在这个项目中,我需要使用公钥/私钥加密来加密消息,然后通过SSL连接发送它。

我选择使用RSACryptoService,因为根据文档,这是唯一用于加密数据的非对称加密方案。问题是,我遇到了很多问题。(我本来想做对称加密,但这不是我的老师要我做的,而且根据他的说法,只需确定块大小,就可以完成所有工作。) 到目前为止,还没有成功,我尝试了一些不同的方法,但现在我回到基础知识并再次尝试,这是我的当前代码:

    public string[] GenerateKeysToStrings(string uniqueIdentifier)
    {
        string[] keys;
        using (var rsa = new RSACryptoServiceProvider(4096))
        {
            try
            {
                string privateKey = rsa.ToXmlString(true);
                string publicKey = rsa.ToXmlString(false);

                this.pki.StoreKey(publicKey, uniqueIdentifier);

                keys = new string[2];
                keys[0] = privateKey;
                keys[1] = publicKey;
            }
            finally
            {
                //// Clear the RSA key container, deleting generated keys.
                rsa.PersistKeyInCsp = false;
            }
        }
        return keys;
    }

如您所见,我生成了密钥,并通过将公钥发送到一个简单的类进行模仿PKI(公钥基础设施)并将私钥写入文件。

请注意,我还有另一种方法,它执行相同的操作但是将其存储在数组中,这只是因为我想测试和简化事情。当我按照示例所示进行时,会出现“找不到密钥”异常和有时出现加密异常,因此我想通过仅将rsa.ToXmlString字符串作为字符串存储在数组中来简化它,但没有成功。

现在我有一个加密和解密方法,如下:

    public string Encrypt(string keyString, string message)
    {
        string encryptedMessage;
        using (var rsa = new RSACryptoServiceProvider())
        {
            try
            {
                //// Load the key from the specified path
                var encryptKey = new XmlDocument();
                encryptKey.Load(@"C:\Test\PrivateKeyInfo.xml");
                rsa.FromXmlString(encryptKey.OuterXml);


                //// Conver the string message to a byte array for encryption
                //// var encoder = new UTF8Encoding();
                ASCIIEncoding byteConverter = new ASCIIEncoding();
                byte[] dataToEncrypt = byteConverter.GetBytes(message);

                byte[] encryptedData = rsa.Encrypt(dataToEncrypt, false);

                //// Convert the byte array back to a string message
                encryptedMessage = byteConverter.GetString(encryptedData);
            }
            finally
            {
                //// Clear the RSA key container, deleting generated keys.
                rsa.PersistKeyInCsp = false;
            }
        }
        return encryptedMessage;
    }

解密:

    public string Decrypt(string keyString, string message)
    {
        string decryptedText;
        using (var rsa = new RSACryptoServiceProvider())
        {
            try
            {
                //// Loads the keyinfo into the rsa parameters from the keyfile
                /*
                var privateKey = new XmlDocument();
                privateKey.Load(keyString);
                 */
                rsa.FromXmlString(keyString);

                //// Convert the text from string to byte array for decryption
                ASCIIEncoding byteConverter = new ASCIIEncoding();
                var encryptedBytes = byteConverter.GetBytes(message);

                //// Create an aux array to store all the encrypted bytes
                byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, false);

                decryptedText = byteConverter.GetString(decryptedBytes);
            }
            finally
            {
                //// Clear the RSA key container, deleting generated keys.
                rsa.PersistKeyInCsp = false;
            }
        }
        return decryptedText;
    }

虽然这篇文章很冗长,但我希望你能帮我解决问题,因为我已经苦思冥想了很长时间,但一直没有头绪:)

问题是,如何使用 RSA (或任何其他公钥/私钥加密算法)加密消息。

下面是测试客户端:

    public static void Main(string[] args)
    {
        PublicKeyInfrastructure pki = new PublicKeyInfrastructure();
        Cryptograph crypto = new Cryptograph();
        string[] keys = crypto.GenerateKeysToStrings("simonlanghoff@gmail.com");


        string plainText = "Hello play with me, please";
        string publicKey = crypto.GetPublicKey("simonlanghoff@gmail.com");

        string encryptedText = crypto.Encrypt(keys[0], plainText);


        string decryptedText = crypto.Decrypt(keys[1], encryptedText);

    }

正如我所提到的,字符串数组的存在是为了消除XML文档中的解析错误...

当我运行测试客户端时,如果我使用私钥加密和公钥解密,我会得到一个"密钥不存在异常",如果我反过来做,我会得到一个坏数据异常。

如果你知道任何好的指南,或者可以告诉我如何在字符串消息上实现公钥/私钥加密,请帮帮我。

我感激任何帮助。

3个回答

10
这不是正确的RSA加密方式。
RSA加密涉及到数学。你要加密的是一个数字,因此它必须具有有限的长度并与你使用的RSA密钥对长度匹配。填充使用了PKCS #1或OAEP,还会对长度施加限制。
如果你想用RSA加密大量的数据,你需要间接地实现-即使用对称密钥加密大数据,然后使用RSA公钥加密该密钥。
你可以在我的博客上阅读有关如何实施这种方法的信息。 blog

我和我的教授交流过,当时我的观点是最好加密密钥,交换密钥,然后将其作为对称算法(如Rijndael)的基础来加密/解密消息。然而,他不希望我们使用对称加密,所以现在我处于这种情况下,暂时无法使用。就性能而言,如果我们谈论包含用户名和密码的HTTP消息,每次加密501字节(使用4096位RSA密钥),需要多长时间?无论是逐块编码还是不编码,我仍然在实际使用RSA方面遇到问题:( - Simon Langhoff
请检查您的代码。您的“Encrypt”方法没有使用“keyString”参数,并加载了一个XML文件(带有密钥?),但未使用其内容。看起来您正在使用自动生成的密钥进行加密(默认为1024位),因此无法匹配解密。 - poupou
我想我找到了为什么会出现“密钥不存在”错误的原因,而且每次使用私钥加密数据时都会发生这种情况。我以为使用哪个密钥都无所谓,如果我理解错了,请务必纠正我,但是我猜测私钥加密是用于验证而不是保密,所以RSA加密服务提供程序的加密和解密方法不允许使用私钥,而应该仅用于签名/验证。但这并不能解释为什么我使用公钥时会出现“坏数据”异常。 - Simon Langhoff
哦,是的 @poupou,我很抱歉那只是我的失误。我一直在尝试以各种不同的方式加载密钥,这有点傻,所以我现在已经更新了原始帖子,在附加行中从 XML 文件加载,其他时候我只是将密钥保存为字符串并将其解析为参数,这样可以直接解析导出的字符串,因此我不会排除任何 XML 解析错误或类似问题的可能性。对于混淆造成的困惑,我感到很抱歉。 - Simon Langhoff
博客链接已失效。 - RBT
显示剩余4条评论

3

好的,我终于想出了一个解决我在原帖中提到的问题的办法。这是我从一些试错过程中找出来的,虽然我还没有进行彻底测试。

以下是我目前的代码:

    public static string Encrypt(string dataToEncrypt, RSAParameters publicKeyInfo)
    {
        //// Our bytearray to hold all of our data after the encryption
        byte[] encryptedBytes = new byte[0];
        using (var RSA = new RSACryptoServiceProvider())
        {
            try
            {
                //Create a new instance of RSACryptoServiceProvider.
                UTF8Encoding encoder = new UTF8Encoding();

                byte[] encryptThis = encoder.GetBytes(dataToEncrypt);

                //// Importing the public key
                RSA.ImportParameters(publicKeyInfo);

                int blockSize = (RSA.KeySize / 8) - 32;

                //// buffer to write byte sequence of the given block_size
                byte[] buffer = new byte[blockSize];

                byte[] encryptedBuffer = new byte[blockSize];

                //// Initializing our encryptedBytes array to a suitable size, depending on the size of data to be encrypted
                encryptedBytes = new byte[encryptThis.Length + blockSize - (encryptThis.Length % blockSize) + 32];

                for (int i = 0; i < encryptThis.Length; i += blockSize)
                {
                    //// If there is extra info to be parsed, but not enough to fill out a complete bytearray, fit array for last bit of data
                    if (2 * i > encryptThis.Length && ((encryptThis.Length - i) % blockSize != 0))
                    {
                        buffer = new byte[encryptThis.Length - i];
                        blockSize = encryptThis.Length - i;
                    }

                    //// If the amount of bytes we need to decrypt isn't enough to fill out a block, only decrypt part of it
                    if (encryptThis.Length < blockSize)
                    {
                        buffer = new byte[encryptThis.Length];
                        blockSize = encryptThis.Length;
                    }

                    //// encrypt the specified size of data, then add to final array.
                    Buffer.BlockCopy(encryptThis, i, buffer, 0, blockSize);
                    encryptedBuffer = RSA.Encrypt(buffer, false);
                    encryptedBuffer.CopyTo(encryptedBytes, i);
                }
            }
            catch (CryptographicException e)
            {
                Console.Write(e);
            }
            finally
            {
                //// Clear the RSA key container, deleting generated keys.
                RSA.PersistKeyInCsp = false;
            }
        }
        //// Convert the byteArray using Base64 and returns as an encrypted string
        return Convert.ToBase64String(encryptedBytes);
    }

    /// <summary>
    /// Decrypt this message using this key
    /// </summary>
    /// <param name="dataToDecrypt">
    /// The data To decrypt.
    /// </param>
    /// <param name="privateKeyInfo">
    /// The private Key Info.
    /// </param>
    /// <returns>
    /// The decrypted data.
    /// </returns>
    public static string Decrypt(string dataToDecrypt, RSAParameters privateKeyInfo)
    {
        //// The bytearray to hold all of our data after decryption
        byte[] decryptedBytes;

        //Create a new instance of RSACryptoServiceProvider.
        using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
        {
            try
            {
                byte[] bytesToDecrypt = Convert.FromBase64String(dataToDecrypt);

                //// Import the private key info
                RSA.ImportParameters(privateKeyInfo);

                //// No need to subtract padding size when decrypting (OR do I?)
                int blockSize = RSA.KeySize / 8;

                //// buffer to write byte sequence of the given block_size
                byte[] buffer = new byte[blockSize];

                //// buffer containing decrypted information
                byte[] decryptedBuffer = new byte[blockSize];

                //// Initializes our array to make sure it can hold at least the amount needed to decrypt.
                decryptedBytes = new byte[dataToDecrypt.Length];

                for (int i = 0; i < bytesToDecrypt.Length; i += blockSize)
                {
                    if (2 * i > bytesToDecrypt.Length && ((bytesToDecrypt.Length - i) % blockSize != 0))
                    {
                        buffer = new byte[bytesToDecrypt.Length - i];
                        blockSize = bytesToDecrypt.Length - i;
                    }

                    //// If the amount of bytes we need to decrypt isn't enough to fill out a block, only decrypt part of it
                    if (bytesToDecrypt.Length < blockSize)
                    {
                        buffer = new byte[bytesToDecrypt.Length];
                        blockSize = bytesToDecrypt.Length;
                    }

                    Buffer.BlockCopy(bytesToDecrypt, i, buffer, 0, blockSize);
                    decryptedBuffer = RSA.Decrypt(buffer, false);
                    decryptedBuffer.CopyTo(decryptedBytes, i);
                }
            }
            finally
            {
                //// Clear the RSA key container, deleting generated keys.
                RSA.PersistKeyInCsp = false;
            }
        }

        //// We encode each byte with UTF8 and then write to a string while trimming off the extra empty data created by the overhead.
        var encoder = new UTF8Encoding();
        return encoder.GetString(decryptedBytes).TrimEnd(new[] { '\0' });

    }

如我所说,除了块大小以下、块大小和块大小以上的尺寸外,我并没有对其进行过多测试,但它似乎在做它应该做的事情。我仍然是个新手,因此我希望你能仔细审查我的代码 :)


0
也许我错过了什么,但是看起来你的 Encrypt() 函数似乎没有使用 keyString 参数或 encryptKey 的内容。

非常抱歉,我可能忘记放进去了,但我保证已经加载了密钥。现在我已经更新了 OP。我尝试加载数据的次数太多了,可能弄混了一些版本,没看到它 :) 但我的问题仍然存在。我现在遇到的问题是,当我使用私钥加密,公钥解密时,会出现 keyDoesn'tExist 异常。但是如果我反过来做,我会得到一个“坏数据”异常。 - Simon Langhoff

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