.NET安全内存结构

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

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

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

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

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

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

从.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(); } } 

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

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

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

我发现很难想象以这种方式加密字节数组会很有用的情况。 确切地说你要做什么的更多细节会有所帮助。

您可以使用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创建所有其他内容,这可能是您的最佳方式。

一种选择:

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

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

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

 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; ///  /// Manage an array that holds sensitive information. ///  ///  /// The type of the array. Limited to built in types. ///  public sealed class SecureArray : SecureArray { private readonly T[] buf; ///  /// Initialize a new instance of the  class. ///  ///  /// The number of elements in the secure array. ///  ///  /// Set to true to do a Win32 VirtualLock on the allocated buffer to /// keep it from swapping to disk. ///  public SecureArray(int size, bool noswap = true) { this.buf = new T[size]; this.Init(this.buf, ElementSize(this.buf) * size, noswap); } ///  /// Gets the secure array. ///  public T[] Buffer => this.buf; ///  /// Gets or sets elements in the secure array. ///  ///  /// The index of the element. ///  ///  /// The element. ///  public T this[int i] { get { return this.buf[i]; } set { this.buf[i] = value; } } } 

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

 ///  /// Base class of all  classes. ///  public class SecureArray : IDisposable { ///  /// 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. ///  private static readonly Dictionary TypeSizes = new Dictionary { { 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; ///  /// Initialize a new instance of the  class. ///  ///  /// You cannot create a  directly, you must /// derive from this class like  does. ///  protected SecureArray() { } ///  /// Gets the size of the buffer element. Will throw a ///  if the element type is not /// a built in type. ///  ///  /// The array element type to return the size of. ///  ///  /// The array. ///  ///  public static int BuiltInTypeElementSize(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; } ///  /// Zero the given buffer in a way that will not be optimized away. ///  ///  /// The type of the elements in the buffer. ///  ///  /// The buffer to zero. ///  public static void Zero(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(); } } ///  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(); } ///  /// 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. ///  ///  /// The array to secure. ///  ///  /// The number of bytes in the buffer in the pinned object. ///  ///  /// True to lock the memory so it doesn't swap. ///  protected void Init(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(secretLength)) { DoSomethingSecret(secret.Buffer); } 

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

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