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

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

    }
}

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

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

不工作的例子:

  • Strings/StringBuilder:它们不断 self 更新,到处都是碎片。
  • SecureString:类在纸面上和 MSDN 上看起来都很棒。但是您必须使用字符串来加载和卸载此对象(请参阅第 #1 项)
  • 数组列表。确实是二维字符串问题。

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

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

有没有人以前在 .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 类型类中清除内存?

最佳答案

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

我们无法使用 SecureString,因为我们正在向第三方发送数据。由于 System.Process() 仅允许流,因此无法使用字符数组。然后我们构建了一个 c++ MiddleLayer 来接收加密数据,使用 STARTUPINFO 解密和传输。我们最初的解决方案使用 I/O 流,这是一个“近乎完美”的解决方案,因为我们只剩下内存中的 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] = '\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);


}

关于c# 并从内存中删除敏感数据和所有垃圾收集副本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27973858/

相关文章:

mysql - MySQL中如何将字符串转换为日期?

c - C 中的右对齐零填充字符串

security - 使用 JWT 受众字段进行授权角色

security - 在 PhoneGap 中保护第三方 API key / secret

c# - 在 T-SQL 中追加数据

c# - 如何使用 WriteEndElement 检查元素名称

c# - 类型声明是隐式的静态成员

c# - 使用 MVVM Light 时如何将弹出窗口绑定(bind)到 ViewModel - BindingExpression 路径错误

javascript - 检查表示 HTML 的文本中的 body 标签是否为空

security - 应用程序用户看到 Paypal API 返回的 "payKey"是否存在安全风险?