从Windows 2000开始,Windows操作系统提供了一套密码学方面的API,称为DPAPI(Data Protection API,数据保护API)。系统中的这套API由crypt32.dll实现,它使用当前用户的登录用户名/密码对来管理密钥。它可以用于标识一个进程、Windows会话或目前使用的机器,从而在用户、进程、会话或机器级别上加密数据从而确保其机密性。同时使用DPAPI可以使我们不用再负责密钥的管理。
深入一些,这套API还可以管理对用户密码的修改。例如,如果为某个特定用户将数据加密保存后,即使该用户的密钥发生了变化,仍然可以使用那些数据。这项功能依靠保存过期密钥这一机制实现的。
使用System.Security.Cryptography.ProtectedData类
下面的例子演示了使用System.Security.Cryptography.ProtectedData类在用户级别保护数据。
using System.Security.Cryptography; class Program{ static void Main() { string sMsg = "The message to encrypt!"; string sEnc, sDec; System.Text.Encoding utf = new System.Text.UTF8Encoding(); byte[] entropy = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; { byte[] bMsg = utf.GetBytes(sMsg); byte[] bEnc = ProtectedData.Protect(bMsg , entropy , DataProtectionScope.CurrentUser); sEnc = System.Convert.ToBase64String(bEnc); } { byte[] bEnc = System.Convert.FromBase64String(sEnc); byte[] bDec = ProtectedData.Unprotect(bEnc, entropy, DataProtectionScope.CurrentUser); sDec = utf.GetString(bDec); } System.Console.WriteLine("Message : " + sMsg); System.Console.WriteLine("Encrypted: " + sEnc); System.Console.WriteLine("Decrypted: " + sDec); } }
可以通过设置DataProtectionScope.LocalMachine值来实现在机器级别保护该数据。本例中为加密添加熵,这意味着,即使进程执行的上下文环境符合要求(即处在正确的用户或机器下),如果不知道加密使用的熵也是无法解密数据。即可以把熵值看成某种形式的辅助密钥。
使用System.Security.Cryptography.ProtectedMemory类
使用System.Security.Cryptography.ProtectedMemory类可以获得比ProtectedData类更加精确的保护数据的程度。枚举量MemoryProtectionScope提供了以下选项:
SameProcess: 表示只有处在同一进程中的代码才能将进过加密的数据解密。
SameLogon: 表示只有处在同一用户上下文的代码才能将经过加密的数据解密。
CrossProcess: 表示只要在加密完成后操作系统没有重启,那么在任何进程中执行的任何代码都能将经过加密的数据解密。
下面的示例演示了此类的使用。注意:被加密的数据的字节数必须是16的倍数。
using System.Security.Cryptography; class Program { static void Main() { string sMsg = "01234567890123456789012345678901"; System.Text.Encoding utf = new System.Text.UTF8Encoding(); System.Console.WriteLine("Message : " + sMsg); byte[] bMsg = utf.GetBytes(sMsg); ProtectedMemory.Protect( bMsg, MemoryProtectionScope.SameProcess ); System.Console.WriteLine("Encrypted: " + utf.GetString(bMsg)); ProtectedMemory.Unprotect( bMsg, MemoryProtectionScope.SameProcess ); System.Console.WriteLine("Decrypted: " + utf.GetString(bMsg)); } }
使用System.Security.SecureString类
字符串在内存中的存储是不加密的,而且由于垃圾回收机制,字符串这种引用类型在销毁之前有一段不受控制的时间。如果某些恶意的人能够访问到Windows进程的页面,将这些字符串暴露在外面是很危险的。
为了解决如上问题,.NET Framework提供了SecureString类,其采用DPAPI服务并且允许字符串以加密的形式保存在内存中。
可以通过一个字符数组(使用过后将其设为零)初始化一个SecureString类的实例,或者通过一个字符一个字符的将其构造出来。Marshal类提供了多个方法以供解密一个保存在SecureString类实例中的加密字符串。
下面示例展示了SecureString类的使用:
using System; using System.Security; using System.Runtime.InteropServices; class Program { static void Main() { SecureString pwd = new SecureString(); ConsoleKeyInfo nextKey = Console.ReadKey( true ); while(nextKey.Key != ConsoleKey.Enter) { pwd.AppendChar( nextKey.KeyChar ); Console.Write( "*" ); nextKey = Console.ReadKey( true ); } Console.WriteLine(); pwd.MakeReadOnly(); IntPtr bstr = Marshal.SecureStringToBSTR(pwd); // Get an instance of the Sring class // only for the example needs. try{ Console.WriteLine( Marshal.PtrToStringAuto(bstr) ); } finally{ Marshal.ZeroFreeBSTR(bstr); } } }
使用中往往很难避免将一个SecureString在内存中解密,甚至存放在一个字符串。一定要注意需要在特定的内容区域执行此操作,而写使用完后要尽快销毁,以免出现前文提到的安全问题。
保护特殊文件中的数据
在应用程序的配置文件中常常包含需要加密的设置。.NET中这通过System.Configuration.Configuration类来实现。下面示例演示了在一个名为TagPassword的配置参数中保存经过加密的MyPassword字符串。
using System.Configuration; class Program { static void Main() { SavePwd("MyPassword"); System.Console.WriteLine(LoadPwd()); } static void SavePwd(string pwd) { Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None ); cfg.AppSettings.Settings.Add("TagPassword", pwd ); cfg.AppSettings.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider" ); cfg.Save(); } static string LoadPwd() { Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None ); return cfg.AppSettings.Settings["TagPassword"].Value; } }
如上,在保存操作的时候需要进行处理,以指定我们希望保存一个加密的版本,而在配置文件被读取的时候加密信息会自动被解密。