安全字符串 SecureString 的设计与实现 [1] 现有问题与解决方法
2004-07-08 11:56
1086 查看
http://www.blogcn.com/user8/flier_lu/index.html?id=2309231&run=.0C9B086
随着安全性编程逐渐受到重视,我们需要面对一些以前容易忽视的安全隐患。例如在一个系统字符串中保存当前用户密码或其他敏感信息,则具备权限的其他进程可以很轻松的通过系统提供的 ReadProcessMemory 函数或调试接口,搜索并读取这个字符串的内容,进而了解对此字符串的维护逻辑。在破解软件时一个很常见的方法,就是提供一个特殊的注册码,然后用调试器找到注册算法保存注册码的位置,再通过设置数据断点跟踪注册码的验证算法。要避免这种安全隐患,一个简单的办法是将字符串加密再保存,只在使用的时候解密使用,这样可以一定程度避免敏感信息泄露。CLR 中系统提供的 System.String 虽然功能强大,但因为系统封装的不透明性以及设计上的一些硬伤,使之不适用于保存敏感数据。Shawn Farkas 在其 BLog 的一篇文章(Making Strings More Secure)中讨论了 Whidbey 中为什么要用一个新的 System.Security.SecureString 替换现有 System.String 来实现类似功能。
首先看看 System.String 为什么不适合保存敏感数据的需求
1.字符串的内存是在堆中分配的,也就是说其内存完全由 GC 来管理。GC 在进行垃圾收集时,根据具体使用算法不同,完全可能将保存明文密码的字符串在内存中多次拷贝,并留下多个副本,造成安全隐患。
2.字符串的内容是不加密的,其他进程可以很容易被其他进程通过读进程内存的方式访问。如果进程数据页被交互到硬盘,则还会在硬盘的交换文件中保存敏感数据的内容。
3.字符串是不可变的,因此一旦要修改一个字符串,则会在内存中留下新旧两份字符串。具体原因和 CLR 内部优化策略请参见我另外一篇文章《CLR中字符串不变性的优化》。
4.因为字符串不可变,所以没有什么好的办法能够显式清除一个字符串的内容。
在 Whidbey 之前,一般推荐使用字节数组来保存敏感数据,因为字节数组可以被 pin 到一个固定内存位置,并能够显式加密和清除内容。而从 Whidbey 开始,将引入一个新的 System.Security.SecureString 类型专门处理自动加密的需求。
SecureString 将使用 DPAPI (Data Protection Application-Programming Interface) 完成字符串内容的加密工作,并确保 GC 不会自动处理字符串内容,而是通过 IDisposable 接口和 finalizer 完成加密字符串资源的生命期维护工作。同时 SecureString 还支持将自己设置为只读,避免其他代码修改其内容。
SecureString 可以从一个数组构造而来,也可以建立空字符串后一个字符一个字符地添加。可以通过 AppendChar()、InsertAt()、RemoveAt() 和 SetAt() 函数对字符串内容进行字符粒度的维护;MakeReadOnly() 和 IsReadOnly() 函数可以确保字符串的只读性;Clear()、Dispose()和 finalizer 可以对字符串的生命期进行维护。
下面是一个使用 SecureString 的简单例子:
而在使用 SecureString 的时候也需要注意不要通过 System.String 操作,否则就白忙了,呵呵。可以用类似下面代码的方法,直接操作其内容,如
然后,来看看 SecureString 使用的 DPAPI 是如何对数据进行保护的。Shawn Farkas 在其 Managed DPAPI Part I: ProtectedData 和 Managed DPAPI Part II: ProtectedMemory 两篇文章里面简要的介绍了 DPAPI 的功能和使用方法。
简单说来常用的就是两对函数,CryptProtectData 和 CryptUnprotectData 使用给定的密钥对指定数据块进行加/解密;CryptProtectMemory 和 CryptUnprotectMemory 则通过 LSA 提供的在一定范围内有效的密钥对数据进行加/解密。前者适用于给定密钥情况下;后者则根据指定范围不同,支持进程内、跨进程和同一登陆帐号等不同范围内的透明加/解密。对面向运行时需求的 SecureString 来说,使用后者足以;而如果需要将加密后内容序列化到磁盘文件或数据库,则需要使用前者。此外还可以通过 CryptAPI 相关函数,提供跨网络的加/解密支持,这是上述两者无法提供的。
最后,我根据 Whidbey 的 SecureString 实现,移植了一个版本到 v1.1 下,但因为缺少 CER (Constrained Execution Regions) 和 CF (Critical Finalization) 的支持,其安全性还是无法完全保障,权且作为 v2.0 之前的过渡品吧,呵呵
to be continue...
随着安全性编程逐渐受到重视,我们需要面对一些以前容易忽视的安全隐患。例如在一个系统字符串中保存当前用户密码或其他敏感信息,则具备权限的其他进程可以很轻松的通过系统提供的 ReadProcessMemory 函数或调试接口,搜索并读取这个字符串的内容,进而了解对此字符串的维护逻辑。在破解软件时一个很常见的方法,就是提供一个特殊的注册码,然后用调试器找到注册算法保存注册码的位置,再通过设置数据断点跟踪注册码的验证算法。要避免这种安全隐患,一个简单的办法是将字符串加密再保存,只在使用的时候解密使用,这样可以一定程度避免敏感信息泄露。CLR 中系统提供的 System.String 虽然功能强大,但因为系统封装的不透明性以及设计上的一些硬伤,使之不适用于保存敏感数据。Shawn Farkas 在其 BLog 的一篇文章(Making Strings More Secure)中讨论了 Whidbey 中为什么要用一个新的 System.Security.SecureString 替换现有 System.String 来实现类似功能。
首先看看 System.String 为什么不适合保存敏感数据的需求
1.字符串的内存是在堆中分配的,也就是说其内存完全由 GC 来管理。GC 在进行垃圾收集时,根据具体使用算法不同,完全可能将保存明文密码的字符串在内存中多次拷贝,并留下多个副本,造成安全隐患。
2.字符串的内容是不加密的,其他进程可以很容易被其他进程通过读进程内存的方式访问。如果进程数据页被交互到硬盘,则还会在硬盘的交换文件中保存敏感数据的内容。
3.字符串是不可变的,因此一旦要修改一个字符串,则会在内存中留下新旧两份字符串。具体原因和 CLR 内部优化策略请参见我另外一篇文章《CLR中字符串不变性的优化》。
4.因为字符串不可变,所以没有什么好的办法能够显式清除一个字符串的内容。
在 Whidbey 之前,一般推荐使用字节数组来保存敏感数据,因为字节数组可以被 pin 到一个固定内存位置,并能够显式加密和清除内容。而从 Whidbey 开始,将引入一个新的 System.Security.SecureString 类型专门处理自动加密的需求。
SecureString 将使用 DPAPI (Data Protection Application-Programming Interface) 完成字符串内容的加密工作,并确保 GC 不会自动处理字符串内容,而是通过 IDisposable 接口和 finalizer 完成加密字符串资源的生命期维护工作。同时 SecureString 还支持将自己设置为只读,避免其他代码修改其内容。
SecureString 可以从一个数组构造而来,也可以建立空字符串后一个字符一个字符地添加。可以通过 AppendChar()、InsertAt()、RemoveAt() 和 SetAt() 函数对字符串内容进行字符粒度的维护;MakeReadOnly() 和 IsReadOnly() 函数可以确保字符串的只读性;Clear()、Dispose()和 finalizer 可以对字符串的生命期进行维护。
下面是一个使用 SecureString 的简单例子:
以下为引用: public static SecureString GetPassword() { SecureString password = new SecureString(); // get the first character of the password ConsoleKeyInfo nextKey = Console.ReadKey(true); while(nextKey.Key != ConsoleKey.Enter) { if(nextKey.Key == ConsoleKey.BackSpace) { password.RemoveAt(password.Length - 1); // erase the last * as well Console.Write(nextKey.KeyChar); Console.Write(" "); Console.Write(nextKey.KeyChar); } else { password.AppendChar(nextKey.KeyChar); Console.Write("*"); } nextKey = Console.ReadKey(true) } Console.WriteLine(); // lock the password down password.MakeReadOnly(); return password; } |
以下为引用: IntPtr bstr = Marshal.SecureStringToBSTR(password); try { // ... // use the bstr // ... } finally { Marshal.ZeroFreeBSTR(bstr); } |
简单说来常用的就是两对函数,CryptProtectData 和 CryptUnprotectData 使用给定的密钥对指定数据块进行加/解密;CryptProtectMemory 和 CryptUnprotectMemory 则通过 LSA 提供的在一定范围内有效的密钥对数据进行加/解密。前者适用于给定密钥情况下;后者则根据指定范围不同,支持进程内、跨进程和同一登陆帐号等不同范围内的透明加/解密。对面向运行时需求的 SecureString 来说,使用后者足以;而如果需要将加密后内容序列化到磁盘文件或数据库,则需要使用前者。此外还可以通过 CryptAPI 相关函数,提供跨网络的加/解密支持,这是上述两者无法提供的。
以下为引用: BOOL WINAPI CryptProtectData( DATA_BLOB* pDataIn, LPCWSTR szDataDescr, DATA_BLOB* pOptionalEntropy, PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags, DATA_BLOB* pDataOut ); BOOL WINAPI CryptUnprotectData( DATA_BLOB* pDataIn, LPWSTR* ppszDataDescr, DATA_BLOB* pOptionalEntropy, PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags, DATA_BLOB* pDataOut ); BOOL CryptProtectMemory( LPVOID pData, DWORD cbData, DWORD dwFlags ); BOOL CryptUnprotectMemory( LPVOID pData, DWORD cbData, DWORD dwFlags ); |
to be continue...
相关文章推荐
- 零拷贝报文捕获要解决的几个问题,和现有的实现方法
- 设计实现OJ平台的遇到的一些问题和解决方法
- 应用框架的设计与实现——.NET平台4.2代码测试问题解决
- J2EE工程实现中常见安全问题解决对策
- 有关Powerdesgner数据模型设计中,属性名同名问题的解决方法
- Div + Css 设计网页时浮动问题的解决方法
- 设计新一代的网游——第二章(网游现存问题及解决方法)
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- J2EE工程实现中常见安全问题解决对策
- 关于SQL的安全问题(ftp.exe、cmd.exe的解决方法)
- java字符串星号、问号匹配问题解决方法
- Windows 操作系统常见安全问题解决方法(3)
- J2EE工程实现中常见安全问题解决对策
- ASP 二进制与字符串互转, 另类完美解决方法, adodb.stream 实现 By shawl.qiu