Rfc2898DeriveBytes + PBKDF2 + SecureString 是不是可以使用安全字符串而不是字符串?
Posted
技术标签:
【中文标题】Rfc2898DeriveBytes + PBKDF2 + SecureString 是不是可以使用安全字符串而不是字符串?【英文标题】:Rfc2898DeriveBytes + PBKDF2 + SecureString is it possible to use a secure string instead of a string?Rfc2898DeriveBytes + PBKDF2 + SecureString 是否可以使用安全字符串而不是字符串? 【发布时间】:2012-04-01 19:20:27 【问题描述】:我有一个函数GetPassword
,它返回一个SecureString
类型。
当我将此安全字符串传递给 Rfc2898DeriveBytes
以生成密钥时,Visual Studio 显示错误。我有限的知识告诉我这是因为Rfc2898DeriveBytes
只接受一个字符串而不接受一个安全字符串。有解决办法吗?
//read the password from terminal
Console.Write("Insert password");
securePwd = myCryptography.GetPassword();
//dont know why the salt is initialized like this
byte[] salt = new byte[] 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 ;
try
//PBKDF2 standard
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(securePwd, salt, iterationsPwd);
【问题讨论】:
...哦,你,我不知道为什么盐是这样的:D 我现在在做什么,是我使用securePwd.ToString 问题是它使使用secureString的整个过程毫无价值:D 就是,以//read the password from terminal
开头的
ToString()
返回SecureString
的内容?你确定吗?文档不是这么说的。
【参考方案1】:
我发现有趣的是 Rfc2898DeriveBytes
类不支持 SecureString
重载来传递用于派生密钥的密码。
WPF 允许使用PasswordBox
控件将密码作为SecureString
对象处理。由于我们无法将SecureString
传递给构造函数,因此失去了该控件提供的附加安全性,这似乎是一种浪费。然而,erickson 提出了使用byte[]
而不是string
重载的优点,因为在内存中正确管理byte[]
的内容比string
更容易.
以 erickson 的 建议为灵感,我想出了以下包装器,该包装器应该允许使用受 SecureString
保护的密码值,而内存中的明文值暴露最少。
private byte[] DeriveKey(SecureString password, byte[] salt, int iterations, int keyByteLength)
IntPtr ptr = Marshal.SecureStringToBSTR(password);
byte[] passwordByteArray = null;
try
int length = Marshal.ReadInt32(ptr, -4);
passwordByteArray = new byte[length];
GCHandle handle = GCHandle.Alloc(passwordByteArray, GCHandleType.Pinned);
try
for (int i = 0; i < length; i++)
passwordByteArray[i] = Marshal.ReadByte(ptr, i);
using (var rfc2898 = new Rfc2898DeriveBytes(passwordByteArray, salt, iterations))
return rfc2898.GetBytes(keyByteLength);
finally
Array.Clear(passwordByteArray, 0, passwordByteArray.Length);
handle.Free();
finally
Marshal.ZeroFreeBSTR(ptr);
这种方法利用了BSTR 是一个指针,该指针指向具有四字节长度前缀的数据字符串的第一个字符。
要点:
通过将Rfc2898DeriveBytes
包装在 using 语句中,可确保以确定的方式处理它。这很重要,因为它有一个内部 HMACSHA1
对象,它是一个 KeyedHashAlgorithm
,并且需要在调用 Dispose 时将其拥有的密钥(密码)的副本清零。详情请参阅Reference Source。
一旦我们完成了BSTR
,我们会将其归零并通过ZeroFreeBSTR 释放它。
最后,我们将密码副本归零(清除)。
更新:添加了byte[]
的固定。正如 answer 的 cmets 中所讨论的,如果 byte[]
未固定,那么垃圾收集器可以在收集期间重新定位对象,我们将无法将原始副本归零。
这应该将明文密码保留在内存中的时间最短,并且不会过多地削弱使用SecureString
的收益。虽然,如果攻击者可以访问 RAM,您可能会遇到更大的问题。另一点是我们只能管理我们自己的密码副本,我们使用的 API 很可能管理不善(不是清零/清除)他们的副本。据我所知,Rfc2898DeriveBytes
的情况并非如此,尽管他们的byte[]
密钥(密码)副本没有被固定,因此如果在归零之前在堆中移动数组的痕迹可能会残留出去。这里的信息是代码看起来很安全,但问题可能隐藏在下面。
如果有人在此实现中发现任何严重漏洞,请告诉我。
【讨论】:
这正是我希望找到的,而且我相信这是最安全的。在我的简短研究中,我相信你已经涵盖了每一点。因为Rfc2898DeriveBytes
的实现没有固定byte[]
,如果垃圾收集器重新定位它,内存可能会在更长的时间内可用,从而暴露攻击向量。但是,我们知道如果攻击者可以访问 RAM,这是我们最不关心的问题。您的解决方案将未固定敏感数据的外观限制为 API 的行为。
我想知道我们是否可以直接返回rfc2898
,同时仍然清除passwordByteArray
。然后,您可以在 DeriveKey
函数调用上使用 using
。似乎你可以从Rfc2898DeriveBytes
uses HMAC
开始执行此操作,它会创建一个.Clone()
of your byte array。
它也是clears the array on disposalcalling the base method。阵列虽然没有固定。通过返回DeriveKey
,您可以根据需要生成任意数量的字节。我在powershell中测试了返回rfc2898
,它似乎工作。使用固定盐更改密码会提供不同的密钥,这意味着密码被克隆并且不会被归零。【参考方案2】:
显然,您可以违反SecureString
和expose its internal state via the Marshal.SecureStringToBSTR()
function. 提供的保护
与其从结果中创建String
,不如将内容复制到Byte[]
以传递给Rfc2898DeriveBytes
。创建String
可以防止您破坏密码信息,使其无限期地挂在堆中,或被分页到磁盘,这反过来又增加了攻击者找到它的机会。相反,您应该在使用完密码后立即销毁密码,方法是用零填充数组。出于同样的原因,您还应该在将BSTR
的每个元素复制到Byte[]
时为其分配一个零。
应该为每个散列密码随机选择盐,而不是一个固定的、可预测的值,否则可能会发生预先计算的字典攻击。您应该迭代数万次以防止暴力攻击。
【讨论】:
谢谢,但我需要在一台机器上用密码加密文件,然后在另一台机器上用相同的密码解密。因此我认为盐必须相同,否则第二台机器将无法解密它。我说的对吗? 我选择另一个作为答案,因为它更“正式”,但我真的很感谢你的棘手建议 @NoobTom 是的,当然两台机器上的盐必须相同。当使用密码散列进行身份验证时,应存储散列、盐和迭代次数,以便在出现密码时重新计算散列,以查看新散列是否与存储的散列匹配。永远不要使用硬编码的哈希进行密码验证。 注意:这也取决于 API 负责管理和归零它自己的潜在密钥副本。查看Reference Source:只要密码字节数组为64 字节或更少,那么Rfc2898DeriveBytes
对象内部的HMACSHA1
对象就有它自己的密码字节数组的克隆副本。值得庆幸的是,HMACSHA1
是一个 KeyedHashAlgorithm
,它在对 Dispose 的调用中提供了它的密钥副本的归零。
我还要补充一点,固定byte[]
也很重要。如果byte[]
没有固定,那么 GC 可以在收集期间重新定位它,让我们无法将原始数组归零。【参考方案3】:
在进行了一些研究并查看了 *** 上提到 SecureString
的先前答案之后,该答案几乎肯定是:“否”。只有 API 的创建者可以接受SecureString
并在内部正确处理它。他们只能在平台的帮助下做到这一点。
如果您 - 作为用户 - 可以检索纯文本 String
,那么您首先会否定使用 SecureString
的大部分优势。这甚至会有点危险,因为您会创建看起来安全的代码,实际上根本不安全(编辑:至少在保护内存数据方面不是)。
【讨论】:
以上是关于Rfc2898DeriveBytes + PBKDF2 + SecureString 是不是可以使用安全字符串而不是字符串?的主要内容,如果未能解决你的问题,请参考以下文章
.NET:PasswordDeriveBytes 和 Rfc2898DeriveBytes 之间的区别
Rfc2898DeriveBytes + PBKDF2 + SecureString 是不是可以使用安全字符串而不是字符串?
使用 Rfc2898DeriveBytes 在 C# 中实现 PBKDF2
Crypto++ pbkdf2 输出不同于 Rfc2898DeriveBytes (C#) 和 crypto.pbkdf2 (JavaScript)