pagefile.sys 或 hiberfil.sys,離線攻擊面擴大。
System.Security.SecureString 使用 DPAPI (Data Protection API) 在記憶體中加密儲存字元,支援:
MakeReadOnly() 防止修改IDisposable,主動釋放非受控記憶體// 建立
var securePwd = new SecureString();
foreach (char c in "P@ssw0rd") securePwd.AppendChar(c);
securePwd.MakeReadOnly();
// 使用時解密(需 Marshal 互操作)
IntPtr ptr = Marshal.SecureStringToBSTR(securePwd);
try
{
string plain = Marshal.PtrToStringBSTR(ptr);
// 使用 plain 進行驗證、連線等
}
finally
{
Marshal.ZeroFreeBSTR(ptr); // 必須釋放非受控記憶體
securePwd.Dispose();
}
解密後的字串仍為 Managed 物件,可能被 GC 移動或換頁。使用 GCHandle.Alloc(..., GCHandleType.Pinned) 釘選記憶體區塊:
public unsafe void UseSecureString(SecureString securePwd)
{
int len = securePwd.Length;
// 配置固定大小的字串緩衝區
string buffer = new string('\0', len);
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr bstrPtr = IntPtr.Zero;
try
{
bstrPtr = Marshal.SecureStringToBSTR(securePwd);
char* src = (char*)bstrPtr;
char* dst = (char*)handle.AddrOfPinnedObject();
for (int i = 0; i < len; i++) dst[i] = src[i]; // 複製到釘選緩衝區
// 此處使用 buffer 進行敏感操作
// 例如:開啟資料庫連線、呼叫需明文密碼的 API
}
finally
{
if (bstrPtr != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstrPtr);
handle.Free(); // 釋放釘選
}
}
釘選僅防 GC 移動,無法阻止 OS 換頁。呼叫 Windows API VirtualLock 強制頁框常駐實體記憶體:
[DllImport("kernel32.dll")]
static extern bool VirtualLock(IntPtr lpAddress, UIntPtr dwSize);
[DllImport("kernel32.dll")]
static extern bool VirtualUnlock(IntPtr lpAddress, UIntPtr dwSize);
// 使用前
VirtualLock(handle.AddrOfPinnedObject(), (UIntPtr)buffer.Length * 2); // char = 2 bytes
// 使用後
VirtualUnlock(handle.AddrOfPinnedObject(), (UIntPtr)buffer.Length * 2);
⚠️ 警告:過度鎖定頁框會耗盡非分頁集區,導致系統效能驟降甚至死機。僅在極高安全需求(如金鑰管理模組)且短時間內使用。
| 問題 | 現況 | 建議 |
|---|---|---|
| 使用者輸入來源 | TextBox/Console 回傳 String | 自訂控件直接寫入 SecureString,或輸入即轉換並清除原 String |
| API 相容性 | ADO.NET、LDAP、WebRequest 等需 String | 封裝輔助方法:SecureString → 釘選明文 → 呼叫 API → 立即清除 |
| Framework 支援度 | 部分 API 新增 SecureString 多載(如 X509Certificate2) | 優先使用支援 SecureString 的多載,避免中間明文 |
SecureString。.NET 提供了工具,關鍵在於開發者的安全意識與紀律。從 String 到 SecureString,是邁向記憶體安全的關鍵一步。