上回写了关于SecureString的特征和为什么我们要使用它,这篇继续研究研究这个SecureString。
**主要内容:**
- SecureString与String之间的转换
- SecureString的基本操作
- 如何销毁一个String?
##SecureString与String之间的转换
###SecureString --> String的转换
我们可以使用[Marshal类](http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal(v=vs.110).aspx)提供的一些方法进行这个转换。
Marshal类并不支持直接将`SecureString`转成托管的`System.String`对像,它提供的方法只能将`SecureString`转成一些非托管的字符串类型。我们需要再做一次转换才能得到`System.String`对象。所以基本的转换思路是这样的:
*SecureString --> BSTR/Unicode/ANSI --> System.String*
下面是代码,特别需要注意的地方是,我们一定要把非托管的字符串正确的释放掉。所以`Marshal.ZeroFreeBSTR`放在`Finally`块里确保一定会被执行。
```
IntPtr bstr = IntPtr.Zero;
try
{
bstr = Marshal.SecureStringToBSTR(sstr);
var str = Marshal.PtrToStringBSTR(bstr);
//处理字符串
}
finally
{
Marshal.ZeroFreeBSTR(bstr);
}
```
**BSTR/Unicode/ANSI有啥区别?**
我们看一下`Marshal`的方法列表就知道`Marshal`可以将`SecureString`转换成五种不同的字符串类型:`BSTR`,`CoTaskMemAnsi`,`CoTaskMemUnicode`, `GlobalAllocAnsi`, `GlobalAllocUnicode`。我当初有一个疑问就是这样子不同的类型转换有啥区别,我后来得出的结论是如果你只是想得到托管的`System.String`类型的话你选哪个作为中间变量都无所谓,只要用完释放资源就行了。
###String --> SecureString的转换
`SecureString`不接受`String`去构造一个对象,我们有两种方式可以将`String`转成`SecureString`。
1. 通过AppendChar方法把`String`对象中的字符一个一个添加到`SecureString`后面。
2. 虽然`SecureString`不接受`String`来构造一个对象,但是它接受一个字符指针来构造一个对象。所以我们可以取的指向`String`对象的非托管`char*`指针来构造。
```
string str = "zhengyi.me";
unsafe
{
fixed (char* p_str = str)
{
SecureString sstr = new SecureString(p_str, str.Length);
sstr.MakeReadOnly();
}
}
```
##SecureString的基本操作
详情参考 [SecureString Class in MSDN](http://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx)
##如何销毁一个String对象?
进一步思考一下,我们引入`SecureString`就是为了让敏感信息不在内存中裸奔。但是目前的.NET Framework提供的接口对`SecureString`的支持很有限,我们总会需要在某一小段时间内将其转换为`String`对象进行一些处理。那我们能不能让这些`String`对象*阅后即焚*呢?
在C++中我们如果要销毁一个字符串的话,无非就是把字符串所在的内存memset成0,然后将这块字符串的内存释放掉。但是在.NET中的话我们主要会遇到这么两个问题:
- 众所周知,Java和.NET中的String类型是[不可变的](http://msdn.microsoft.com/zh-cn/library/362314fe.aspx)(immutable)。我们如何将其中的内容重置掉?
- .NET中,String是一个[引用类型](http://msdn.microsoft.com/zh-cn/library/362314fe.aspx),所以String实例时会存在于堆上的。那么在一次GC以后如果进行了内存的压缩,那么String实例可能在内存上会被移动。所以也就不方面释放内存。
**如何破?**
- 虽然说`String`在.NET中是不可变的,但是.NET中有一种unsafe的机制能够让我们像在C++中一样写*不安全代码*(使用指针的代码)。
- fixed关键字可以防止GC去在内存中挪动变量。
基本的代码就像下面这样:
```
unsafe {
//利用fixed关键字将字符串pin在内存上,确保其不会挪动而出现多个拷贝
fixed(char* c = str)
{
// 处理字符串
// 利用unsafe的代码遍历字符串将其内容重置
for(int i = 0; i < str.Length; i++)
{
c[i] = '\0';
}
}
}
```
##总之
如同我在前篇文章中写的,我们所做的所有的这一切只是说提高了获取密码的难度和所需的时间。因为如果有人都能够拿到你的程序的内存的话,他可以干很多很多事情。
##引用:
[1] 关于使用SecureString的一些介绍:http://blogs.msdn.com/b/fpintos/archive/2009/06/12/how-to-properly-convert-securestring-to-string.aspx
[2] 还不错的关于SecureString的一些讨论: http://bytes.com/topic/c-sharp/answers/583441-destroy-string