AES 加密 - Encryption/Decryption 添加额外字节

AES Encryption - Encryption/Decryption Adding Extra Bytes

我正在使用框架的对象为 encryption/decryption 编写一个简单的库。方法如下:

public static byte[] Encrypt(byte[] key, byte[] vector, byte[] input)
{
    if(key.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty key");

    if (vector.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty vector");

    if (input.Length == 0)
        throw new ArgumentException("Cannot encrypt empty input");

    var unencryptedBytes = input;

    using(AesCryptoServiceProvider aes = new AesCryptoServiceProvider {Key = key, IV = vector})
    using(ICryptoTransform encryptor = aes.CreateEncryptor())
    using (MemoryStream ms = new MemoryStream())
    using (CryptoStream writer = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
    {
        writer.Write(unencryptedBytes, 0, unencryptedBytes.Length);
        writer.FlushFinalBlock();
        var byteArray = ms.ToArray();

        if(byteArray.Length == 0)
            throw new Exception("Attempted to encrypt but encryption resulted in a byte array of 0 length.");

        return byteArray;
    }
}


public static byte[] Decrypt(byte[] key, byte[] vector, byte[] encrypted)
{
    if (key.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty key");

    if (vector.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty vector");

    if (encrypted == null || encrypted.Length == 0)
        throw new ArgumentException("Cannot decrypt empty or null byte array");

    byte[] unencrypted;

    using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
    using (ICryptoTransform decryptor = aes.CreateDecryptor(key, vector))
    using (MemoryStream ms = new MemoryStream(encrypted))
    using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
    {

        cs.Read(encrypted, 0, encrypted.Length);

        unencrypted =  ms.ToArray();
    }


    return unencrypted;
}

在编码字符串中提供它,例如,如下所示:

var expected = "This is an example message";

var messageBytes = Encoding.UTF8.GetBytes(expected);

输入为(在此示例中)26 个字节。加密后保留为 32 个字节,当将其转换回字符串时,它在末尾附加了随机字符,但在其他方面完全有效,如下所示:

"This is an example message�(4���"

生成新的 vector/key 更改附加到末尾的随机字符。

如何消除这种额外字节行为?

编辑

包括请求的单元测试。

[TestMethod]
public void CanEncryptAndDecryptByteArray()
{
    var expected = "This is an example message";

    var messageBytes = Encoding.UTF8.GetBytes(expected);


    AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
    aes.GenerateIV();
    aes.GenerateKey();


    var byteKey = Convert.ToBase64String(aes.Key);
    var vectorKey = Convert.ToBase64String(aes.IV);

    var key = Convert.FromBase64String("Qcf+3VzYNAfPUCfBO/ePSxCLBLItkVfk8ajK86KYebs=");
    var vector = Convert.FromBase64String("aJNKWP7M2D44jilby6BzGg==");

    var encrypted = EncryptionManager.Encrypt(key, vector, messageBytes);
    var decrypted = EncryptionManager.Decrypt(key, vector, encrypted);

    var actual = Encoding.UTF8.GetString(decrypted);

    Assert.AreEqual(expected, actual);
}

AES 加密是 block encryption 块大小为 16 字节(128 位)[注意:密钥大小可以是 128 位、192 位或 256 位,但数据块大小是 16 字节的乘积]。因此,要使用 AES,生成的加密消息将是 16 的乘积 - 否则不能。

要解密它,您可能还想去掉它引起的额外字节。

  1. 一种方法是在消息中附加 header,说明消息的字节数。然后在解密端,你读这个 header 并减少字节数

    [header = value of 26] + [message]
    
  2. 或者,您可以在加密邮件的末尾附加一些额外的字节

    [message][append with 000000] //to make up the block, such that the message length will be of multiplication of 16
    

以上两种方法,我个人更喜欢第一种,因为它更清晰,而第二种假设你收到的是某种文本模式,不可能在最后连接0000。

我不知道除了上面给出的两种方式之外是否还有解决这个问题的方法 - 隐式(例如使用 CryptoStream)或显式。

AES 加密以 16、24 或 32 字节(或 128、192 或 256 位)块进行加密。所以你得到的数据只是打包到最后的随机数据。

您必须在数据前添加几个字节以表示实际长度:

var length = new byte [] { (byte) ((unencryptedBytes.Length >> 8) & 0xff) ,
    (byte) (unencryptedBytes.Length & 0xff);
writer.Write(length, 0, length.Length); 
writer.Write(unencryptedBytes, 0, unencryptedBytes.Length);
writer.FlushFinalBlock();

然后在reader中检索长度:

unencrypted =  ms.ToArray();

int length = ((int) unencrypted[0] << 8) + unencrypted[1];

var result = new byte[length];

unencrypted.CopyTo(result, 2, 0, length);

return result;

junk-bytes 是因为我们正在读取和写入同一个数组。

new MemoryStream(encrypted)) 告诉地穴从 encrypted 读取。

cs.Read(encrypted, 0, encrypted.Length); 告诉它写入 encrypted

解决方法:

using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
using (ICryptoTransform decryptor = aes.CreateDecryptor(key, vector))
using (MemoryStream ms = new MemoryStream(encrypted))
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
    var decrypted = new byte[encrypted.Length];
    var bytesRead = cs.Read(decrypted, 0, encrypted.Length);    

    return decrypted.Take(bytesRead).ToArray();
}

请注意,我们从数组中获取 bytesRead,而不是整个数组,否则我们将以空字节结束(字符串比较失败但在编辑器中显示相同)