加密 &在 C# 中解密字符串 [英] Encrypting & Decrypting a String in C#

查看:42
本文介绍了加密 &在 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).

推荐答案

UPDATE 23/Dec/2015:由于这个答案似乎得到了很多赞成,我已经更新它以修复愚蠢的错误并通常根据评论和反馈改进代码.有关具体改进的列表,请参阅帖子末尾.

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

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 值.由于给定字符串的加密和解密之间的 salt 和 IV 必须相同,因此在加密时将 salt 和 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# 中解密字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆