我正在尝试保护我的Windows Service项目免受内存抓取器的侵害。 我正在尝试存储一些极其敏感的数据。例如,让我们使用信用卡号"1234-5678-1234-5678"。 我需要能够将此数据存储到两个主要的 C# 对象中,并在完成后将其删除。
我已经能够从我在 StackOverflow 示例的帮助下构建的自定义类中存储和删除敏感数据:
public void DestorySensitiveData()
{
try
{
unsafe
{
int iDataSize = m_chSensitiveData.Length;
byte[] clear = new byte[iDataSize];
for (int i = 0; i < clear.Length; i++)
{
clear[i] = 70; //fixed ascii character
}
fixed (char* ptr = &m_chSensitiveData[0])
{
System.Runtime.InteropServices.Marshal.Copy(clear, 0, new IntPtr(ptr), iDataSize);
}
}
}
catch (Exception e)
{
}
}
通过使用 Char 数组而不是字符串,我已经能够覆盖/擦除内存中的敏感数据。 如果没有这个类,.NET 和字符串的内存管理可以并且会将此数据复制到需要它的任何位置。 即使我的类超出范围或我试图调用 dispose 时,我也可以运行内存抓取并以纯文本形式查找我的敏感数据,就像您阅读本段一样简单。
我正在寻找流的实践或方法。 我需要能够将敏感数据移入/移出System.Diagnostics.Process()甚至文件。 当我使用它时,数据是纯文本的"可以"的 - 当我完成使用它时,它不能保留在内存中的任何地方。 这包括通过内存管理或垃圾回收创建的副本。
不起作用的示例:
- Strings/StringBuilders:它们不断自我更新,到处都是碎片。
- SecureString:类在纸上和 MSDN 上看起来很棒,但是您必须使用字符串来加载和卸载此对象(请参阅项目 #1)
- 数组列表。 真的是一个二维的字符串问题。
我甚至尝试创建一个单独的 EXE 项目并在进程中执行我的内存敏感工作。 当我不得不使输入/输出流过载时,我有点。
流似乎到处都是复制。 在我的应用程序中,我创建了一个流,将敏感数据加载到其中,然后完成。 我加载了一次敏感数据,执行完成后,我通过原始内存找到了大约十几个完整的副本。
以前有没有人在 .NET 中遇到过类似的问题? 它们是如何解决的?
更新: 1/16/15:
我必须通过在同一台计算机上运行的软件发送信用卡进行处理:
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.WorkingDirectory = @"<full working path>";
proc.StartInfo.FileName = @"<vendor EXE>";
proc.StartInfo.Arguments = "<args>";
proc.Start();
//pTran.ProcessorData.ProcID = proc.Id;
StreamWriter myWriter = proc.StandardInput;
StreamReader myReader = proc.StandardOutput;
// Send request API to Protobase
//I need a process to send the data
//even if I use a protected object to hold it,
//the 'myWriter' cannot be controlled
**myWriter.Write(sSensitiveData);**
myWriter.Flush();
myWriter.Close();
// Read Response API
string sResponseData = myReader.ReadToEnd();
Console.Write(sResponseData);
proc.WaitForExit();
proc.Close();
myReader.Close();
这就是为什么我询问 Stream 类并销毁它们包含的内存的原因。 与我们存储和比较为哈希的密码不同,此数据必须可由我们的供应商读取。
@Greg:我喜欢你在我的流程和供应商之间相互同意的加密的想法。 我已经在研究这个角度了。
除了加密数据并允许 GC 到处复制片段之外,有没有办法从 Stream 类型类中清理内存?
首先 - 谢谢大家的想法和帮助!
我们不能使用安全字符串,因为我们要向第三方发送数据。 由于 System.Process() 只允许流,因此无法使用 char 数组。 然后我们构建了一个 c++ 中间层来接收加密数据,使用 STARTUPINFO 进行解密和传输。 我们最初的解决方案使用流进行 I/O,这是一个"几乎完美"的解决方案,因为我们只剩下内存中的 c++ 流。
我们联系了Microsoft寻求帮助,他们提出了一个几乎相同的解决方案,但使用句柄/管道而不是流,因此您可以关闭管道的两端。
下面是示例,虚拟代码。 希望这对其他人有所帮助!
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <WinBase.h>
#include <wincrypt.h>
int _tmain(int argc, _TCHAR* argv [])
{
HANDLE hFile = INVALID_HANDLE_VALUE;
STARTUPINFO StartInfo;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
BOOL fResult;
HANDLE hReadPipe = NULL;
HANDLE hWritePipe = NULL;
HANDLE hSensitiveReadPipe = NULL;
HANDLE hSensitiveWritePipe = NULL;
DWORD dwDataLength = 0;
DWORD dwWritten;
DWORD dwRead;
char responseData[1024];
char *sensitiveDataChar = NULL;
char *otherStuff = NULL;
char *sensitiveDataCharB = NULL;
DWORD dwSectorsPerCluster;
DWORD dwBytesPerSector;
DWORD dwNumberOfFreeClusters;
DWORD dwTotalNumberOfClusters;
SIZE_T SizeNeeded;
__try
{
sensitiveDataChar = "YourSensitiveData";
int lastLoc = 0;
ZeroMemory(&sa, sizeof(sa));
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
// Create Pipe to send sensitive data
fResult = CreatePipe(&hSensitiveReadPipe, &hSensitiveWritePipe, &sa, 0);
if (!fResult)
{
printf("CreatePipe failed with %d", GetLastError());
__leave;
}
// Create Pipe to read back response
fResult = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
if (!fResult)
{
printf("CreatePipe failed with %d", GetLastError());
__leave;
}
// Initialize STARTUPINFO structure
ZeroMemory(&StartInfo, sizeof(StartInfo));
StartInfo.cb = sizeof(StartInfo);
StartInfo.dwFlags = STARTF_USESTDHANDLES;
StartInfo.hStdInput = hSensitiveReadPipe;
StartInfo.hStdOutput = hWritePipe;
StartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
ZeroMemory(&pi, sizeof(pi));
// Launch third party app
fResult = CreateProcess(_T("c:\temp\ThirdParty.exe"), NULL, NULL, NULL, TRUE, 0, NULL, _T("c:\temp"), &StartInfo, &pi);
if (!fResult)
{
printf("CreateProcess failed with %d", GetLastError());
__leave;
}
dwDataLength = strlen(sensitiveDataChar);
// Write to third party's standard input
fResult = WriteFile(hSensitiveWritePipe, sensitiveDataChar, dwDataLength, &dwWritten, NULL);
if (!fResult)
{
printf("WriteFile failed with %d", GetLastError());
__leave;
}
FlushFileBuffers(hSensitiveWritePipe);
CloseHandle(hSensitiveReadPipe);
DWORD dwLength = 1024;
printf("Waiting...n");
// Read from third party's standard out
fResult = ReadFile(hReadPipe, responseData, dwLength, &dwRead, NULL);
if (!fResult)
{
printf("ReadFile failed with %dn", GetLastError());
__leave;
}
responseData[dwRead] = ' ';
printf("%sn", responseData);
}
__finally
{
// Clean up
ZeroMemory(responseData, sizeof(responseData));
if (hFile != INVALID_HANDLE_VALUE)
{
FlushFileBuffers(hFile);
CloseHandle(hFile);
}
if (hSensitiveWritePipe != NULL)
{
FlushFileBuffers(hSensitiveWritePipe);
CloseHandle(hSensitiveWritePipe);
}
if (hSensitiveReadPipe != NULL)
{
CloseHandle(hSensitiveReadPipe);
}
if (hReadPipe != NULL) CloseHandle(hReadPipe);
if (hWritePipe != NULL) CloseHandle(hWritePipe);
}
}
您应该查看 Microsoft .Net 中内置SecureString
,信息可以在这里找到。它允许您加密内存中的数据,作为单个字符分开,并且可以在调用IDispose
时立即处置,此处的文档。
System.String 类的实例既是不可变的,又没有 需要更长的时间,无法以编程方式安排垃圾 收集;也就是说,实例在创建后是只读的,并且 无法预测何时将从中删除实例 计算机内存。因此,如果 String 对象包含敏感 密码、信用卡号或个人数据等信息, 信息在使用后存在泄露的风险 因为您的应用程序无法从计算机内存中删除数据。
重要说明 此类型实现 ID是可置的 接口。使用该类型后,应释放 直接或间接地。要直接处理该类型, 在 try/catch 块中调用其 Dispose 方法。处理它 间接地使用语言结构,例如使用(在 C# 中)或使用 (在 Visual Basic 中)。有关详细信息,请参阅"使用对象 实现 IDisposable"部分,以解决接口主题。
SecureString 对象类似于 String 对象,因为它具有 文本值。但是,安全字符串对象的值为 自动加密,可以修改,直到您的应用程序标记 它是只读的,可以通过以下任一方式从计算机内存中删除 您的应用程序或 .NET 框架垃圾回收器
SecureString
的前提如下:
- 没有成员进行检查、比较和/或转换。
- 完成后实施
IDispose
。 - 本质上强制对象进入加密
char []
。
此设计旨在确保不会发生意外或恶意暴露。
我建议要么加密敏感数据,永远不要维护纯文本,要么可以使用安全字符串(http://msdn.microsoft.com/en-us/library/system.security.securestring%28v=vs.110%29.aspx)。
您可以使用空字符串初始化安全字符串,然后使用 AppendChar
生成字符串。 这避免了保留字符串的纯文本的需要。