加密与在 C# 中解密字符串

Encrypting amp; Decrypting a String in C#(加密与在 C# 中解密字符串)
本文介绍了加密与在 C# 中解密字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 C# 中满足以下要求的最现代(最佳)方式是什么?

What is the most modern (best) way of satisfying the following in C#?

string encryptedString = SomeStaticClass.Encrypt(sourceString);

string decryptedString = SomeStaticClass.Decrypt(encryptedString);

但在涉及盐、密钥、字节 [] 等方面的问题最少.

BUT with a minimum of fuss involving salts, keys, mucking about with byte[], etc.

一直在谷歌上搜索并对我的发现感到困惑(您可以查看类似 SO Q 的列表,以了解这是一个欺骗性问题).

Been Googling and confused at what I'm finding (you can see the list of similar SO Qs to see this is a deceptive question to ask).

推荐答案

2015 年 12 月 23 日更新:由于这个答案似乎得到了很多支持,我已经对其进行了更新以修复愚蠢的错误并通常根据评论和反馈改进代码.请参阅帖子末尾的具体改进列表.

正如其他人所说,密码学并不简单,因此最好避免滚动自己的"加密算法.

As other people have said, Cryptography is not simple so it's best to avoid "rolling your own" encryption algorithm.

但是,您可以围绕内置 RijndaelManaged 加密类.

You can, however, "roll your own" wrapper class around something like the built-in RijndaelManaged cryptography class.

Rijndael 是当前高级加密标准的算法名称,所以你肯定在使用一种可以被认为是最佳实践"的算法.

Rijndael is the algorithmic name of the current Advanced Encryption Standard, so you're certainly using an algorithm that could be considered "best practice".

RijndaelManaged 类通常确实确实需要您搞砸"字节数组、盐、键、初始化向量等.但这正是可以在包装器"类中抽象出来的那种细节.

The RijndaelManaged class does indeed normally require you to "muck about" with byte arrays, salts, keys, initialization vectors etc. but this is precisely the kind of detail that can be somewhat abstracted away within your "wrapper" class.

下面的类是我前段时间写的,用来执行你所追求的那种事情,一个简单的单一方法调用,允许使用基于字符串的密码对一些基于字符串的明文进行加密,结果加密字符串也被表示为字符串.当然,还有一种等效的方法可以用相同的密码解密加密的字符串.

The following class is one I wrote a while ago to perform exactly the kind of thing you're after, a simple single method call to allow some string-based plaintext to be encrypted with a string-based password, with the resulting encrypted string also being represented as a string. Of course, there's an equivalent method to decrypt the encrypted string with the same password.

与此代码的第一个版本每次都使用完全相同的 salt 和 IV 值不同,这个新版本每次都会生成随机的 salt 和 IV 值.由于在给定字符串的加密和解密之间盐和 IV 必须相同,因此盐和 IV 在加密时被添加到密文中,并再次从中提取以执行解密.这样做的结果是,用完全相同的密码加密完全相同的明文每次都会得到完全不同的密文结果.

Unlike the first version of this code, which used the exact same salt and IV values every time, this newer version will generate random salt and IV values each time. Since salt and IV must be the same between the encryption and decryption of a given string, the salt and IV is prepended to the cipher text upon encryption and extracted from it again in order to perform the decryption. The result of this is that encrypting the exact same plaintext with the exact same password gives and entirely different ciphertext result each time.

使用它的优势"来自于使用 RijndaelManaged 类为您执行加密,同时使用 System.Security.Cryptography 命名空间的 "noreferrer">Rfc2898DeriveBytes 函数,它将使用标准且安全的算法生成您的加密密钥(具体而言,PBKDF2) 基于您提供的基于字符串的密码.(注意这是对第一个版本使用旧 PBKDF1 算法的改进).

The "strength" of using this comes from using the RijndaelManaged class to perform the encryption for you, along with using the Rfc2898DeriveBytes function of the System.Security.Cryptography namespace which will generate your encryption key using a standard and secure algorithm (specifically, PBKDF2) based upon the string-based password you supply. (Note this is an improvement of the first version's use of the older PBKDF1 algorithm).

最后,重要的是要注意这仍然是未经身份验证的加密.单独的加密仅提供隐私(即消息不为第三方所知),而经过身份验证的加密旨在提供隐私和真实性(即接收者知道消息是由发送者发送的).

Finally, it's important to note that this is still unauthenticated encryption. Encryption alone provides only privacy (i.e. message is unknown to 3rd parties), whilst authenticated encryption aims to provide both privacy and authenticity (i.e. recipient knows message was sent by the sender).

在不了解您的确切要求的情况下,很难说这里的代码是否足够安全以满足您的需求,但是,它的生成是为了在实现的相对简单性与质量"之间实现良好的平衡.例如,如果加密字符串的接收者"直接从受信任的发送者"接收字符串,则身份验证可能不会甚至是必要的.

Without knowing your exact requirements, it's difficult to say whether the code here is sufficiently secure for your needs, however, it has been produced to deliver a good balance between relative simplicity of implementation vs "quality". For example, if your "receiver" of an encrypted string is receiving the string directly from a trusted "sender", then authentication may not even be necessary.

如果您需要更复杂的东西,并且提供经过身份验证的加密,请查看 这篇文章用于实现.

If you require something more complex, and which offers authenticated encryption, check out this post for an implementation.

代码如下:

using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Linq;

namespace EncryptStringSample
{
    public static class StringCipher
    {
        // This constant is used to determine the keysize of the encryption algorithm in bits.
        // We divide this by 8 within the code below to get the equivalent number of bytes.
        private const int Keysize = 256;

        // This constant determines the number of iterations for the password bytes generation function.
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
            // so that the same Salt and IV values can be used when decrypting.  
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }
    }
}

上面的类可以很简单地使用类似下面的代码:

The above class can be used quite simply with code similar to the following:

using System;

namespace EncryptStringSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter a password to use:");
            string password = Console.ReadLine();
            Console.WriteLine("Please enter a string to encrypt:");
            string plaintext = Console.ReadLine();
            Console.WriteLine("");

            Console.WriteLine("Your encrypted string is:");
            string encryptedstring = StringCipher.Encrypt(plaintext, password);
            Console.WriteLine(encryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Your decrypted string is:");
            string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
            Console.WriteLine(decryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
}

(您可以下载一个简单的 VS2013 示例解决方案(其中包括一些单元测试)这里).

(You can download a simple VS2013 sample solution (which includes a few unit tests) here).

2015 年 12 月 23 日更新:代码的具体改进列表如下:

UPDATE 23/Dec/2015: The list of specific improvements to the code are:

  • 修复了一个愚蠢的错误,即加密和加密之间的编码不同解密.作为盐和盐的机制生成的 IV 值已更改,不再需要编码.
  • 由于 salt/IV 更改,之前错误地指出 UTF8 编码 16 个字符的字符串产生 32 个字节的代码注释不再适用(因为不再需要编码).
  • 已被取代的 PBKDF1 算法的使用已被更现代的 PBKDF2 算法所取代.
  • 密码派生现在已正确加盐,而以前根本没有加盐(另一个愚蠢的错误被压扁).

这篇关于加密与在 C# 中解密字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

DispatcherQueue null when trying to update Ui property in ViewModel(尝试更新ViewModel中的Ui属性时DispatcherQueue为空)
Drawing over all windows on multiple monitors(在多个监视器上绘制所有窗口)
Programmatically show the desktop(以编程方式显示桌面)
c# Generic Setlt;Tgt; implementation to access objects by type(按类型访问对象的C#泛型集实现)
InvalidOperationException When using Context Injection in ASP.Net Core(在ASP.NET核心中使用上下文注入时发生InvalidOperationException)
LINQ many-to-many relationship, how to write a correct WHERE clause?(LINQ多对多关系,如何写一个正确的WHERE子句?)