建议118:使用SecureString保存密钥等机密字符串
建議118:使用SecureString保存密鑰等機密字符串
托管代碼中的字符串是一類特殊的對象,它們不可用被改變。每次使用System.String類張的方法之一時,或者使用此類型進行運算時(如賦值、拼接等),都要在內存中創建新的字符串對象,也就是為該新對象分配新的空間。這就帶來了兩個問題:
針對第一個問題,我們來看一段代碼:
static void Method1(){string str = "liming"; Console.WriteLine(str);}static void Main(string[] args){Method1(); //在此處打上斷點Console.ReadKey();}在Method1方法處打上斷點。在VS中讓程序執行到此處,在即時窗口中相繼運行命令:
.load sos.dll
和
!dso
運行結果:
.load sos.dll
已加載擴展 G:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll
!dso
PDB symbol for clr.dll not loaded
OS Thread Id: 0x9806c (622700)
ESP/REG? Object?? Name
003EE700 027022a8 System.Object[]??? (System.String[])
003EE9F4 027022a8 System.Object[]??? (System.String[])
003EEDA0 027022a8 System.Object[]??? (System.String[])
003EEDB8 027022a8 System.Object[]??? (System.String[])
003EEDC4 027022a8 System.Object[]??? (System.String[])
003EEE40 027022a8 System.Object[]??? (System.String[])
003EEF9C 027022a8 System.Object[]??? (System.String[])
003EEFD4 027022a8 System.Object[]??? (System.String[])
003EF510 02701238 System.SharedStatics
?
打開“調試”->“窗口”->“內存”->“內存1”窗口,找到對應Object列的內存地址"027022a8",然后在內存窗口中輸入。
由于此時還沒有進入到Method1中,所以內存當中不存在字符串“liming”。接著讓程序運行到方法內部,可以看到內存中應經存在“liming”了。
這就出現了一個問題,如果有人惡意掃描你的內存,程序中所保存的機密信息將無處可逃。幸好FCL提供了System.Security.SecureString,SecureString表示一個應保密的文本,它在初始化時就已經被加密了。使用SecureString的示例如下:
static System.Security.SecureString secureString = new System.Security.SecureString();static void Method2(){secureString.AppendChar('l');secureString.AppendChar('i');secureString.AppendChar('m');secureString.AppendChar('i');secureString.AppendChar('n');secureString.AppendChar('g');}static void Main(string[] args){Method2(); Console.ReadKey();}使用相同的調試手法可以發現,再次進入Method2后,已經找不到對應的字符串“liming”了。但是,核心數據保存問題已經解決了,可是文本總是要取出來的,只要取出來不是就會被發現嗎?這個問題沒法避免,但是我們可以做到文本使用完畢就釋放掉,代碼如下:
static void Method3(){secureString.AppendChar('l');secureString.AppendChar('i');secureString.AppendChar('m');secureString.AppendChar('i');secureString.AppendChar('n');secureString.AppendChar('g');IntPtr addr = Marshal.SecureStringToBSTR(secureString);string temp = Marshal.PtrToStringBSTR(addr);//使用該機密文本做一些事情///=======開始清理內存//清理掉非托管代碼中對應的內存的值 Marshal.ZeroFreeBSTR(addr);//清理托管代碼對應的內存的值(采用重寫的方法)int id = GetProcessID();byte[] writeBytes = Encoding.Unicode.GetBytes("xxxxxx");IntPtr intPtr = Open(id);unsafe{fixed (char* c = temp){WriteMemory((IntPtr)c, writeBytes, writeBytes.Length);}}///=======清理完畢 }static PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();public static int GetProcessID(){Process p = Process.GetCurrentProcess();return p.Id;}public static IntPtr Open(int processId){IntPtr hProcess = IntPtr.Zero;hProcess = ProcessAPIHelper.OpenProcess(ProcessAccessFlags.All, false, processId);if (hProcess == IntPtr.Zero)throw new Exception("OpenProcess失敗");processInfo.hProcess = hProcess;processInfo.dwProcessId = processId;return hProcess;}static int WriteMemory(IntPtr addressBase, byte[] writeBytes, int writeLength){int reallyWriteLength = 0;if (!ProcessAPIHelper.WriteProcessMemory(processInfo.hProcess, addressBase, writeBytes, writeLength, out reallyWriteLength)){throw new Exception();}return reallyWriteLength;}[StructLayout(LayoutKind.Sequential)]internal struct PROCESS_INFORMATION{public IntPtr hProcess;public IntPtr hThread;public int dwProcessId;public int dwThreadId;}[Flags]enum ProcessAccessFlags : uint{All = 0x001F0FFF,Terminate = 0x00000001,CreateThread = 0x00000002,VMOperation = 0x00000008,VMRead = 0x00000010,VMWrite = 0x00000020,DupHandle = 0x00000040,SetInformation = 0x00000200,QueryInformation = 0x00000400,Synchronize = 0x00100000}static class ProcessAPIHelper{[DllImport("kernel32.dll")]public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);[DllImport("kernel32.dll", SetLastError = true)]public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);[DllImport("kernel32.dll", SetLastError = true)]public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out uint lpNumberOfBytesRead);[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]public static extern bool CloseHandle(IntPtr hObject);}?
注意查看上文中的代碼:
IntPtr addr = Marshal.SecureStringToBSTR(secureString); string temp = Marshal.PtrToStringBSTR(addr);這兩行代碼表示的就是把機密文本從SecureString取出來,臨時賦值給字符串temp。這里存在兩個問題:第一行實際調用的是非托管代碼,它在內存中也會存儲一個“liming”;第二行代碼會在托管內存中存儲一個“liming”。這兩段文本的釋放方式是不一樣的。前者可以通過使用下面代碼釋放:
Marshal.ZeroFreeBSTR(addr);而托管內存中的文本,只能通過重寫來完成(如上文中,就是重寫成無意義的“xxxxxx”了)。當然,沒有絕對的安全,因為即便如此,讓關鍵字符串在內存中像流星一樣一閃而過,它也存在被捕獲的可能性。但是我們通過這種方法降低了數據被破解的概率。
?
?
轉自:《編寫高質量代碼改善C#程序的157個建議》陸敏技
總結
以上是生活随笔為你收集整理的建议118:使用SecureString保存密钥等机密字符串的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: peer之间的通信协议
- 下一篇: 西瓜玲子5.20打卡日记