【问题标题】:How to validate salted and hashed password in c#如何在 C# 中验证加盐和散列密码
【发布时间】:2018-09-03 09:07:29
【问题描述】:

我使用以下方法对密码进行加盐和哈希处理

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;

        }

    }

【问题讨论】:

  • 加盐和加密的密码从未被解盐或解密。如果您必须匹配两个密码,那么您必须从用户那里获取密码,然后您必须先对其进行加盐然后对其进行加密。然后将这个加盐加密的输入密码与数据库中加盐加密的密码进行比较,明白了吗?
  • bcoz sha1 或 sha256 密码一旦加密就再也不会被解密
  • 那我是不是在登录的时候就逆着做呢?
  • 是的,对吗?如果相等,则只需登录用户,否则将消息填充给your username or password is incorrect的用户
  • 但是假设我的密码是“abc123”如果我加盐我得到一个加密值让我们说n,再假设我加盐“abc123”这次我会得到一个不同的值它不会是。

标签: c# mysql .net cryptography


【解决方案1】:

在您的用户表UsernameHashSalt 中创建一个列

用户注册

1) 在您的注册表单中获取用户输入的usernamepassword

2) 使用以下方法为输入密码创建哈希和盐。

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) 在您的登录表单中从用户那里输入usernamepassword

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

3) 将存储的HashSalt 传递给下面的函数。

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
}

参考:Effective Password Hashing

【讨论】:

  • @prkash,查看答案可能对您有帮助:)
  • 工作正常...但是你什么时候给size赋值?就我而言,我设置了这样的默认值(int size =16)
【解决方案2】:

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

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

所以你的用户表应该至少有这 3 列: 用户名、哈希密码、盐

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

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

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

当您尝试登录时:

  • 您将获得登录名和密码作为输入
  • 通过登录,您可以检索 base 中的哈希和盐
  • 你已经有了哈希函数
  • 这样您就可以处理散列函数(password_entered_by_user+salt)
  • 您将结果与 hashedpassword 进行比较,如果相同则记录用户

在散列密码旁边放盐不会降低安全性:盐的目的是防止使用rainbowtable

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

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

guessed_password => hash(guessed_password)

例如:

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

如果 hash(guessed_pa​​ssword) 在你的数据库中,他知道这个(这些)用户的密码是 guessed_pa​​ssword。

他可以生成一个包含数十亿个guessed_pa​​sswords 的字典,并且由于许多用户不使用真正的强密码,他很可能能够在他的字典中找到大量您的用户哈希。因此,如果他为“lola80”和“lola79”生成哈希值,并且这些是您的 2 个用户的密码,他就知道了。

现在,如果您在输入的每个密码中添加一个随机盐,他必须为每个盐生成一个完整的字典,因为他必须这样做:

guessed_password + salt = hash(guessed_password + salt)

对于盐为 '09ç@p$' 的用户 A,他必须生成一个完整的字典,其中每个单词都以 '09ç@p$' 结尾

现在,如果他想猜测用户 B 与 salt 'Yuè45gh' 关联的密码,他必须生成另一个字典,其中每个单词都以 'Yuè45gh' 结尾

基本上,它通过 your_number_of_users 因素减慢猜测用户密码的过程。

【讨论】:

  • 如果我有 hashedpassword 列,它将不安全,因为很容易找出相似的密码。
  • @prkash,前提是你们都发生在同一个随机盐上。有了足够大的盐,这将是天文数字上罕见的。如果您真的担心,请包含用户的主键添加部分盐,以便每个用户有所不同。
  • @prkash 盐对有针对性的暴力攻击没有帮助,强密码可以,盐有助于抵抗彩虹表攻击并大大减慢针对整个数据库的任何攻击。我会用解释编辑我的答案。
【解决方案3】:

我想我可以使用与加密相同的方法来取消它,但加盐不会返回相同的值。

您的意思是您通过加盐机制再次发送了加盐密码并期望得到未散列的明文密码?这没有任何意义。

因此,您将密码加盐并将其保存在某个地方。您现在要做的是,当用户输入他的密码时,您对这个新密码进行加盐并获得加盐密码。然后检查这个新的和旧的;如果它们匹配,那就是正确的密码。

【讨论】:

  • 是的,但是每次加盐不会返回相同的值,即使我再次输入相同的密码也不会相同,只有每次使用相同的密码时,散列都会返回相同的值。
  • 那么你应该阅读@Maxime 的回答。我认为他更好地理解了你的问题。正如他解释的那样,您存储用于每个密码的盐。
  • @prkash,如果盐在散列密码旁边可见,这不是问题。使用预哈希密码的攻击者将无法使用它们。必须猜测您的密码并使用您的盐对数据库中的 每个 条目进行哈希处理,这将花费太长时间。如果您为每个密码设置不同的盐,那么预先收集的彩虹表、查找表等将毫无用处。
猜你喜欢
  • 2011-06-02
  • 2014-12-03
  • 2015-02-17
  • 1970-01-01
  • 2012-03-24
  • 2015-08-20
  • 1970-01-01
  • 1970-01-01
  • 2014-06-10
相关资源
最近更新 更多