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

c# and removing sensitive data and all garbage collection copies from memory

我正在尝试保护我的 windows 服务项目免受内存抓取器的侵害。我正在尝试存储一些极其敏感的数据。让我们以信用卡号“1234-5678-1234-5678”为例。有两个主要的 C# 对象,我需要能够将这些数据存储到其中,并在完成后将其删除。

我已经能够存储和删除自定义的敏感数据-class 我在 Whosebug 示例的帮助下构建:

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)
    {

    }
}

通过使用字符数组而不是字符串,我已经能够 override/wipe 内存中的敏感数据。如果没有这个 class,.NET 和字符串的内存管理可以并且会在需要的地方复制这些数据。即使当我的 class 超出范围或我试图调用 dispose 时,我也可以 运行 内存擦除并以纯文本形式找到我的敏感数据,就像您阅读本段一样简单。

我正在寻找 Streams 的实践或方法。我需要能够将敏感数据 to/from 移动到 System.Diagnostics.Process() 甚至文件。它 'ok' 在我使用它时数据是纯文本的——当我使用完它时,它不能保留在内存中的任何地方。这包括由内存管理或垃圾回收生成的副本。

无效的例子:

我什至尝试创建一个单独的 EXE 项目并在该进程中执行我的内存敏感工作。当我不得不使 input/output 流超载时,我有点生气了。

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

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

更新:2015 年 1 月 16 日:

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

        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 classes 并破坏它们包含的内存的原因。与我们以哈希形式存储和比较的密码不同,此数据必须可供我们的供应商读取。

@Greg:我喜欢你关于我的流程和供应商之间相互同意的加密的想法。我已经在研究那个角度了。

除了数据加密和允许 GC 到处复制碎片之外,有没有办法从 Stream 类型中清除内存class?

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

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

你应该看看SecureString 内置于Microsoft .Net 中,可以找到信息here. It allows you to encrypt the data in memory, separated as individual character, and can instantly be disposed when you call IDispose, documentation here

  • An instance of the System.String class is both immutable and, when no longer needed, cannot be programmatically scheduled for garbage collection; that is, the instance is read-only after it is created and it is not possible to predict when the instance will be deleted from computer memory. Consequently, if a String object contains sensitive information such as a password, credit card number, or personal data, there is a risk the information could be revealed after it is used because your application cannot delete the data from computer memory.

  • Important This type implements the IDisposable interface. When you have finished using the type, you should dispose of it either directly or indirectly. To dispose of the type directly, call its Dispose method in a try/catch block. To dispose of it indirectly, use a language construct such as using (in C#) or Using (in Visual Basic). For more information, see the “Using an Object that Implements IDisposable” section in the IDisposable interface topic.

  • A SecureString object is similar to a String object in that it has a text value. However, the value of a SecureString object is automatically encrypted, can be modified until your application marks it as read-only, and can be deleted from computer memory by either your application or the .NET Framework garbage collector

SecureString的前提如下:

  • 没有要检查、比较和/或转换的成员。
  • 完成后实施 IDispose
  • 本质上强制对象进入加密char []

此设计是为了确保不会发生意外或恶意暴露

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

我们无法使用 SecureString,因为我们正在向第三方发送数据。由于 System.Process() 仅允许流,因此无法使用字符数组。然后我们构建了一个 c++ MiddleLayer 来接收加密数据,使用 STARTUPINFO 解密和传输。我们最初的解决方案使用 I/O 的流,这是一个 'almost perfect' 解决方案,因为我们只剩下内存中剩余的 c++ 流。

我们联系了 Microsoft 寻求帮助,他们提出了几乎相同的解决方案,但使用 Handles/PIPES 而不是流,因此您可以关闭管道的两端。

这是示例,虚拟代码。希望这可以帮助别人!

#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] = '[=10=]';

    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);


}

}