【问题标题】:Using AES encryption in .NET - CryptographicException saying the padding is invalid and cannot be removed在 .NET 中使用 AES 加密 - CryptographicException 表示填充无效且无法删除
【发布时间】:2011-05-31 12:44:42
【问题描述】:

我用 C# 编写了一些 AES 加密代码,但无法正确加密和解​​密。如果我输入“test”作为密码并且“这个数据必须对所有人保密!”我收到以下异常:

System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
   at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
   at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
   at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
   at System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Stream.Dispose()
   ...

如果我输入的内容少于 16 个字符,我将不会得到任何输出。

我相信我需要在加密中进行一些特殊处理,因为 AES 是一种分组密码,但我不确定那是什么,而且我无法在网络上找到任何示例来说明如何操作。这是我的代码:

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

public static class DatabaseCrypto
{
    public static EncryptedData Encrypt(string password, string data)
    {
        return DatabaseCrypto.Transform(true, password, data, null, null) as EncryptedData;
    }

    public static string Decrypt(string password, EncryptedData data)
    {
        return DatabaseCrypto.Transform(false, password, data.DataString, data.SaltString, data.MACString) as string;
    }

    private static object Transform(bool encrypt, string password, string data, string saltString, string macString)
    {
        using (AesManaged aes = new AesManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;
            int key_len = aes.KeySize / 8;
            int iv_len = aes.BlockSize / 8;
            const int salt_size = 8;
            const int iterations = 8192;

            byte[] salt = encrypt ? new byte[salt_size] : Convert.FromBase64String(saltString);
            if (encrypt)
            {
                new RNGCryptoServiceProvider().GetBytes(salt);
            }

            byte[] bc_key = new Rfc2898DeriveBytes("BLK" + password, salt, iterations).GetBytes(key_len);
            byte[] iv = new Rfc2898DeriveBytes("IV" + password, salt, iterations).GetBytes(iv_len);
            byte[] mac_key = new Rfc2898DeriveBytes("MAC" + password, salt, iterations).GetBytes(16);

            aes.Key = bc_key;
            aes.IV = iv;

            byte[] rawData = encrypt ? Encoding.UTF8.GetBytes(data) : Convert.FromBase64String(data);

            using (ICryptoTransform transform = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor())
            using (MemoryStream memoryStream = encrypt ? new MemoryStream() : new MemoryStream(rawData))
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, encrypt ? CryptoStreamMode.Write : CryptoStreamMode.Read))
            {
                if (encrypt)
                {
                    cryptoStream.Write(rawData, 0, rawData.Length);

                    return new EncryptedData(salt, mac_key, memoryStream.ToArray());
                }
                else
                {
                    byte[] originalData = new byte[rawData.Length];
                    int count = cryptoStream.Read(originalData, 0, originalData.Length);

                    return Encoding.UTF8.GetString(originalData, 0, count);
                }
            }
        }
    }
}

public class EncryptedData
{
    public EncryptedData()
    {
    }

    public EncryptedData(byte[] salt, byte[] mac, byte[] data)
    {
        this.Salt = salt;
        this.MAC = mac;
        this.Data = data;
    }

    public EncryptedData(string salt, string mac, string data)
    {
        this.SaltString = salt;
        this.MACString = mac;
        this.DataString = data;
    }

    public byte[] Salt
    {
        get;
        set;
    }

    public string SaltString
    {
        get { return Convert.ToBase64String(this.Salt); }
        set { this.Salt = Convert.FromBase64String(value); }
    }

    public byte[] MAC
    {
        get;
        set;
    }

    public string MACString
    {
        get { return Convert.ToBase64String(this.MAC); }
        set { this.MAC = Convert.FromBase64String(value); }
    }

    public byte[] Data
    {
        get;
        set;
    }

    public string DataString
    {
        get { return Convert.ToBase64String(this.Data); }
        set { this.Data = Convert.FromBase64String(value); }
    }
}

    static void ReadTest()
    {
        Console.WriteLine("Enter password: ");
        string password = Console.ReadLine();

        using (StreamReader reader = new StreamReader("aes.cs.txt"))
        {
            EncryptedData enc = new EncryptedData();
            enc.SaltString = reader.ReadLine();
            enc.MACString = reader.ReadLine();
            enc.DataString = reader.ReadLine();

            Console.WriteLine("The decrypted data was: " + DatabaseCrypto.Decrypt(password, enc));
        }
    }

    static void WriteTest()
    {
        Console.WriteLine("Enter data: ");
        string data = Console.ReadLine();
        Console.WriteLine("Enter password: ");
        string password = Console.ReadLine();

        EncryptedData enc = DatabaseCrypto.Encrypt(password, data);

        using (StreamWriter stream = new StreamWriter("aes.cs.txt"))
        {
            stream.WriteLine(enc.SaltString);
            stream.WriteLine(enc.MACString);
            stream.WriteLine(enc.DataString);

            Console.WriteLine("The encrypted data was: " + enc.DataString);
        }
    }

【问题讨论】:

    标签: .net cryptography aes


    【解决方案1】:

    在需要填充的模式(如 CBC)中使用像 AES 这样的分组密码时,您必须注意输出始终是分组大小的倍数。为了实现这一点,像 PKCS7 这样的填充模式将在加密过程结束时向密码添加一些字节。但是你必须让加密器知道何时结束。为此,您所要做的就是插入语句

    cryptoStream.FlushFinalBlock();  
    

    之后

    cryptoStream.Write(rawData, 0, rawData.Length);
    

    PS:

    也许它只是为了调试,但你的盐生成方法每次都会生成完全相同的盐。

    【讨论】:

    • 哇,就这么简单,嗯?我认为这可能与 FlushFinalBlock 有关,但我不确定。我想我之前添加了它,但也许我只是输入了错误的密码。我应该生成一些随机单元测试。 :) 我也知道盐是一样的——我还不确定如何生成随机盐,但我在编写该代码后找到了RNGCryptoServiceProvider。感谢您指出这一点。
    • msdn 和 stackoverflow 中的许多示例都不太健康,而这个答案挽救了我的一天。 FlushFinalBlock()。哇。
    • 正在将 CryptoStream 包装在 StreamWriter 中...cryptoStream.FlushFinalBlock(); 无论如何都需要调用。谢谢 - 这大大节省了时间!
    • 由于某种原因,这种方法在 C# 4.0/4.5 中对我不起作用 - 必须在每个 using( ) 之后放置花括号。不需要.FlushFinalBlock( ) 电话。见stackoverflow.com/questions/29809687/#29835039
    • @Astrogator:当执行超出using 块时,调用CryptoStream 对象的Dispose 方法,最终导致Close 方法和FlushFinalBlock 方法被调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 2016-02-02
    • 1970-01-01
    • 1970-01-01
    • 2018-07-17
    • 1970-01-01
    相关资源
    最近更新 更多