如何在C#中验证加盐和哈希密码

8
我使用以下方法对密码进行加盐和哈希处理。
public string CreateSalt(int size)
{
    var rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
    var buff = new byte[size];
    rng.GetBytes(buff);
    return Convert.ToBase64String(buff);
}
public string GenerateSHA256Hash(String input, String salt)
{
    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(input + salt);
    System.Security.Cryptography.SHA256Managed sha256hashstring =
        new System.Security.Cryptography.SHA256Managed();
    byte[] hash = sha256hashstring.ComputeHash(bytes);
    return Convert.ToBase64String(hash);
}
public void Submit1_click(object sender, EventArgs r)
{

    try
    {
        String salt = CreateSalt(10);
        String hashedpassword = GenerateSHA256Hash(password1.Text, salt);
        string MyConString = "SERVER=localhost;DATABASE=mydb;UID=root;PASSWORD=abc123;";
        MySqlConnection connection = new MySqlConnection(MyConString);
        string cmdText = "INSERT INTO authentication(agentlogin ,password ,question ,answer)VALUES ( @login, @pwd, @question, @answer)";
        MySqlCommand cmd = new MySqlCommand(cmdText, connection);
        cmd.Parameters.AddWithValue("@login", labeluname.Text);
        cmd.Parameters.AddWithValue("@pwd", hashedpassword);
        cmd.Parameters.AddWithValue("@question", ddlquestion.Text);
        cmd.Parameters.AddWithValue("@answer", txtanswer.Text);
        connection.Open();
        int result = cmd.ExecuteNonQuery();
        connection.Close();
        lblmsg.Text = "Registered succesfully";
        lblmsg.ForeColor = System.Drawing.Color.Green;
        Response.Redirect("index.aspx");
    }
    catch (Exception)
    {
        Console.Write("not entered");
        lblmsg.Text = "Registration failed!";
        lblmsg.ForeColor = System.Drawing.Color.Red;
        Response.Redirect("index.aspx");
    }
}

我已经从上面得到了完全加密的密码,但现在我无法使用输入的密码登录。当登录时,如何取消加盐密码?我想我可以使用与加密相同的方法来解密它,但是加盐不会返回相同的值。 以下是验证页面上的代码

    public string GenerateSHA256Hash(String input)
    {
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(input);
        System.Security.Cryptography.SHA256Managed sha256hashstring =
            new System.Security.Cryptography.SHA256Managed();
        byte[] hash = sha256hashstring.ComputeHash(bytes);
        return Convert.ToBase64String(hash);
    }

    public void Login_click(object sender, EventArgs r)
    {
        String hashedpassword = GenerateSHA256Hash(txtpassword.Text);
        string MyConString = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
        MySqlConnection con = new MySqlConnection(MyConString);
        MySqlCommand cmd = new MySqlCommand("select * from authentication where agentlogin=@username and password=@word", con);
        cmd.Parameters.AddWithValue("@username", txtusername.Text);
        cmd.Parameters.AddWithValue("@word", hashedpassword);
        MySqlDataAdapter sda = new MySqlDataAdapter(cmd);
        DataTable dt = new DataTable();
        sda.Fill(dt);
        con.Open();
        int i = cmd.ExecuteNonQuery();
        con.Close();
        if (dt.Rows.Count > 0)
        {
            Session["id"] = txtusername.Text;
            Response.Redirect("calendar.aspx");
            Session.RemoveAll();
        }
        else
        {
            lblmsg.Text = "Credential doesn't match!";
            lblmsg.ForeColor = System.Drawing.Color.Red;

        }

    }

加盐和加密的密码永远不会被解盐或解密。如果您必须匹配两个密码,则必须从用户那里获取密码,然后首先对其进行加盐,然后再加密。然后将此加盐和加密的输入密码与数据库中的加盐和加密的密码进行比较,明白了吗? - er-sho
因为SHA1或SHA256密码一旦加密,就永远无法解密。 - er-sho
那么当登录时,我只需要反过来做吗? - prkash
是的,对吗?如果相等,那么简单地登录用户,否则向用户弹出消息,即“您的用户名或密码不正确”。 - er-sho
@ershoaib 我会尝试并让你知道。 - prkash
显示剩余2条评论
3个回答

24

在你的用户表中创建一个名为UsernameHashSalt的列。

用户注册

1) 在你的注册表单中从用户输入usernamepassword

2) 使用以下方法为输入密码创建Hash和Salt。

public class HashSalt
{
    public string Hash { get; set; }
    public string Salt { get; set; }
}

public static HashSalt GenerateSaltedHash(int size, string password)
{
    var saltBytes = new byte[size];
    var provider = new RNGCryptoServiceProvider();
    provider.GetNonZeroBytes(saltBytes);
    var salt = Convert.ToBase64String(saltBytes);

    var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, saltBytes, 10000);
    var hashPassword = Convert.ToBase64String(rfc2898DeriveBytes.GetBytes(256));

    HashSalt hashSalt = new HashSalt { Hash = hashPassword, Salt = salt };
    return hashSalt;
}

Rfc2898DeriveBytes类用于使用RFC2898规范生成哈希,该规范使用一种称为PBKDF2(基于密码的密钥派生函数#2)的方法,目前被IETF(互联网工程任务组)推荐用于新应用程序。

3)然后将此HashSalt与用户记录一起存储在数据库中。

public void Submit1_click(object sender, EventArgs r)
{
    //Your code here

    HashSalt hashSalt = GenerateSaltedHash(64, password1.Text);

    //Your code here

    cmd.Parameters.AddWithValue("@hash", hashSalt.Hash);
    cmd.Parameters.AddWithValue("@salt", hashSalt.Salt);

    //You code here
}

用户登录

1)在您的登录表单中获取用户输入的用户名密码

2)在Login_click中从数据库中按用户名获取用户。

3)将存储的哈希值传递给以下函数。

public static bool VerifyPassword(string enteredPassword, string storedHash, string storedSalt)
{
    var saltBytes = Convert.FromBase64String(storedSalt);
    var rfc2898DeriveBytes = new Rfc2898DeriveBytes(enteredPassword, saltBytes, 10000);
    return Convert.ToBase64String(rfc2898DeriveBytes.GetBytes(256)) == storedHash;
}

4) 然后通过验证密码登录用户。

public void Login_click(object sender, EventArgs r)
{
    //You code here

    User user = GetUserByUsername(txtUsername.Text);

    bool isPasswordMatched = VerifyPassword(txtpassword.Text, user.Hash, user.Salt);

    if (isPasswordMatched)
    {
        //Login Successfull
    }
    else
    {
        //Login Failed
    }

    //Your code here
}

参考资料:有效密码哈希


1
@prkash,查看答案可能会对您有所帮助 :) - er-sho
运行正常...但是你什么时候给size赋值的?在我的情况下,我会像这样设置默认值(int size =16) - Qwerty

3

在将盐添加到密码并对其进行哈希之前,您必须先存储盐。

因此,当有人尝试登录时,您将密码与存储的盐连接起来,然后可以对其进行哈希并将其与基础中现有的哈希进行比较。

因此,您的用户表应至少具有以下3个列: 用户名、哈希密码、盐

更长的解释: 哈希函数是确定性的但不可逆的,因此当用户第一次创建密码时:

  • 您生成一个盐
  • 您将密码和盐连接起来
  • 您对连接的密码和盐的结果字符串进行哈希处理
  • 您保存哈希和盐

所以您有:hashedpassword = hashingfunction(password+salt)

当您尝试登录时:

  • 您获取登录名和密码作为输入
  • 使用登录名您可以检索基础中的哈希和盐
  • 您已经有了哈希函数
  • 因此,您可以处理hashingfunction(password_entered_by_user+salt)
  • 您将结果与hashedpassword进行比较,如果相同则记录用户

将盐值与哈希密码放在一起并不会使其 less secure:盐值的目的是防止使用 彩虹表

如果有人闯入您的数据库,他可能会瞄准您用户的电子邮件+密码,因为大多数人在很多不同的地方重复使用相同的密码和电子邮件。

现在,由于哈希函数是不可逆的,攻击者唯一能做的就是尝试猜测密码并创建一个包含以下内容的字典:

guessed_password => hash(guessed_password)

例:

pet345 => 23FD7890F0F3FA3AE468F37CB900402A1F1977CF926F3452CA519056E16985AB
lola78 => 876B42DC10A0822CC52B894DC7517C784A542B43FB033B4A93635ADA67946B2E
lola79 => A7DCAF5195463FA367CDEA6F23688C1280EC98F4AF2B08BC3469D2496537D48D
lola80 => 8D8E1CF212F5DDC3CA1D510900382DF945625A9AE1584CE0D539B2C4D73717CB

如果猜测的密码的哈希值存在于您的数据库中,他就知道这个(这些)用户的密码是猜测的密码。他可以生成数十亿个猜测的密码字典,由于很多用户没有使用真正强大的密码,他很可能能够在他的字典中找到许多用户的哈希值。因此,如果他为“lola80”和“lola79”生成哈希值,并且这是您的2个用户的密码,他就知道了。
现在,如果您为每个输入的密码添加随机盐,则对于每个盐,他都必须生成一个完整的字典,因为他必须执行以下操作:
guessed_password + salt = hash(guessed_password + salt)

对于带有盐值“09ç@p$”的用户A,他必须生成一个完整的字典,其中每个单词都以“09ç@p$”结尾。
现在,如果他想猜测与盐值“Yuè45gh”相关联的用户B的密码,他必须生成另一个字典,其中每个单词都以“Yuè45gh”结尾。
基本上,它通过你的用户数量因子来减慢猜测用户密码的过程。

如果我有 hashedpassword 列,它不会很安全,因为很容易找出相似的密码。 - prkash
@prkash,只有在相同的随机盐值下才会发生。如果盐值足够大,这种情况将极其罕见。如果您真的很担心,可以将用户的主键添加到盐值中,以便每个用户的盐值都不同。 - Adam G
2
@prkash 盐并不能对付有针对性的暴力攻击,强密码可以。盐主要对抗彩虹表攻击,并且能够大幅减缓攻击整个数据库的任何攻击方式。我会在我的回答中加入解释。 - Maxime

0
我想我可以使用加密时用的相同方法来解密它,但是加盐不会返回相同的值。
你的意思是将经过加盐的密码再次通过加盐机制发送,并期望得到未经哈希处理的明文密码吗?这没有任何意义。
所以,你将一个密码加盐并保存了起来。现在你要做的是,当用户输入密码时,你对这个新密码进行加盐并获得一个加盐密码。然后检查这个新密码是否与旧密码匹配;如果它们匹配,则是正确的密码。

是的,但加盐并不会每次返回相同的值,即使我再次加盐相同的密码,它也不会相同,只有哈希在使用相同的密码时才会每次返回相同的值。 - user1800114
那么你应该阅读@Maxime的答案。我认为他更好地理解了你的问题。正如他所解释的那样,你需要存储用于每个密码的盐。 - RUL
@prkash,如果盐值与哈希密码一起可见,这并不是问题。使用预先哈希的密码的攻击者将无法使用它们。要猜测您的密码并将其与您的盐值进行哈希以用于每个数据库条目,这需要太长时间。如果每个密码都有不同的盐值,则预先收集的彩虹表、查找表等都将无用。 - RUL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接