String对象可能包含一些敏感数据,比如用户密码等,如果允许执行一些不安全或者非托管的代码,这些代码就可以扫描进程的地址空间,找到包含敏感数据的字符串,并以一种非授权的方式来使用这些数据,即使String对象只是使用一小段时间,然后就会垃圾收集器收集,CLR也可能无法立即使用String对象的内存,致使String对象的字符长时间保留在进程的内存中,造成机密数据泄露,而且,由于String对象是不可变的,所以当处理它们时,旧的副本会留在内存中,以最终造成不同版本的字符串散布在整个内存空间中。

     为了安全,FCL中有一个更安全的字符串类System.Security.SecureString,构造一个SecureString对象时,它会在内存分配一个非托管内存块,其中包含一个字符数组,之所以要使用非托管内存,是为了避开垃圾收集器。这些字符串是经过加密的,能防范任何恶意的非托管代码获取机密信息。利用以下任何一个方法,即可在安全字符串中追回,插入,删除或者设置一个字符:AppendChar,InsertAt,RemoveAt,SetAt,调用这些方法时,方法内部会会解密字符,执行指定操作,然后重新加密字符,而会对应用程序的性能造成影响。

     SecureString类实现了IDisposable接口,以便提供一种简单的方式来确定性的摧毁字符串中的安全内容。当应用程序不再需要敏感的字符串信息时,只需要调用SecureString的Dispose方法,在内部,Dispose会清零内存缓冲区的内容,确保恶意代码无法获得毛敏感信息,然后释放缓冲区。另外要注意的是,SecureString类是从CriticalFinalizerObject类派生的,和String对象不同,在回收一个SecureString对象时,加密字符串的内容将不存在于内存中。

在.NET Framework 2.0版本中,可以在以下情况下将SecureString作为一个密码来传递:

(1)与一个加密服务提供者协作

(2)创建、导入、导出一个X.509证书。

(3)在特定用户下启动一个新进程。

(4)不能在Window窗体、Wed窗体等中使用SecureString

     我样可以创建一个方法来接受一个SecureString对象参数,在方法内部,必须让SecureString对象创建一个非托管内存缓冲区,并且其中包含解密过的字符,然后才能让该方法使用缓冲区,我们的代码在访问解密的字符串时,经历的时间应尽量的短,结束使用字符串后,代码应尽快清零并释放缓冲区,而且绝不能将SecureString的内容放到一个String中,SecuteString类没有专门重写ToString方法来避免泄露敏感数据。

以下代码演示了如何初始化和使用一个SecureString,编译它是,要为C#编译器指定/unsafe开关:

 

 Program
{
    public static void Main()
    {
        
//在using后SecureString被销毁,内存中就没有敏感数据了
        using (SecureString ss = new SecureString())
        {
            Console.WriteLine(
"Please enter a password : ");
            
while (true)
            {
                ConsoleKeyInfo cki 
= Console.ReadKey(true);
                
if (cki.Key == ConsoleKey.Enter) break;

                
//将密码字符串附加到SecureString中
                ss.AppendChar(cki.KeyChar);
                Console.Write(
"*");
            }
            Console.WriteLine();

            
//密码已输入,出于演示目的而显示它
            Console.WriteLine(ss);
        }
    }  
 
    
//这个方法是不安全的,因为它要访问非托管内存
    private unsafe static void DisplaySecureString(SecureString ss)
    {
        Char
* pc = null;
        
try
        {
            
//将SecureString解密到一个非托管内存缓冲区中
            pc = (Char*)Marshal.SecureStringToCoTaskMemUnicode(ss);

            
//访问包含已解密的SecureString的非托管内存缓冲区
            for (Int32 index = 0; pc[index] != 0; index++)
            {
                Console.Write(pc[index]);
            }
        }
        
finally
        { 
            
//确定我们清零并释放包含已解密的SecureString字符的非托管内存缓冲区
            if (pc != null)
            {
                Marshal.ZeroFreeCoTaskMemUnicode((IntPtr)pc);
            }
        }
    }
}   

 

相关文章: