将CryptoStream转换为使用声明,当文本少于15个字符时,内存流为空。

问题描述 投票:1回答:1

我在用C#.ReSharper加密文本时遇到了一个奇怪的问题(我同意),建议替换掉C#中的 using 块上的代码。

public object GetEncryptedOrDefault(object value, ICryptoTransform encryptor)
{
    if (encryptor is null)
    {
        throw new ArgumentNullException(nameof(encryptor));
    }
    var isEncryptionNeeded = value != null;
    if (isEncryptionNeeded)
    {
        using var memoryStream = new MemoryStream();
        using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
        {
            using var writer = new StreamWriter(cryptoStream);
            var valueAsText = value.ToString();
            writer.Write(valueAsText);
        }

        var encryptedData = memoryStream.ToArray();
        var encryptedText = Convert.ToBase64String(encryptedData);
        return encryptedText;
    }

    return default;
}

改成这个简化的代码(注意使用声明而不是代码块)。

public object GetEncryptedOrDefault(object value, ICryptoTransform encryptor)
{
    if (encryptor is null)
    {
        throw new ArgumentNullException(nameof(encryptor));
    }
    var isEncryptionNeeded = value != null;
    if (isEncryptionNeeded)
    {
        using var memoryStream = new MemoryStream();
        using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        using var writer = new StreamWriter(cryptoStream);
        var valueAsText = value.ToString();
        writer.Write(valueAsText);

        var encryptedData = memoryStream.ToArray();
        var encryptedText = Convert.ToBase64String(encryptedData);
        return encryptedText;
    }

    return default;
}

好吧... 第一段代码很好用,它能够对文本进行加密,但是第二段代码就不行了! encryptedData 是空的,因此它产生一个空的 encryptedText.

我看不出有什么问题 为什么会这样?


更新1多亏了Emanuel的回答,我可以让它只在要加密的文本大于15个字符时工作。这真的很奇怪. 在15个字符以下,只有使用 "老式的 "using block的代码才能工作,而不是使用using声明的代码。

我重现了这个问题 在Github上的这个样本仓库中.

即使这个问题与AesManaged有关(我不知道)。缘何前功尽弃 对于任何15个字符或以下的文本?

这就是代码。

class Program
{
    static void Main(string[] args)
    {
        var encryptor = GetEncryptor();
        var text = "Under 15 characters this text causes problems";
        while (text.Length >= 0)
        {
            text = text.Substring(0, text.Length - 1);
            Console.WriteLine($"Result Method A with {text.Length} characters: {GetWorkingEncrypted(text, encryptor)}");
            Console.WriteLine($"Result Method B with {text.Length} characters: {GetNonWorkingEncrypted(text, encryptor)}");
        }
    }

    private static string GetWorkingEncrypted(string text, ICryptoTransform encryptor)
    {
        using var memoryStream = new MemoryStream();
        using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
        {
            using var writer = new StreamWriter(cryptoStream);
            writer.Write(text);
            writer.Flush();
        }

        var encryptedData = memoryStream.ToArray();
        if (encryptedData.Length == 0)
        {
            throw new Exception($"Encrypted data is 0 for text {text}");
        }
        var encryptedText = Convert.ToBase64String(encryptedData);
        return encryptedText;
    }

    private static string GetNonWorkingEncrypted(string text, ICryptoTransform encryptor)
    {
        using var memoryStream = new MemoryStream();
        using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        using var writer = new StreamWriter(cryptoStream);
        writer.Write(text);
        writer.Flush();

        var encryptedData = memoryStream.ToArray();
        if (encryptedData.Length == 0)
        {
            throw new Exception($"Encrypted data is 0 for text \"{text}\" with length {text.Length}");
        }
        var encryptedText = Convert.ToBase64String(encryptedData);
        return encryptedText;
    }

    private static ICryptoTransform GetEncryptor()
    {
        var aesManaged =
            new AesManaged
            {
                Padding = PaddingMode.PKCS7
            };

        return aesManaged.CreateEncryptor();
    }
}

这是执行的结果

Result Method A with 44 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3937Peo255iHRylA9DF0lf4K+
Result Method B with 44 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 43 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934Pwaqyce+T6SG3WaqnzNRt
Result Method B with 43 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 42 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3936tIT0560Lky1gz3FXKHU3Y
Result Method B with 42 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 41 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934xr6AiKuSxRet/e8iWhLEV
Result Method B with 41 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 40 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3937gCR2Lf9zQClOlCFw51dVo
Result Method B with 40 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 39 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3936OjZ4HEtzkcIjVMUJcDzum
Result Method B with 39 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 38 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3936ti1b7pskEFKb2zJrRkVaD
Result Method B with 38 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 37 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3937rKO73A+OiHd1aAMqOd3Df
Result Method B with 37 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 36 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934/BNp0BiYZPRMUUiODp/kb
Result Method B with 36 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 35 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3935sjuGp/uE4fVOn26J1ESzH
Result Method B with 35 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 34 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+39360AAj7hDLcnbMZH7aknpDl
Result Method B with 34 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 33 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3935EfO82m/jR81he3Jt4z1h+
Result Method B with 33 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 32 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934zeVj3CoE5YIFK8/g07QmH
Result Method B with 32 characters: /ppBS775B1KRShB+QKTLZJH/fCQbNFhCvzfbFP+3934=
Result Method A with 31 characters: /ppBS775B1KRShB+QKTLZCRCNZXU9Ndp7uKLJkUXFsw=
Result Method B with 31 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 30 characters: /ppBS775B1KRShB+QKTLZJ1WbVjggwJM3uOTZ2dHx5c=
Result Method B with 30 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 29 characters: /ppBS775B1KRShB+QKTLZDiI785bQRbNeZX2aNFQvZo=
Result Method B with 29 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 28 characters: /ppBS775B1KRShB+QKTLZMmLT/ycIHWz0sjPsdfg/ys=
Result Method B with 28 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 27 characters: /ppBS775B1KRShB+QKTLZJDekWQLgx9tTUE/59ldSqs=
Result Method B with 27 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 26 characters: /ppBS775B1KRShB+QKTLZKIkr5xwCc8SS9eSnw715vk=
Result Method B with 26 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 25 characters: /ppBS775B1KRShB+QKTLZFAtZM8oTV/uTBb6OccqErc=
Result Method B with 25 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 24 characters: /ppBS775B1KRShB+QKTLZD5BAXR9qZav1rG5NnaLEQQ=
Result Method B with 24 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 23 characters: /ppBS775B1KRShB+QKTLZFof3ATUQWJqiZ2wZ6Gj4Vc=
Result Method B with 23 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 22 characters: /ppBS775B1KRShB+QKTLZNWhgIhTYyERb74rKEl8bos=
Result Method B with 22 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 21 characters: /ppBS775B1KRShB+QKTLZIsgSoHGJT3XysDLqmV9Bi0=
Result Method B with 21 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 20 characters: /ppBS775B1KRShB+QKTLZO0ZdC9DzISByS5T1Rx4hQ4=
Result Method B with 20 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 19 characters: /ppBS775B1KRShB+QKTLZBFfUwWYJ5ECKF2JexKf8Xk=
Result Method B with 19 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 18 characters: /ppBS775B1KRShB+QKTLZNkZyUqqwkELWI4JN14M2RE=
Result Method B with 18 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 17 characters: /ppBS775B1KRShB+QKTLZOKdO3s345tAlCrN+q3QV68=
Result Method B with 17 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 16 characters: /ppBS775B1KRShB+QKTLZE6HtWd1ZLwZMvy3E9Bm5CI=
Result Method B with 16 characters: /ppBS775B1KRShB+QKTLZA==
Result Method A with 15 characters: OMMFxti/svtQ/Z5fqaLaEg==
Unhandled exception. System.Exception: Encrypted data is 0 for text "Under 15 charac" with length 15
   at IssueEncryptionStreamEmpty.Program.GetNonWorkingEncrypted(String text, ICryptoTransform encryptor) in /media/sasw/Data/src/issue-encryption-stream-empty/Program.cs:line 53
   at IssueEncryptionStreamEmpty.Program.Main(String[] args) in /media/sasw/Data/src/issue-encryption-stream-empty/Program.cs:line 17

Process finished with exit code 134.

c# encryption aes memorystream cryptostream
1个回答
1
投票

因为你的 writer 不再局限于 cryptoStream的使用块,现在它在从你的函数返回之前就被处理掉了(而不是在使用 cryptoStream的作用域结束)。) 但它并没有将其内容刷新到流中,因为你没有调用 Flush 其上,及其 AutoFlush 属性的默认值为 false.

public object GetEncryptedOrDefault(object value, ICryptoTransform encryptor)
{
    if (encryptor is null)
    {
        throw new ArgumentNullException(nameof(encryptor));
    }
    var isEncryptionNeeded = value != null;
    if (isEncryptionNeeded)
    {
        using var memoryStream = new MemoryStream();
        using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
        using var writer = new StreamWriter(cryptoStream);
        var valueAsText = value.ToString();
        writer.Write(valueAsText);
        writer.Flush();

        var encryptedData = memoryStream.ToArray();
        var encryptedText = Convert.ToBase64String(encryptedData);
        return encryptedText;
    }

    return default;
}

在你的第一个例子中, writer 在用完它所写入的流之前就被处理掉了(从而冲刷了它的缓冲区),所以当试图访问它时,流并不是空的。

更新。在查看 CryptoStream.Dispose的源代码,我注意到了一个对 FlushFinalBlock,这就是输出不匹配的原因。

至于输入长度问题。encryptor.InputBlockSize 等于16,这也是为什么写一篇15个字符或更少的文字,而不调用 FlushFinalBlock (注意 CryptoStream.Flush 是一个no-op)的结果是一个空流。

所以,再次工作的代码变成了。

private static string GetWorkingEncrypted(string text, ICryptoTransform encryptor)
{
    using var memoryStream = new MemoryStream();
    using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
    using var writer = new StreamWriter(cryptoStream);
    writer.Write(text);
    writer.Flush();
    cryptoStream.FlushFinalBlock();

    var encryptedData = memoryStream.ToArray();
    var encryptedText = Convert.ToBase64String(encryptedData);
    return encryptedText;
}
© www.soinside.com 2019 - 2024. All rights reserved.