切换到宽版
  • 1558阅读
  • 2回复

在Windows中存储秘密数据 [复制链接]

上一主题 下一主题
 

只看楼主 倒序阅读 0 发表于: 2012-10-17
    有一句关于保守秘密的格言:“如果三个人可以保守一个秘密的话,那么其中两人一定是死人。”本文就将讲述计算机系统中的秘密数据处理问题。
  开发人员经常问我,在建立安全系统的时候,如何才能安全地存储秘密信息?秘密信息包括只能有一个或多个合法计算机、用户或应用程序知道的数据,比如口令、对其它数据进行解密的关键字等等。本文就介绍解决这种问题的方法,涵盖了一些将秘密存储在不同的Windows平台上的最好方法。
有时候你并不需要存储秘密
  如果你存储一个秘密信息的目的只是为了验证另一个实体是否也知道这个秘密信息,那么你可能就不需要存储这个秘密信息本身。反之,你可以存储一个“验证符”,它通常就是这个秘密信息的hash数值。例如,如果应用程序需要验证一个用户的口令值,就可以将用户输入的秘密信息的hash数值与存储秘密信息的hash数值做比较。这样,应用程序并没有存储秘密信息本身,从而引出的风险也要少些:即使攻击者闯入了系统,他们也无法恢复秘密信息本身。
  为了给攻击者带来更多困难,你还可以给hash数值加点“盐”。“盐”是一个随即数,将它加到hash数值上以阻止预先计算好的字典式攻击,这样想要恢复原始的秘密信息就非常困难。“盐”与hash数值存储在一起。选择hash函数是一个重要决定,而且一定要使用密码性很强的hash函数,这样的hash函数已经被证明没有冲突的可能,或者是这种可能性相当低。换句话说就是,创建两个能计算出同一hash数值的数据是行不通的。现在最常使用的hash函数是SHA-1。MD5已经有些过时了,因为在其算法中有一些小小的弱点。
  现在来看看存储秘密信息的hash数值以及确认秘密信息所需要的步骤:
  1、存储加过“盐”的has数值
确定你希望保护的秘密信息。例如:一个用户口令。
使用CryptGenRandom()函数生成一个随机的128位数。这就是“盐”。
在秘密信息上运行一个hash函数[使用CryptCreateHash()和CryptHashData()]。
将盐与hash数值相加[使用CryptHashData()]。
存储盐和hash数值。   2、验证用户知道这个秘密信息
从用户处取得这个秘密信息。
在秘密信息上运行一个hash函数[使用CryptCreateHash()和CryptHashData()]。
从存储的位置取得hash数值和盐。
将盐添加到hash数值上[使用CryptHashData()]。
比较这两个加了盐的数值,如果它们相同,那么说明用户知道这个秘密信息。   你能看到,不用存储秘密信息就能解决问题了,而且这样比存储秘密信息还好。但是有时也必须要存储秘密信息。现在来看看存储秘密信息的一个安全方法。
存储秘密信息的最安全方法
  存储和保护秘密信息的最安全方法是从用户那里获取一个输入,这个输入可以作为对被保护对象进行加密和解密的关键字。换句话说就是,秘密信息是由保存在用户头脑中的数据保护的。
  但是,按照这种方法保存秘密信息对于大多数用户而言经常会变得没有用。你让他们记住的信息条目(口令数)越多,他们就越有可能一遍又一遍地使用同一个口令,这样就降低了系统的安全性和可用性,从而增加了复杂性。
  现在让我们将注意力转移到更为复杂的问题上:不提示用户定义关键字而存储秘密信息。
最简单的情况:Windows2000
  在Windows2000上存储秘密信息是很简单的。要存储一个登录用户的数据时,可以使用数据保护APIs (DPAPI)、CryptProtectData()和CryptUnprotectData()。这些函数使用从用户的口令中衍生出来的一个关键字对数据进行加密/解密。只有拥有登录信用的用户才能对数据进行破解,所谓的登录信用要与那些加密数据相匹配。另外,只有在对数据进行加密的计算机上才能进行解密。但是一个拥有漫游特性的用户可以从网络上的另一个计算机对数据进行解密。CryptProtectData()还向加密的数据增加了一个键入的完整性检查,称为MAC或信息证明代码,目的是为了防止数据遭到破坏。
  要为一个系统组件或服务存储数据时,你也可以使用LSA秘密信息[LsaStorePrivateData()和LsaRetrievePrivateData()]。能够使用LSA秘密信息的服务必须是存储了特地服务的秘密信息,而不是特定于用户的或每个用户的秘密信息。这是因为LSA只能为每个系统存储4,096个秘密信息。在这4,096个秘密信息中,系统预留了一半供自己使用。
一个比较简单的情况:Windows NT 4
  Windows NT 4没有DPAPI,但是它有 CryptoAPI 和访问控制列表ACLs。调用CryptGenRandom()得到一个关键字,然后用这个关键字对你希望保护的数据进行加密,将这个关键字存储在一个可以被列入访问控制列表的资源中(如注册表),并在资源上定义一个ACL-这个ACL只允许你的应用程序读取它。一个典型的ACL只包含“创建者/所有者”的完全控制权限和“管理员”的完全控制权限。如果你还不放心,就在资源上再放置一个审核用的ACE(SACL)。
  你还可以使用LSA秘密信息[LsaStorePrivateData()和 LsaRetrievePrivateData()],就象前面Windows2000部分所讨论的那样。
  关于LSA秘密信息的一个注解:如果管理员对计算机有物理访问权的话,他可以查看用LSA保护的秘密信息,查看时可以使用BindView的LSADump2.exe。
其他的Windows家族
  Windows 95、Windows 98、Windows ME和Windows CE 3.0中都有CryptoAPI形式的内置密码功能,但是它们都没有ACL,因而它们也都没有识别的概念。在注册表或文件等资源中保存秘密数据很容易,但是应该把对数据加密时用的关键字存储在哪里呢?也存储在注册表中吗?你又如何使之安全呢?
  这是一个困难的问题。在试图解决它之前,你应该了解一些事情:
  1、你可以将秘密信息就隐藏在这些平台上,但是这会比隐藏在Windows NT 4或Windows2000上要容易发现。简单地说,如果被保护的数据是高风险的(如医学数据),那么就要考虑使用Windows2000,除非你从一个用户或一个外部来源那里获取了对数据加密或解密的关键字。
  2、使用这些平台时,可以调用CryptGenRandom()来得到一个关键字。然后,你可以把这个关键字存储在注册表中,并从物理设备上的某些内容中得出一个关键字,如卷名、设备名、视频卡名等等,然后用这个关键字与第一个关键字再进行加密。我敢打*你现在希望Intel将Pentium III的系列号一起装箱运来了,是不是?代码可以读取“设备”信息来得到解开注册表关键字的关键字。当然,如果这些改变了,那么数据也就丢失了。
  其实这些中没有一个是真正安全的,但是对于你试图保护的数据来说,它也许足够安全了,认识到这一点很重要。一定要提示用户,应用程序是尽可能努力地存储秘密信息的。
  不管你所工作的是哪种平台,都应该使用一切可用的资源。要利用好操作系统。如果你的应用程序在所有Windows平台(Windows 2000、WinNT、Win9x和WinCE)上都能工作,那么就应使用最合适的操作系统。
对秘密信息进行编码
  在代码中处理秘密信息时,你应该将其停留在明码(也就是非密码)中的时间减到最少,这样就会降低秘密信息泄露到页面文件中的风险。一旦在代码中使用了秘密信息,就应假数据覆盖缓冲器,使用的语句是memset()。 关于如何最好地“擦洗掉”未使用的数据有许多理论,但是以下这个C/C++ 代码的片段就足够了:
  void ScrubBlob(void *b, DWORD cb) {
  for (int i=0; i < 7; i++) {
  memset(b,0xFF,cb); // all 1's
  memset(b,0x00,cb); // all 0's
  memset(b,0xAA,cb); // 10101010
  memset(b,0x55,cb); // 01010101
  }
  ZeroMemory(b,cb);
  }
最后一个技巧
  如果应用程序中包含测试例程代码,那么一定不要采取默认方式最后安装它们。多年以来,许多Internet上的系统都因其测试例程而容易遭受攻击,因为那些被默认包含的测试应用程序本身就有许多弱点。
评价一下你浏览此帖子的感受

精彩

感动

搞笑

开心

愤怒

无聊

灌水
离线何佳诚

只看该作者 1 发表于: 2012-10-17
学习学习
努力学习

只看该作者 2 发表于: 2012-10-18
好好学习了,真的不错哦