我使用了一个字符串加密/解密类,类似于这里提供的解决方案。
这在.Net 5中运行得很好。
现在我想把我的项目更新到.Net 6。
使用.Net 6时,解密后的字符串会根据输入字符串的长度在某个点被截断。
️为了便于调试/重现我的问题,我创建了一个公共的repro Repository here。
- 加密代码是在标准2.0项目中故意使用的。
- 引用此项目的是.Net 6和.Net 5 Console项目。
这两个方法都使用完全相同的输入"12345678901234567890"
和路径短语"nzv86ri4H2qYHqc&m6rL"
来调用加密方法。
.净5输出:"12345678901234567890"
.净6输出:"1234567890123456"
长度差为4
。
我也看了breaking changes for .Net 6,但找不到引导我找到解决方案的东西。
我很高兴对我的问题有任何建议,谢谢!
加密类
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 = 128;
// 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 = Generate128BitsOfRandomEntropy();
var ivStringBytes = Generate128BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = Aes.Create())
{
symmetricKey.BlockSize = 128;
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] + [16 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 16 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 16 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 = Aes.Create())
{
symmetricKey.BlockSize = 128;
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[] Generate128BitsOfRandomEntropy()
{
var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits.
using (var rngCsp = RandomNumberGenerator.Create())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
调用代码
var input = "12345678901234567890";
var inputLength = input.Length;
var inputBytes = Encoding.UTF8.GetBytes(input);
var encrypted = StringCipher.Encrypt(input, "nzv86ri4H2qYHqc&m6rL");
var output = StringCipher.Decrypt(encrypted, "nzv86ri4H2qYHqc&m6rL");
var outputLength = output.Length;
var outputBytes = Encoding.UTF8.GetBytes(output);
var lengthDiff = inputLength - outputLength;
5条答案
按热度按时间bihw5rsg1#
我在我的**. net6**项目中使用了这两种扩展方法。
6tdlim6h2#
从.net 2.2升级到6后,我也遇到了同样的问题。它不读取整个缓冲区-大多数情况下只读取最多16个字节,因此,只需将其分解为最多16个字节的循环。
这段代码可以帮助:
z4bn682m3#
尝试使用
ReadAtLeast()
gc0ot86w4#
原因是this breaking change:
DeflateStream、GZipStream和CryptoStream在两个方面与典型的Stream.Read和Stream.ReadAsync行为不同:
直到传递给读操作的缓冲区被完全填满或到达流的结尾,它们才完成读操作。
新的行为是:
从.NET 6开始,当对缓冲区长度为N的受影响流类型之一调用Stream.Read或Stream.ReadAsync时,操作在以下情况下完成:
从流中至少读取了一个字节,或者它们 Package 的基础流从对其读取的调用中返回0,表示没有更多的数据可用。
在你的例子中,你会受到影响,因为
Decrypt
方法中的代码:您不需要检查
Read
实际读取了多少字节,以及它是否读取了所有字节。在.NET的早期版本中,你可以避开这一点,因为如前所述,CryptoStream
的行为与其他流不同,而且你的缓冲区长度足以容纳所有数据。但是,现在情况不再是这样了,您需要像检查其他流一样检查它。或者更好-只需使用CopyTo
:或者更好的答案,因为你解密了UTF8文本:
wd2eg0qa5#
我认为你的问题在这里:
Stream.Read
文档:即使尚未到达流的结尾,实现也可以自由地返回比请求的字节少的字节。
因此,对
Read
的单个调用不能保证读取所有可用字节(最多plainTextBytes.Length
)--它有权读取较少数量的字节。.NET 6有许多性能改进,如果这是他们以性能的名义做出的那种权衡,我不会感到惊讶。
你必须做好准备,不断调用
Read
,直到它返回0
,这表明没有更多的数据要返回。然而,使用
StreamReader
要容易得多,它还可以为您处理UTF-8解码。