.NET安全内存结构

时间:2009-07-22 17:37:20

标签: c# memory object security

我知道.NET库提供了一种以受保护/安全的方式存储字符串的方法= SecureString。

我的问题是,如果我想存储一个字节数组,那么保存它的最佳,最安全的容器是什么?

6 个答案:

答案 0 :(得分:10)

了解System.String类型的漏洞非常重要。不可能使其完全安全,SecureString的存在是为了最大限度地降低暴露的风险。 System.String存在风险,因为:

  • 他们的内容在其他地方可见,无需使用调试器。攻击者可以查看分页文件(c:\ pagefile.sys),它保留了换成磁盘的RAM页面内容,为其他需要RAM的程序腾出空间
  • System.String是不可变的,您使用它后不能擦除字符串的内容
  • 垃圾收集堆会压缩堆,但不会重置已释放的内存的内容。哪个可以将字符串数据的副本留在内存中,完全无法从程序中获取。

这里明显的风险是字符串内容在使用字符串后很久就可见,因此大大增加了攻击者可以看到它的几率。 SecureString通过将字符串存储在非托管内存中来提供一种解决方法,在该内存中,它不受垃圾收集器的影响而留下字符串内容的杂散副本。

现在应该清楚如何使用SecureString提供的相同类型的保证来创建自己的安全阵列版本。你有不可变性问题,使用它后擦洗数组不是问题。这本身几乎总是足够好,隐含在减少暴露的可能性是你不会长时间保持对数组的引用。因此,在垃圾收集之后存在的阵列数据的非擦除副本的可能性应该已经很低。您也可以降低风险,仅适用于小于85,000字节的数组。通过SecureString执行它并使用Marshal.AllocHGlobal()的方式。或者通过固定数组GCHandle.Alloc()更容易。

答案 1 :(得分:3)

从.Net 2.0开始,<。>使用ProtectedData.Protect方法,看起来像将范围设置为DataProtectionScope.CurrentUser应该提供与安全字符串相同的预期效果

从这里采用的示例

http://msdn.microsoft.com/en-us/library/system.security.cryptography.protecteddata.protect.aspx

using System;
using System.Security.Cryptography;

public class DataProtectionSample
{
// Create byte array for additional entropy when using Protect method. 
    static byte [] s_aditionalEntropy = { 9, 8, 7, 6, 5 };

    public static void Main()
    {
// Create a simple byte array containing data to be encrypted. 

byte [] secret = { 0, 1, 2, 3, 4, 1, 2, 3, 4 };

//Encrypt the data. 
        byte [] encryptedSecret = Protect( secret );
        Console.WriteLine("The encrypted byte array is:");
        PrintValues(encryptedSecret);

// Decrypt the data and store in a byte array. 
        byte [] originalData = Unprotect( encryptedSecret );
        Console.WriteLine("{0}The original data is:", Environment.NewLine);
        PrintValues(originalData);

    }

    public static byte [] Protect( byte [] data )
    {
        try
        {
            // Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted 
            //  only by the same current user. 
            return ProtectedData.Protect( data, s_aditionalEntropy, DataProtectionScope.CurrentUser );
        } 
        catch (CryptographicException e)
        {
            Console.WriteLine("Data was not encrypted. An error occurred.");
            Console.WriteLine(e.ToString());
            return null;
        }
    }

    public static byte [] Unprotect( byte [] data )
    {
        try
        {
            //Decrypt the data using DataProtectionScope.CurrentUser. 
            return ProtectedData.Unprotect( data, s_aditionalEntropy, DataProtectionScope.CurrentUser );
        } 
        catch (CryptographicException e)
        {
            Console.WriteLine("Data was not decrypted. An error occurred.");
            Console.WriteLine(e.ToString());
            return null;
        }
    }

    public static void PrintValues( Byte[] myArr )  
    {
          foreach ( Byte i in myArr )  
            {
                 Console.Write( "\t{0}", i );
             }
      Console.WriteLine();
     }

}

答案 2 :(得分:2)

没有“最好”的方法可以做到这一点 - 你需要确定你想要保护的威胁,以便决定做什么或者确实需要做什么。

需要注意的一点是,与不可变的字符串不同,您可以在完成它们之后将字节数组中的字节清零,这样您就不会遇到SecureString设计的同一组问题解决。

加密数据可能适用于某些问题,但是您需要确定如何保护密钥免受未经授权的访问。

我发现很难想象以这种方式加密字节数组会很有用的情况。关于你正在尝试做什么的更多细节会有所帮助。

答案 3 :(得分:1)

您可以使用SecureString存储字节数组。

  SecureString testString = new SecureString();

  // Assign the character array to the secure string.
  foreach (byte b in bytes)
     testString.AppendChar((char)b);

然后你只需要反转该过程以恢复字节。


这不是唯一的方法,你总是可以使用MemoryBuffer和System.Security.Cryptography之外的东西。但这是唯一专门设计为以这种方式安全的东西。您必须使用System.Security.Cryptography创建所有其他内容,这可能是您最好的方式。

答案 4 :(得分:1)

RtlZeroMemoryVirtualLock的组合可以做你想要的。 VirtualLock如果你想保持数据不交换到磁盘和RtlZeroMemory以确保内存被归零(我试图使用RtlSecureZeroMemory但这似乎并不存在于kernel.dll中)下面的类将存储安全的任何内置类型的数组。我将解决方案分成两个类,以分离出与类型无关的代码。

第一个类只分配并保存一个数组。它执行运行时检查,模板类型是内置类型。不幸的是,我无法在编译时找到一种方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

/// <summary>
/// Manage an array that holds sensitive information.
/// </summary>
/// <typeparam name="T">
/// The type of the array. Limited to built in types.
/// </typeparam>
public sealed class SecureArray<T> : SecureArray
{
    private readonly T[] buf;

    /// <summary>
    /// Initialize a new instance of the <see cref="SecureArray{T}"/> class.
    /// </summary>
    /// <param name="size">
    /// The number of elements in the secure array.
    /// </param>
    /// <param name="noswap">
    /// Set to true to do a Win32 VirtualLock on the allocated buffer to
    /// keep it from swapping to disk.
    /// </param>
    public SecureArray(int size, bool noswap = true)
    {
        this.buf = new T[size];
        this.Init(this.buf, ElementSize(this.buf) * size, noswap);
    }

    /// <summary>
    /// Gets the secure array.
    /// </summary>
    public T[] Buffer => this.buf;

    /// <summary>
    /// Gets or sets elements in the secure array.
    /// </summary>
    /// <param name="i">
    /// The index of the element.
    /// </param>
    /// <returns>
    /// The element.
    /// </returns>
    public T this[int i]
    {
        get
        {
            return this.buf[i];
        }

        set
        {
            this.buf[i] = value;
        }
    }
}

下一堂课做了真正的工作。它告诉垃圾收集器将数组固定在内存中。它然后将其锁定,以便它不会交换。处置后,它会将数组归零并解锁,然后告诉垃圾收集器取消固定它。

/// <summary>
/// Base class of all <see cref="SecureArray{T}"/> classes.
/// </summary>
public class SecureArray : IDisposable
{
    /// <summary>
    /// Cannot find a way to do a compile-time verification that the
    /// array element type is one of these so this dictionary gets
    /// used to do it at runtime.
    /// </summary>
    private static readonly Dictionary<Type, int> TypeSizes =
        new Dictionary<Type, int>
            {
                { typeof(sbyte), sizeof(sbyte) },
                { typeof(byte), sizeof(byte) },
                { typeof(short), sizeof(short) },
                { typeof(ushort), sizeof(ushort) },
                { typeof(int), sizeof(int) },
                { typeof(uint), sizeof(uint) },
                { typeof(long), sizeof(long) },
                { typeof(ulong), sizeof(ulong) },
                { typeof(char), sizeof(char) },
                { typeof(float), sizeof(float) },
                { typeof(double), sizeof(double) },
                { typeof(decimal), sizeof(decimal) },
                { typeof(bool), sizeof(bool) }
            };

    private GCHandle handle;

    private uint byteCount;

    private bool virtualLocked;

    /// <summary>
    /// Initialize a new instance of the <see cref="SecureArray"/> class.
    /// </summary>
    /// <remarks>
    /// You cannot create a <see cref="SecureArray"/> directly, you must
    /// derive from this class like <see cref="SecureArray{T}"/> does.
    /// </remarks>
    protected SecureArray()
    {
    }

    /// <summary>
    /// Gets the size of the buffer element. Will throw a 
    /// <see cref="NotSupportedException"/> if the element type is not
    /// a built in type.
    /// </summary>
    /// <typeparam name="T">
    /// The array element type to return the size of.
    /// </typeparam>
    /// <param name="buffer">
    /// The array.
    /// </param>
    /// <returns></returns>
    public static int BuiltInTypeElementSize<T>(T[] buffer)
    {
        int elementSize;
        if (!TypeSizes.TryGetValue(typeof(T), out elementSize))
        {
            throw new NotSupportedException(
              $"Type {typeof(T).Name} not a built in type. "
              + $"Valid types: {string.Join(", ", TypeSizes.Keys.Select(t => t.Name))}");
        }

        return elementSize;
    }

    /// <summary>
    /// Zero the given buffer in a way that will not be optimized away.
    /// </summary>
    /// <typeparam name="T">
    /// The type of the elements in the buffer.
    /// </typeparam>
    /// <param name="buffer">
    /// The buffer to zero.
    /// </param>
    public static void Zero<T>(T[] buffer)
        where T : struct
    {
        var bufHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            IntPtr bufPtr = bufHandle.AddrOfPinnedObject();
            UIntPtr cnt = new UIntPtr(
                 (uint)buffer.Length * (uint)BuiltInTypeElementSize(buffer));
            RtlZeroMemory(bufPtr, cnt);
        }
        finally
        {
            bufHandle.Free();
        }
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        IntPtr bufPtr = this.handle.AddrOfPinnedObject();
        UIntPtr cnt = new UIntPtr(this.byteCount);
        RtlZeroMemory(bufPtr, cnt);
        if (this.virtualLocked)
        {
            VirtualUnlock(bufPtr, cnt);
        }

        this.handle.Free();
    }

    /// <summary>
    /// Call this with the array to secure and the number of bytes in that
    /// array. The buffer will be zeroed and the handle freed when the
    /// instance is disposed.
    /// </summary>
    /// <param name="buf">
    /// The array to secure.
    /// </param>
    /// <param name="sizeInBytes">
    /// The number of bytes in the buffer in the pinned object.
    /// </param>
    /// <param name="noswap">
    /// True to lock the memory so it doesn't swap.
    /// </param>
    protected void Init<T>(T[] buf, int sizeInBytes, bool noswap)
    {
        this.handle = GCHandle.Alloc(buf, GCHandleType.Pinned);
        this.byteCount = (uint)sizeInBytes;
        IntPtr bufPtr = this.handle.AddrOfPinnedObject();
        UIntPtr cnt = new UIntPtr(this.byteCount);
        if (noswap)
        {
            VirtualLock(bufPtr, cnt);
            this.virtualLocked = true;
        }
    }

    [DllImport("kernel32.dll")]
    private static extern void RtlZeroMemory(IntPtr ptr, UIntPtr cnt);

    [DllImport("kernel32.dll")]
    static extern bool VirtualLock(IntPtr lpAddress, UIntPtr dwSize);

    [DllImport("kernel32.dll")]
    static extern bool VirtualUnlock(IntPtr lpAddress, UIntPtr dwSize);
}

要使用该类,只需执行以下操作:

using (var secret = new SecureArray<byte>(secretLength))
{
    DoSomethingSecret(secret.Buffer);
}

现在,这门课做了两件你不应该轻易做的事情,首先,它会锁定记忆。这可能会降低性能,因为垃圾收集器现在必须解决它无法移动的内存。其次,它可以将页面锁定在操作系统可能希望换出的内存中。这会短暂地改变您系统上的其他进程,因为现在他们无法访问该RAM。

为了尽量减少SecureArray<T>的不利影响,请不要使用它,并且只能在短时间内使用它。如果您希望将数据保留更长时间,则需要对其进行加密。为此,你最好的选择是ProtectedData课程。不幸的是,这会将您的敏感数据放入非安全的字节数组中。你可以做的最好的事情是快速复制到SecureArray<byte>.Buffer,然后复制到敏感字节数组上的SecureArray.Zero

答案 5 :(得分:0)

一个选项:

您可以将字节存储在内存流中,使用System.Security.Cryptography命名空间中的任何提供程序进行加密。