程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET內存中敏感數據的保護方案

.NET內存中敏感數據的保護方案

編輯:關於.NET

從具體上來說,.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 content
System.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 pointer
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(password);
try
{
//Convert secure content to string using DPAPI
decryptedPassword = 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 form
string decryptedPassword = new string('',pwdLength);
GCHandle gch = GCHandle.Alloc(decryptedPassword,GCHandleType.Pinned);
try
{
//copy the secure content to a long pointer
passwordPtr = 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類入手討論,希望對您有一些啟發。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved