.NET : 如何保護內存中的敏感數據?
我們程序的威脅來自于各個方面.在互聯網高度發達的今天, 安全性問題已經是企業軟件開發所必須面對的最重要的問題. 從安全學的一般意義上來講,安全性主要體現在兩個方面:
- 敏感數據的泄露
- 敏感數據的破壞
從具體上來說, .NET 元數據機制的設計, 既方便了反射等強大特性的實現, 又同時給代碼安全及程序運行時安全帶來了巨大的隱患.迄今為止, 還未發現比較有效元數據可見性控制方法. 當然, 這不在本文的討論范圍之內. 我還是更愿意在這篇文章在針對.NET的內存分配機制討論一個更具體的問題: 如何保護在內存中存儲的敏感數據?
String的駐留機制帶來的安全性問題
String是代碼中使用頻率很高的對象類型. 為了提高字符串的處理速度, 節省內存空間, Microsoft為.NET String類設計了駐留機制. 其大概的邏輯模型是, 大部分String存儲在一個類似的Hash Table中, string的內容是哈希表的key, 該key對應的value是string的內存地址. 這樣內容相同的string實際上只是對應內存堆上同一個字符串.之所以說是大部分而不是全部, 是因為有一部分動態創建(concat)的string, 是不會進入這樣一個虛擬的hash Table中的. 本文的最后附上String類的源代碼, 有興趣的同學可以研究研究:D
這就帶來了最主要的問題, 你無法準確控制或者預測一個特定字符串的生命周期. 一個以string形式呈現的敏感數據(比如密碼)很有可能在內存中一直存在, 而你卻預測它在超出某個特定函數的作用域的時候就被垃圾回收了. 這樣, 當發生操作系統換頁的時候(而這也往往是可能發生的), 這個敏感數據就被保存到本地文件pagefile.sys當中, 或者當操作系統休眠的時候, 敏感數據進入hiberfil.sys中.一個可能的敏感數據泄漏過程是:
使用SecureString類
現在既然String靠不住了,我們能有什么簡單的方法來特別的保護我的敏感數據嗎? 幸運的是, .NET從Version 2.0開始, 為我們提供了一套基于DPAPI的解決方法 - SecureString.
SecureString類具有以下特性:
-
SecureString中的內容是加密之后的,而不是平文;
-
使用windows的加密方案DPAPI ;
-
SecureString只能在基于NT的平臺上使用
C#代碼示例
這段代碼中有幾個值得說明的地方:
- 代碼寫得有些粗糙, 僅為示意。
- 使用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就應該修改成如下:
C#代碼示例
我們用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類入手討論, 希望對您有一些啟發.
下載: String Class
聲明: 本文為作者Shining Sun原創, 禁止用于任何商務用途! 如需轉載, 請務必注明作者及作品出處。