简体   繁体   English

C#AES解密代码

[英]C# AES Decryption code

I have the code for string encryption/decryption with AES algorithm. 我有使用AES算法进行字符串加密/解密的代码。 Encryption works fine as expected but I could not get correct plain text when I decrypt it back. 加密按预期工作正常,但是将其解密后却无法获得正确的纯文本。 There must be something wrong in the decryption code. 解密代码一定有问题。

I have copied the code below. 我已复制以下代码。
Please help with this. 请帮忙。 Thanks. 谢谢。

Below is the encryption and Decryption code. 以下是加密和解密代码。

public static class EncryptionHelper
{
    private static int BlockSize = 16;

    private static byte[] Key
    {
        get
        {
            byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes("BlahBlahBlah"));
            byte[] key = new byte[BlockSize];
            Array.Copy(hash, 0, key, 0, BlockSize);
            return key;
        }
    }

    private static byte[] IV
    {
        get
        {
            StringBuilder builder = new StringBuilder();
            Random random = new Random();
            for (int i = 0; i < BlockSize; i++)
            {
                char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                builder.Append(ch);
            }
            return Encoding.UTF8.GetBytes(builder.ToString());
        }
    }

    public static string DecodeAndDecrypt(string cipherText)
    {
        string DecodeAndDecrypt = AesDecrypt(Convert.FromBase64String(HttpUtility.UrlDecode(cipherText)));
        return (DecodeAndDecrypt);
    }

    public static string EncryptAndEncode(string plaintext)
    {
        return HttpUtility.UrlEncode(Convert.ToBase64String(AesEncrypt(plaintext))); 
    }

    public static string AesDecrypt(Byte[] inputBytes)
    {
        Byte[] outputBytes = inputBytes;

        string plaintext = string.Empty;

        using (MemoryStream memoryStream = new MemoryStream(outputBytes))
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateDecryptor(Key, IV), CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(cryptoStream))
                {
                    plaintext = srDecrypt.ReadToEnd();
                }
            }
        }

        return plaintext;
    }

    public static byte[] AesEncrypt(string inputText)
    {
        byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(inputText); 

        for (int i = 0; i < BlockSize; i++)
        {
            inputBytes[i] ^= IV[i];
        }

        byte[] result = null;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateEncryptor(Key, IV), CryptoStreamMode.Write))
            {
                cryptoStream.Write(inputBytes, 0, inputBytes.Length);
                cryptoStream.FlushFinalBlock();
                result = memoryStream.ToArray();
            }
        }
        return result;
    }

    private static RijndaelManaged GetCryptoAlgorithm()
    {
        RijndaelManaged algorithm = new RijndaelManaged();
        algorithm.Padding = PaddingMode.PKCS7;
        algorithm.Mode = CipherMode.CBC;
        algorithm.KeySize = 128;
        algorithm.BlockSize = 128;
        return algorithm;
    }
}

I made a few changes and will post some source code below. 我做了一些更改,并将在下面发布一些源代码。 To make it work, just put 3 textBoxes on Form1 and use this code for Form1.cs. 若要使其工作,只需在Form1上放置3个文本框,然后将此代码用于Form1.cs。 I mentioned above I think a major issue was with how you were reading from StreamReader as a string without specifying your encoding. 上面我提到过,我认为一个主要问题是如何在不指定编码的情况下以字符串形式从StreamReader读取数据。 My change uses BinaryReader to read as a byte array, then converts to UTF-8 string after the read. 我的更改使用BinaryReader读取为字节数组,然后在读取后转换为UTF-8字符串。 I also deleted that XOR loop. 我还删除了XOR循环。 I think you were trying to implement CBC on your own (CBC is a sort of feedback loop of XORs) but it is not needed -- when you specify CBC mode, .NET does the CBC mode for you. 我认为您正在尝试自己实现CBC(CBC是一种XOR的反馈循环),但不是必需的-当您指定CBC模式时,.NET会为您提供CBC模式。 To keep my version as close as reasonably possible to yours, I did not make many more changes. 为了使我的版本尽可能地接近您的版本,我没有做更多更改。 But please take some of the above comments to heart. 但是请牢记上述一些评论。 Don't use a simple hash when .NET provides a DeriveBytes function, for example. 例如,当.NET提供DeriveBytes函数时,请不要使用简单的哈希。 Treat an IV like a paper towel -- use it once and only once, ever. 将IV像纸巾一样对待-永远只使用一次。 And so on. 等等。 Anyway, functional if not "best practice" code is below: 无论如何,如果不是“最佳实践”代码,则功能如下:

Edit: Sorry I forgot to mention I also changed the plain text type to regular strings, deleted the HTTP-related code. 编辑:对不起,我忘了提起我也将纯文本类型更改为常规字符串,删除了与HTTP相关的代码。 This was just to make it simpler for me to work with. 这只是为了简化我的工作。 It should work just as well though with your HTTP methods as it does with regular strings. 它在HTTP方法中的效果应与在常规字符串中一样好。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace StackOverflow
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void textBox3_TextChanged(object sender, EventArgs e)
        {
            textBox1.Text = EncryptionHelper.EncryptAndEncode(textBox3.Text);
            textBox2.Text = EncryptionHelper.DecodeAndDecrypt(textBox1.Text);
        }
    }

    public static class EncryptionHelper
    {
        private static int BlockSize = 16;

        private static byte[] Key
        {
            get
            {
                byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes("BlahBlahBlah"));
                byte[] key = new byte[BlockSize];
                Array.Copy(hash, 0, key, 0, BlockSize);
                return key;
            }
        }

        private static byte[] IV
        {
            get
            {
                StringBuilder builder = new StringBuilder();
                Random random = new Random();
                for (int i = 0; i < BlockSize; i++)
                {
                    char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                    builder.Append(ch);
                }
                return Encoding.UTF8.GetBytes(builder.ToString());
            }
        }

        public static string DecodeAndDecrypt(string cipherText)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            string decodeAndDecrypt = AesDecrypt(cipherBytes);
            return decodeAndDecrypt;
        }

        public static string EncryptAndEncode(string plaintext)
        {
            return Convert.ToBase64String(AesEncrypt(plaintext));
        }

        public static string AesDecrypt(Byte[] inputBytes)
        {
            Byte[] outputBytes = inputBytes;

            string plaintext = string.Empty;

            using (MemoryStream memoryStream = new MemoryStream(outputBytes))
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateDecryptor(Key, IV), CryptoStreamMode.Read))
                {
                    using (BinaryReader srDecrypt = new BinaryReader(cryptoStream))
                    {
                        outputBytes = srDecrypt.ReadBytes(inputBytes.Length);
                        plaintext = Encoding.UTF8.GetString(outputBytes);
                    }
                }
            }
            return plaintext;
        }

        public static byte[] AesEncrypt(string inputText)
        {
            byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(inputText);
            byte[] result = null;
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateEncryptor(Key, IV), CryptoStreamMode.Write))
                {
                    cryptoStream.Write(inputBytes, 0, inputBytes.Length);
                    cryptoStream.FlushFinalBlock();
                    result = memoryStream.ToArray();
                }
            }
            return result;
        }

        private static RijndaelManaged GetCryptoAlgorithm()
        {
            RijndaelManaged algorithm = new RijndaelManaged();
            algorithm.Padding = PaddingMode.PKCS7;
            algorithm.Mode = CipherMode.CBC;
            algorithm.KeySize = 128;
            algorithm.BlockSize = 128;
            return algorithm;
        }
    }
}

The comments above (on the question) have the most important point: you need to use the same IV on decrypt as used on the encrypt , otherwise your first 16 bytes of recovered plaintext will be trashed. 上面的注释(关于这个问题)具有最重要的意义:您需要在解密时使用与加密相同的IV ,否则恢复的前16个字节的纯文本将被丢弃。 (CBC mode will auto-correct after the first block.) (在第一个程序段之后,CBC模式将自动更正。)

You could simply combine the IV with the ciphertext before sending it, and then just break them apart at the other end before decrypting. 您可以在发送前简单地将IV与密文结合起来,然后在解密之前在另一端将它们分开。

It doesn't matter if the IV is public knowledge, as long as it is cryptographically strong, random data, different for each encryption. 只要IV是具有加密强度的随机数据,并且每次加密都不同,则IV是否为公共知识并不重要。 But you need that same corresponding, individual IV at the other end to decrypt. 但是您需要在另一端使用相同的相应的单独的IV进行解密。

Also, as WDS points out, you should not perform that XOR operation. 同样,正如WDS指出的那样,您不应执行该XOR操作。 (But I can't see how WDS's code recovers the first 16 bytes without preserving the IV.) (但我看不到WDS的代码如何在不保留IV的情况下恢复前16个字节。)

Also, IMHO using a SHA1 over the password to generate the key is a security risk. 同样,恕我直言,使用SHA1而不是密码来生成密钥也存在安全风险。 You should be using PBKDF2 or similar to derive a password. 您应该使用PBKDF2或类似的密码来导出密码。

Edit: As Artjom B. points out, since your original code XOR's the IV against the first block of plaintext, you could recover the first 16 bytes by using an IV of all 0x00 bytes on decrypt. 编辑:正如Artjom B.指出的那样,由于您的原始代码XOR是针对第一个明文块的IV,因此可以通过使用解密时所有0x00字节的IV来恢复前16个字节。 But this would only work if the ciphertext was at least two blocks (32 bytes), otherwise, you will get padding errors as this will corrupt the padding. 但这仅在密文至少为两个块(32字节)的情况下才有效,否则,将出现填充错误,因为这会破坏填充。 Try using an IV of all zero bytes on your decrypt, and find out if the input is always at least 16 bytes -- you have an off-the-end-of-the-array bug anyway in your encrypt if it isn't. 尝试在解密中使用全零字节的IV,并确定输入是否始终至少为16个字节-如果不是,则加密中总会出现阵列末端错误。 (You'll still have an insecure IV, but at least you will get decrypt working.) (您仍然有一个不安全的IV,但是至少您可以使用解密。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM