通过 File() 返回 Stream 会导致未来的操作抛出 "the process cannot access the file path because it is being used by another process"
Returning Stream via File() causes future operations to throw "the process cannot access the file path because it is being used by another process"
为了在 return 将文件发送给客户端时减少内存使用,同时解密,我们使用了流。
直到出现一个怪癖,即当您将相同的文件上传回服务器时(例如,当客户端修改它时),它一直运行良好。它导致.net core 抛出“该进程无法访问文件路径,因为它正被另一个进程使用”。
由于该系统仍在开发中,我不确定这是否是 运行 处于调试模式而非发布模式的应用程序的怪癖。尽管我将代码构建到发布版中,但仍然收到相同的错误。
据我所知,return 流是如何工作的,它应该自动处理流。
创建流的第一个方法包含以下内容:
return (await Decrypt(File.OpenRead(path), AesKey, AesIv), contentType);
解密方法然后执行以下操作:
public static async Task<MemoryStream> Decrypt(FileStream data, string key, string iv)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
return await PerformCryptography(data, decryptor);
}
然后调用加密方法:
private static async Task<MemoryStream> PerformCryptography(FileStream data, ICryptoTransform cryptoTransform)
{
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write);
await data.CopyToAsync(cryptoStream);
cryptoStream.FlushFinalBlock();
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
这 return 将链备份到 return 以下控制器:
return File(file, contentType, fileName);
当我开发这个时,似乎在 using 中包装这些中的任何一个都会导致对象处置异常,但是我可能做错了什么。
有人知道如何解决这样的问题吗?
将 FileStream
的内容复制到 CryptoStream
后,您需要对其进行处置。一种简单的方法是在 Decrypt
中使用 using
块。请注意 await
在 using
块内,因此 FileStream
在 await
完成之前不会被释放:
public static async Task<MemoryStream> Decrypt(FileStream data, string key, string iv)
{
using (data)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
return await PerformCryptography(data, decryptor);
}
}
另一种方法是在 FileStream
上以读取模式打开 CryptoStream
。不是将数据复制到 CryptoStream
(然后 CryptoStream
将该数据写入 MemoryStream
),而是从 CryptoStream
(以及 CryptoStream
根据需要从 FileStream
中提取数据):
private static async Task<MemoryStream> PerformCryptography(FileStream data, ICryptoTransform cryptoTransform)
{
MemoryStream memoryStream = new MemoryStream();
using (CryptoStream cryptoStream = new CryptoStream(fileStream, cryptoTransform, CryptoStreamMode.Read))
{
await cryptoStream.CopyToAsync(memoryStream);
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
这让您可以处理 CryptoStream
(您应该处理它,因为它是 IDisposable
)而不是调用 FlushFinalBlock()
,这样会更整洁一些。处置它也会处置底层 FileStream
.
如果您处于不需要在 Decrypt
return 流中搜索的情况(遗憾的是,如果您 return 从 ASP.NET Core 和 File(...)
) 中获取它,你可以 return CryptoStream
。这意味着解密是在读取该流时发生的,而不是预先发生的,这可能不是您想要的:
public static Stream Decrypt(FileStream data, string key, string iv)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
return new CryptoStream(data, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read);
}
我不喜欢你派生密钥和 IV 的方式。 ASCII 仅支持 0 到 127 之间的值,因此您的密钥空间只有应有大小的一半:您实际上使用的是 AES-128 而不是 AES-256。同样,您的 IV 是应有大小的一半。使用 PBKDF2 等密钥派生函数将(长)纯文本密钥转换为适当的 256 位二进制密钥。
你的IV也很可疑。请记住,您使用特定密钥加密的所有内容都需要具有唯一的 IV。这非常重要。不要重复使用静脉注射!!通常的做法是在加密某些东西时随机生成一个 IV,并将其写为密文的前几个字节(如果 IV 是 public 就可以了,它必须是唯一的)。然后你可以在解密时提取它。
您的填充也有点可疑:这意味着 encryption/decryption 进程删除了明文中任何尾随的 0。使用 PKCS7 之类的东西会好得多。
为了在 return 将文件发送给客户端时减少内存使用,同时解密,我们使用了流。 直到出现一个怪癖,即当您将相同的文件上传回服务器时(例如,当客户端修改它时),它一直运行良好。它导致.net core 抛出“该进程无法访问文件路径,因为它正被另一个进程使用”。
由于该系统仍在开发中,我不确定这是否是 运行 处于调试模式而非发布模式的应用程序的怪癖。尽管我将代码构建到发布版中,但仍然收到相同的错误。
据我所知,return 流是如何工作的,它应该自动处理流。
创建流的第一个方法包含以下内容:
return (await Decrypt(File.OpenRead(path), AesKey, AesIv), contentType);
解密方法然后执行以下操作:
public static async Task<MemoryStream> Decrypt(FileStream data, string key, string iv)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
return await PerformCryptography(data, decryptor);
}
然后调用加密方法:
private static async Task<MemoryStream> PerformCryptography(FileStream data, ICryptoTransform cryptoTransform)
{
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write);
await data.CopyToAsync(cryptoStream);
cryptoStream.FlushFinalBlock();
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
这 return 将链备份到 return 以下控制器:
return File(file, contentType, fileName);
当我开发这个时,似乎在 using 中包装这些中的任何一个都会导致对象处置异常,但是我可能做错了什么。
有人知道如何解决这样的问题吗?
将 FileStream
的内容复制到 CryptoStream
后,您需要对其进行处置。一种简单的方法是在 Decrypt
中使用 using
块。请注意 await
在 using
块内,因此 FileStream
在 await
完成之前不会被释放:
public static async Task<MemoryStream> Decrypt(FileStream data, string key, string iv)
{
using (data)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
return await PerformCryptography(data, decryptor);
}
}
另一种方法是在 FileStream
上以读取模式打开 CryptoStream
。不是将数据复制到 CryptoStream
(然后 CryptoStream
将该数据写入 MemoryStream
),而是从 CryptoStream
(以及 CryptoStream
根据需要从 FileStream
中提取数据):
private static async Task<MemoryStream> PerformCryptography(FileStream data, ICryptoTransform cryptoTransform)
{
MemoryStream memoryStream = new MemoryStream();
using (CryptoStream cryptoStream = new CryptoStream(fileStream, cryptoTransform, CryptoStreamMode.Read))
{
await cryptoStream.CopyToAsync(memoryStream);
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
这让您可以处理 CryptoStream
(您应该处理它,因为它是 IDisposable
)而不是调用 FlushFinalBlock()
,这样会更整洁一些。处置它也会处置底层 FileStream
.
如果您处于不需要在 Decrypt
return 流中搜索的情况(遗憾的是,如果您 return 从 ASP.NET Core 和 File(...)
) 中获取它,你可以 return CryptoStream
。这意味着解密是在读取该流时发生的,而不是预先发生的,这可能不是您想要的:
public static Stream Decrypt(FileStream data, string key, string iv)
{
Aes aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(key);
aes.IV = Encoding.ASCII.GetBytes(iv);
return new CryptoStream(data, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read);
}
我不喜欢你派生密钥和 IV 的方式。 ASCII 仅支持 0 到 127 之间的值,因此您的密钥空间只有应有大小的一半:您实际上使用的是 AES-128 而不是 AES-256。同样,您的 IV 是应有大小的一半。使用 PBKDF2 等密钥派生函数将(长)纯文本密钥转换为适当的 256 位二进制密钥。
你的IV也很可疑。请记住,您使用特定密钥加密的所有内容都需要具有唯一的 IV。这非常重要。不要重复使用静脉注射!!通常的做法是在加密某些东西时随机生成一个 IV,并将其写为密文的前几个字节(如果 IV 是 public 就可以了,它必须是唯一的)。然后你可以在解密时提取它。
您的填充也有点可疑:这意味着 encryption/decryption 进程删除了明文中任何尾随的 0。使用 PKCS7 之类的东西会好得多。