.NET内存中敏感数据的保护方案

来源:互联网 发布:suse linux下安装snmp 编辑:程序博客网 时间:2024/06/05 12:02
 

从具体上来说,.NET元数据机制的设计,既方便了反射等强大特性的实现,又同时给代码安全及程序运行时安全带来了巨大的隐患。迄今为止,还未发现比较有效元数据可见性控制方法。当然,这不在本文的讨论范围之内。我还是更愿意在这篇文章在针对.NET的内存分配机制讨论一个更具体的问题:如何保护在内存中存储的敏感数据?

String的驻留机制带来的安全性问题

String是代码中使用频率很高的对象类型。为了提高字符串的处理速度,节省内存空间,Microsoft为.NET String类设计了驻留机制。其大概的逻辑模型是,大部分String存储在一个类似的Hash Table中,string的内容是哈希表的key,该key对应的value是string的内存地址。这样内容相同的string实际上只是对应内存堆上同一个字符串。之所以说是大部分而不是全部,是因为有一部分动态创建(concat)的string,是不会进入这样一个虚拟的hash Table中的。本文的最后附上String类的源代码,有兴趣的同学可以研究研究。

这就带来了最主要的问题,你无法准确控制或者预测一个特定字符串的生命周期。一个以string形式呈现的敏感数据(比如密码)很有可能在内存中一直存在,而你却预测它在超出某个特定函数的作用域的时候就被垃圾回收了。这样,当发生操作系统换页的时候(而这也往往是可能发生的),这个敏感数据就被保存到本地文件pagefile。sys当中,或者当操作系统休眠的时候,敏感数据进入hiberfil.sys中。一个可能的敏感数据泄漏过程是:

使用SecureString类

现在既然String靠不住了,我们能有什么简单的方法来特别的保护我的敏感数据吗? 幸运的是,.NET从Version 2.0开始,为我们提供了一套基于DPAPI的解决方法 - SecureString。

SecureString类具有以下特性:

SecureString中的内容是加密之后的,而不是平文;

使用Windows的加密方案DPAPI ;

SecureString只能在基于NT的平台上使用

C#代码示例:

public void MethodA(){//using DPAPI to encrpt the sensitive contentSystem.Security.SecureString password = new System.Security.SecureString();char[] pass = { 'p','a','s','s','w','o','r','d' };for (int i = 0;i < pass.Length;i++){password.AppendChar(pass[i]);}password.MakeReadOnly();//pass the encrypted password through memory or file}public void MethodB(System.Security.SecureString password){string decryptedPassword = "";//copy the secure content to a long pointerIntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(password);try{//Convert secure content to string using DPAPIdecryptedPassword = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);//using the decrypted password to check}finally{System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);password.Dispose();}}

这段代码中有几个值得说明的地方:

代码写得有些粗糙,仅为示意。

使用Char数组来保存敏感数据的原始值。 因为Char数组的生命周期是可以预期的,它在超出自己的作用域之后,就被回收。

MakeReadOnly方法,一旦使用了该方法后,SecureString的内容就不能再被修改,从而保证了加密后的数据不能再被修改,否则将引发异常。

SecureString的解密,是通过将其内容复制到一个长指针中,然后利用DPAPI,最终获得String。该String不会进入上文所说的那个虚拟Hash Table中。

ZeroFreeBSTR()方法。因为使用COM Interop引入了非托管资源,所以一定不能忘记使用ZeroFreeBSTR来释放指针,否则会造成内存泄漏。

SecureString类重写了基类的ToString()方法,不过该方法不会返回所持有的加密内容,而总是返回System.Security.SecureString。

敏感数据已经足够安全了吗?

这个问题的答案很让我们沮丧,不是。有两个问题:

用户的输入往往先被处理成string,然后才能传递到我们的处理函数,比如command line parameters,或者textbox。

.NET Framework的很多函数都要求string参数,而非SecureString,比如ADO.NET的Connect函数。

幸运的是,对于这两个问题,我们除了祈祷Microsoft尽快更新Framework以外,在当前条件下还有些办法来处理。

针对第一个问题,重写Command Line或者Textbox,添加对SecureString的支持。

针对第二个问题,利用GC特性来处理。

第二个问题的主要安全隐患是来自于string的特性,即不可变性(immutable)。为了防止GC的自作聪明处理我们的数据,从而造成敏感数据泄漏,我们需要对GC做一些处理,此时上面代码的MethodB就应该修改成如下:

public unsafe void MethodB(System.Security.SecureString password){int pwdLength = password.Length;IntPtr passwordPtr = IntPtr.Zero;//allocate a pinned memory to store the password in string formstring decryptedPassword = new string('/0',pwdLength);GCHandle gch = GCHandle.Alloc(decryptedPassword,GCHandleType.Pinned);try{//copy the secure content to a long pointerpasswordPtr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(password);var pPassword = (char*)passwordPtr;var pDecryptedPassword = (char*)gch.AddrOfPinnedObject();for (int index = 0; index < pwdLength; index++){pDecryptedPassword[index] = pPassword[index];}}finally{if (IntPtr.Zero != passwordPtr){System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(passwordPtr);}} }

我们用GCHandleType。Pinned标志,申请了一块固定位置的内存来存储密码,这段明文密码是独立于string类的虚拟hash table的。这可以在一定程度上减少因不当权限访问造成的敏感数据泄露。

到这里,string是可以用了,但是换页的问题还没有解决啊?

是的,你可能已经觉得麻烦了。我们不得已而为之,实在是因为.NET Framework对于SecureString的支持还不够完善,或者说是部分的。上面虽然解决了String的不可变特性造成的问题,但是重新引入系统换页的问题。怎么办?

在这种情况下,我们只能求助于Windows API。Windows API对于页的操作为我们提供了2个接口:

AllocateuserPhysicalPages 和VirtualLock,这两个函数可以将我们在上例中所取得的密码存储地址pDecryptedPassword 锁定在内存中,强制不换页。不过这么做要万分小心,因为一旦pDecryptedPassword 所指向的密码内容被强制不换页,那该程序的整个workset都会一直被强制在内存中,一直到程序结束。这可能给系统的其他程序带来糟糕的体验。

关于使用VirtualLock来强制Page In的修改,就不再讨论了。

总结

事物总是两面性的,.NET给我们带来了快速实现,关注业务的好处,却缺少了譬如C++般精确操作内存这样的灵活性,因而在安全性方面如果Framework不够完善,我们就会多多少少有些掣肘。总之,在现有条件下,尽力实现系统安全性,是我们的目标。本文没有讨论系统设计的安全性考虑等这些概念性理论性的东西,而是从最具体的String类入手讨论,希望对您有一些启发。