使用 .NET 2.0 SecureString 类保护敏感数据
什么时候应当使用SecureString类
你可能感到怀疑,既然可以使用基本的String类,为什么还有必要使用SecureString类呢?问题在于String类的设计方面。例如,如果你有一个String类的实例包含某个人的社会安全号,你就希望应用程序在处理过程中将其安全保存,以避免泄露。
问题的关键在于安全保存文本,因为String类将其以明文形式保存。查明数据的保存位置需要对.NET字符串有一定了解。.NET字符串是不可变的。也就是说,字符串值的每一个变化都在内存中建立一个新的字符串。因此,内存中的各种敏感数据可能会被不严格的代码或用户访问。
这并不是说明为什么使用StringBuilder对象的最有效方法和解释。由于垃圾收集功能,你可能认为这并不是一个太大的问题,但利用.NET垃圾收集使用字符串恢复内存没有确定性。因此,如果数据确实十分敏感,你应当避免String类,而选择SecureString类这些其它方法。
SecureString类具有以下特性:
- 它对保存在内存中的字符串值进行加密。它利用数据保护API(DPAPI),因此它只能在基于NT的平台上使用。
- SecureString类遵循IDisposable模式。
- 加密文本不能被轻易解密;也就是说,不存在可以迅速阅读字符串内容的ToString类(没人说过加密很容易)。
一个简单的事例:
System.Security.SecureString ss = new System.Security.SecureString();
ss.AppendChar('T');
ss.AppendChar('e');
ss.AppendChar('c');
ss.AppendChar('h');
ss.AppendChar('R');
ss.AppendChar('e');
ss.AppendChar('p');
ss.AppendChar('u');
ss.AppendChar('b');
ss.AppendChar('l');
ss.AppendChar('i');
ss.AppendChar('c');
ss.AppendChar('.');
ss.AppendChar('c');
ss.AppendChar('o');
ss.AppendChar('m');
Console.WriteLine(ss);
增加最后一个字符后,以上代码用MakeReadOnly方法锁定字符串值。这意味着这个值不能被修改。使数据只读后,一旦你企图修改数据,就会出现异常。你会注意到,在执行时,代码只显示类的名称(System.Security.SecureString)。
获得数据
像.NET Framework的大多数对象一样,SecureString类也提供一个ToString方法。但是,ToString方法源于基本的System.Object类,在SecureString类中它不能被忽略。因此,调用SecureString类的ToString方法只显示对象(System.Security.SecureString)的类型,而没有实际的值。
应用SecureString类的难点在于如何恢复保存在其中的数据。因为它使用Windows的加密服务,要利用它,你需要使用System.Runtime命名空间。下面的C#代码是恢复数据的第一个步骤。它将SecureString类的内容复制到一个长指针对象中。
IntPtr pointerName = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(SecureString object);
植入指针对象后,指针通过System.Runtime.InteropServices.Marshal类转换为一个字符串。这一操作由以下C#代码完成:
Console.WriteLine(System.Runtime.InteropServices.Marshal.PtrToStringBSTR(bstr));
微软文件指出,转换对象到一个指针分配了一个字符串所需的未管理内存,因此任务完成后你应当总是通过调用ZeroFreeBSTR方法释放指针对象,如以下的C#所示:
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(bstr);
下面的C#代码将在SecureString对象中保存数据与恢复数据、保存和显示内容的例子结合起来:
System.Security.SecureString ss = new System.Security.SecureString();
ss.AppendChar('T');
ss.AppendChar('e');
ss.AppendChar('c');
ss.AppendChar('h');
ss.AppendChar('R');
ss.AppendChar('e');
ss.AppendChar('p');
ss.AppendChar('u');
ss.AppendChar('b');
ss.AppendChar('l');
ss.AppendChar('i');
ss.AppendChar('c');
ss.AppendChar('.');
ss.AppendChar('c');
ss.AppendChar('o');
ss.AppendChar('m');
Console.WriteLine(ss);
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(ss);
try
{
Console.WriteLine(System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr));
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
}