.NET安全内存结构
我知道.NET库提供了一种以受保护/安全的方式存储字符串的方法= SecureString。
我的问题是,如果我想存储一个字节数组,最好的,最安全的容器是什么?
了解System.String类型的漏洞非常重要。 不可能使其完全安全,SecureString的存在可以最大限度地降低暴露风险。 System.String有风险,因为:
这里明显的风险是字符串内容在使用字符串后很久就可见,因此大大增加了攻击者可以看到它的几率。 SecureString通过将字符串存储在非托管内存中来提供一种解决方法,它不受垃圾收集器的影响,不会留下字符串内容的杂散副本。
现在应该清楚如何使用SecureString提供的相同类型的保证来创建自己的安全arrays版本。 您没有不可变性问题,在使用它之后擦除arrays不是问题。 这本身几乎总是足够好,隐含在减少暴露的可能性是你不会长时间保持对数组的引用。 因此,在垃圾收集之后存在的arrays数据的非擦除副本的可能性应该已经很低。 您也可以降低风险,仅适用于小于85,000字节的数组。 要么像SecureString那样做,要么使用Marshal.AllocHGlobal()。 或者通过固定数组GCHandle.Alloc()更容易。
从.Net 2.0开始,使用ProtectedData.Protect方法,看起来像将范围设置为DataProtectionScope.CurrentUser应该给出与安全字符串相同的预期效果
示例用法取自此处
https://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命名空间中的任何提供程序进行加密。
RtlZeroMemory
和VirtualLock
组合可以做你想要的。 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
,然后在敏感的字节数组上复制SecureArray
。
上述就是C#学习教程:.NET安全内存结构分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1022044.html