c#并从内存中删除敏感数据和所有垃圾回收副本

时间:2015-01-15 22:05:43

标签: c# string security stream garbage-collection

我正在尝试保护我的Windows服务项目免受内存刮刀的影响。我试图存储一些极其敏感的数据。让我们使用信用卡号码" 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和Strings的内存管理可以并且可以将这些数据复制到需要它的任何地方。即使我的课程超出了范围,或者我试图调用dispose,我也可以运行内存清理并以纯文本形式查找我的敏感数据,就像阅读本段一样简单。

我正在寻找Streams的练习或方法。我需要能够将敏感数据移入/移出System.Diagnostics.Process()甚至文件。它的确定&#39;当我使用它时,数据是纯文本的 - 当我使用它时,它在内存中无法保留。这包括由内存管理或垃圾回收制作的副本。

DONT工作的例子:

  • Strings / StringBuilders:它们不断重新创建,无处不在。
  • SecureString:Class在纸上和MSDN上都很棒。但是你必须使用一个字符串来加载和卸载这个对象(见第1项)
  • 的ArrayList。真的是二维的字符串问题。

我甚至尝试创建一个单独的EXE项目,并在流程中执行对内存敏感的工作。当我不得不重载输入/输出流时,我得到了一点。

Streams似乎被复制到了整个地方。在我的应用程序中,我创建了一个流,将我的敏感数据加载到其中,然后完成。我加载了我的敏感数据ONCE,并在执行完成后通过原始内存找到了大约十几个完整的副本。

之前有没有人在.NET中遇到过类似的问题?他们是如何解决的?

更新:2015年1月16日:

我必须通过在同一台机器上运行的软件发送信用卡进行处理:

        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类型中擦除内存?

3 个答案:

答案 0 :(得分:1)

首先 - 谢谢大家的想法和帮助!

我们无法使用SecureString,因为我们正在向第三方发送数据。由于System.Process()仅允许Streams,因此无法使用Char Arrays。然后我们构建了一个c ++ MiddleLayer来接收加密数据,使用STARTUPINFO进行解密和传输。我们的初始解决方案使用了I / O流,这是一个“几乎完美”的解决方案,因为我们只剩下内存中的c ++流。

我们联系了微软寻求帮助,他们建议使用几乎相同的解决方案,但是使用Handles / PIPES代替流,因此您可以关闭管道的两端。

这是示例,dummied代码。希望这有助于其他人!

#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 %d\n", GetLastError());
        __leave;
    }
    responseData[dwRead] = '\0';

    printf("%s\n", 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);


}

}

答案 1 :(得分:0)

我建议加密敏感数据,不要使用SecureString(http://msdn.microsoft.com/en-us/library/system.security.securestring%28v=vs.110%29.aspx)保持纯文本或可能。

您可以使用空字符串初始化SecureString,然后使用AppendChar来构建字符串。这样就不需要保留字符串的纯文本了。

答案 2 :(得分:0)

您应该查看Microsoft .Net中内置的SecureString,可以找到信息here。它允许您加密内存中的数据,以单个字符分隔,并在您致电IDispose,文档here时立即处理。

  
      
  • System.String类的实例既是不可变的,也不是   需要更长时间,无法以编程方式安排垃圾   采集;也就是说,实例在创建后是只读的   无法预测何时将从中删除实例   电脑记忆。因此,如果String对象包含敏感   密码,信用卡号或个人数据等信息,   信息在使用后可能会被泄露   因为您的应用程序无法从计算机内存中删除数据。

  •   
  • 重要此类型实现IDisposable   接口。当您使用完该类型后,您应该进行处理   直接或间接地要直接处理该类型,   在try / catch块中调用它的Dispose方法。处理它   间接地,使用语言构造,例如使用(在C#中)或使用   (在Visual Basic中)。有关更多信息,请参阅“使用对象   在IDisposable接口主题中实现IDisposable“部分。

  •   
  • SecureString对象类似于String对象,因为它具有   文字价值。但是,SecureString对象的值是   自动加密,可以修改,直到您的应用程序标记   它是只读的,可以通过任何一个从计算机内存中删除   您的应用程序或.NET Framework垃圾收集器

  •   

SecureString的前提如下:

  • 没有会员可以检查,比较和转换。
  • 完成后实施IDispose
  • 实质上强制对象加密char []

此设计旨在确保不会发生意外或恶意曝光