simplemembershipprovider,安全密码和加密助手

有些人质疑ASP.NET SimpleMembershipprovider的密码的安全性。关注的原因似乎源于SimpleMembershivider未使用标准会员表中的PasswordSalt字段,而它由例如SqlMembershipProvider使用。那么是什么是passwordsalt字段,为什么它没有用来养眉毛?

每个人都知道你不应该以纯文本,右边存储用户密码?这样做是非常不负责任的。可能是您提供的内容不高的价值,并且如果有人管理损害您的密码存储,则不会使用任何凭据损益以登录您网站中的受限区域。但是,这是一个事实上,许多人在多个网站中重用密码,所以您最终可能会使恶意用户与某人的钥匙完全连接到另一个王国。如果您要求人们向您提供个人信息,那么您欠那些人的关怀责任。

那么你应该如何存储密码?通常,存储密码的接受方式不是要存储它。你应该代替哈希。哈希函数是一种算法,它采用数据块,例如字符串,并基于输入生成新值或散列。哈希固定尺寸。它们也是决定性的。这意味着经过相同算法的相同值将始终导致相同的哈希。当用户首先使用您的网站注册时,您将采用其提供的密码,散列并将哈希存储在数据库中。每当他们登录之后,您就会提交的密码,哈希,并将其与存储在数据库中的哈希进行比较。

哈希是单向的。从给定哈希计算原始值应该是不可行的。人们有一种使用真实单词和名称作为密码的习惯。当使用真实单词时,重复密码的可能性会增长,并且由于散列是确定性的,因此哈希对于共享相同密码的用户将是相同的。密码表中的重复哈希的存在是使用真实单词或名称的良好指标。黑客可以使用许多技术来尝试破解散列密码,包括 蛮力 , 字典攻击彩虹桌子。所有这些方法都无法对包括的散列无效"".

在加密术语中,盐是在散列之前与密码连接的随机值。如果您盐散,则需要在散列它之前将盐本身包含在密码中,以便与存储的值进行比较。现在你开始看看为什么人们关注的是SimpleMembershipProvider - 由于没有使用数据库中的密码字段,因此看起来密码哈希不盐,因此不如他们应该的那么安全。

好消息是出场可能是欺骗性的,而且SimpleMembershipProvider确实盐散。事实上,SimpleMembershipprovider利用了鲜为人知的人 Crypto Helper. 谁的 hashpassword. 方法对本文最有兴趣:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 0:
 * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
 * (See also: SDL crypto guidelines v5.1, Part III)
 * Format: { 0x00, salt, subkey }
 */

public static string  hashpassword. (string password)
{
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    // Produce a version 0 (see comment above) password hash.
    byte[] salt;
    byte[] subkey;
    using (var deriveBytes = new RFC2898DERIVEBYTES.(password, SaltSize, PBKDF2IterCount))
    {
        salt = deriveBytes.Salt;
        subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
    }

    byte[] outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];
    Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
    Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);
    return Convert.ToBase64String(outputBytes);
}

此方法获取密码,并将其传递给A的构造函数 RFC2898DERIVEBYTES. 班级。类构造函数还需要盐的大小,它应该生成(128位)和它应该将算法应用于密码/盐组合的次数以产生安全哈希。它生成了自己的密码强大的盐,并将其作为字节阵列与哈希返回。然后,该方法构造由(按顺序)一个空字节(0x00),16个字节组成的新字节阵列,其中包含盐值的另外32个字节,包含散列盐+密码。然后将其编码为由该方法返回的Base64字符串,这是存储在密码字段中的值。

Crypto Helper. 还提供另一种方法 - VerifyHashedPassword:

// hashedPassword must be of the format of HashWithPassword (salt + Hash(salt+input)
public static bool VerifyHashedPassword.(string hashedPassword, string password)
{
    if (hashedPassword == null)
    {
        throw new ArgumentNullException("hashedPassword");
    }
    if (password == null)
    {	
        throw new ArgumentNullException("password");
    }

    byte[] hashedPasswordBytes = Convert.FromBase64String(hashedPassword);

    // Verify a version 0 (see comment above) password hash.

    if (hashedPasswordBytes.Length != (1 + SaltSize + PBKDF2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
    {
         // Wrong length or version header.
         return false;
    }

    byte[] salt = new byte[SaltSize];
    Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
    byte[] storedSubkey = new byte[PBKDF2SubkeyLength];
    Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PBKDF2SubkeyLength);

    byte[] generatedSubkey;
    using (var deriveBytes = new RFC2898DERIVEBYTES.(password, salt, PBKDF2IterCount))
    {
         generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
    }
    return ByteArraysEqual(storedSubkey, generatedSubkey);
}

此方法提供了用户在登录时输入的密码,以及存储在特定用户的密码字段中的值。然后它扭转了部分工作的一部分 hashpassword. 方法 - 它将Base64编码的字符串转换回一个字节数组,然后提取该阵列的散列部分和盐。然后,它将盐散列提交的密码,并将结果与​​检索到的哈希进行比较。

如果您只需要简单(但安全)的密码存储和验证,则可以使用Crypto Helper方法而无需配置和使用SimpleMembershipProvider:

@{
    var db = Database.Open("MyDb");
    var username = Request["UserName"];
    var password = Request["Password"];
    var hash = Crypto.HashPassword(password);
    db.Execute("INSERT INTO Users (Username, Password) VALUES (@0, @1)", username, hash);
}

帮助程序中的其他有趣方法提供了生成SHA1和SHA256哈希度以及更多通用方法,这些方法将返回所指定的算法生成的哈希值。还有一种用于产生盐的实用方法。

那么为什么有一个未使用的列名为passwordsalt的未使用的列?谁知道,但我的猜测是模式是在加密助手之前设计的,因为HashPassword方法返回一个表示编码和组合盐和密码哈希的一个字符串,因此决定不打扰解构填充passwordsalt字段。无论是那个,还是有人根本忘了删除它。