分析问题
System.Security.SecureString被设计用来保存一些机密的字符串,完成传统字符串所不能做到的工作。传统字符串以明码的形式分配在内存上,一个简单的内存读写软件可以轻易地捕获这些字符串,这在有些机密系统中是不被允许的。读者可能觉得对字符串加密会解决类似的问题,但事实上对字符串加密时字符串已经以明码的方式驻留在内存上很久了,对于该问题唯一的解决办法就是在字符串的获取过程中直接进行加密。SecureString的设计初衷就是解决该类问题。
为了保证它的安全性,SecureString的分配不同于传统字符串,它是被分配在非托管内存上的。并且SecureString的对象从分配的一开始就以加密的形式存在。对SecureString的所有操作,无论添加、删除、插入等,都是逐字符进行的。在这些操作进行的时候,驻留在非托管内存上的安全字符串会被解密,然后再进行操作,最后进行加密。在操作的过程中确实有一小段时间字符串是处于明码状态的,但逐字符的机制让这段时间维持在非常短的区间内,以保证破解程序很难有机会读取明码的字符串。
System.Security.SecureString实现了标准的Dispose/Finalize模式,这是因为其对象的分配是在非托管内存上,所以需要保证每个对象在作用域退出后将被释放掉。SecureString的释放方式是把其对象内存全部置0,而不仅仅是告诉CLR这一块内存可以分配,当然这样做仍然是为了确保安全。以下代码展示了System.Security.SecureString的使用方法。
using System; using System.Security; using System.Runtime.InteropServices; namespace Test { class UseSecureString { static void Main() { //Use using guarantee the Dispose method is invoked using (SecureString ss = new SecureString ()) { //Only one character to visit SecureString object ss.AppendChar(‘a‘); ss.AppendChar(‘c‘); ss.AppendChar(‘d‘); ss.InsertAt(1,‘c‘); PrintSecureString(ss); Console.Read(); } } //Print SecureString object unsafe static void PrintSecureString(SecureString ss) { char* buffer = null; try { buffer = (char*)Marshal.SecureStringToCoTaskMemUnicode(ss); for (int i = 0;*(buffer+i)!=‘\0‘; i++) { Console.Write(*(buffer + i)); } Console.Write("\r\n"); } finally { //Release the memory object if (buffer!=null) { Marshal.ZeroFreeCoTaskMemUnicode((IntPtr)buffer); } } } } }
注意
为了显示SecureString的内容,程序需要访问非托管内存块,PrintSecureString方法使用了unsafe关键字,所以编译时需要添加/unsafe开发。项目-属性-生成-允许不安全代码。
在以上代码中,程序分配了一个安全字符串类型,并且提供了一个打印安全字符的方法。其中使用了Marshal.SecureStringToTaskMemUnicode方法把安全字符串解密到非托管内存块中,读者需要注意的是,在使用非托管内存时要确保其被释放。
答案
System.Security.SecureString提供了加密的字符串类型。其对象会被分配在非托管的内存中,并且以加密的形式保存。对于SecureString的操作都是逐字符的,SecureString会负责在操作时进行解密和加密。SecureString实现了标准的Dispose/Finalize方法,对象被释放时先被全部置0,以保证机密信息不会在内存中驻留过长时间。
SecureString的实例如何被分配和释放