【问题标题】:How to authenticate user with hashed password?如何使用哈希密码对用户进行身份验证?
【发布时间】:2020-08-17 17:09:48
【问题描述】:

我正在尝试使用 实现安全密码存储,但docsTestWorkshashesDontMatchwrongPassword 似乎总是正确。我不认为他们应该是。我做错了什么(或很多事)吗?

public async Task<IUser> Authenticate(ICredentials credentials)
{
    using var connection = _databaseGateway.Connection;
    connection.Open();

    const string PASSWORD = "Correct Horse Battery Staple";
    var hash = PasswordHash
        .ScryptHashString(PASSWORD);
    var docsTestWorks = PasswordHash.ScryptHashStringVerify(hash, PASSWORD);

    var incomingHash = PasswordHash
        .ScryptHashString(credentials.Password);
    var test1 = PasswordHash
        .ScryptHashString("myPassword");
    var test2 = PasswordHash
        .ScryptHashString("myPassword");
    var hashesDontMatch = test1 != test2;
    var users = await connection.GetAllAsync<User>();
    var existingUser = users
        .Single(u => u.Username == credentials.Username);
    var wrongPassword = !PasswordHash
        .ScryptHashStringVerify(existingUser.PasswordHash, credentials.Password);

    if (wrongPassword)
        throw new AuthenticationException(Error.WrongPassword,
            "Error: Incorrect Password.");

    return AddToken(existingUser);
}

我觉得这太简单了,我可能遗漏了很多东西......

我正在使用的包:

dotnet add package Sodium.Core

【问题讨论】:

  • libsodium 是否在散列密码中添加了盐?如果是这样,它们实际上将永远不会相同。
  • @TonyTroeff 是的。我认为确实如此。但是我看不到如何从外部传递它进行比较。

标签: libsodium c# asp.net-core passwords password-hash libsodium


【解决方案1】:

我写了一些使用System.Security.Cryptography命名空间的扩展来简化这种工作。

https://github.com/jscarle/Crypto.NET/blob/master/Crypto.NET/System.Security.Cryptography.Extensions.cs

添加到您的项目后,使用using System.Security.Cryptography 引用命名空间,然后您可以散列并比较来自任何字符串的散列。

例如:

string password1 = "some password";
string hash1 = password1.Hash();

string password2 = "some other password";
string hash2 = password2.Hash();

string password3 = "some password";
string hash3 = password3.Hash();

Console.WriteLine($"{password1} = {hash1}");
Console.WriteLine($"{password2} = {hash2}");
Console.WriteLine($"{password3} = {hash3}");

if (password1.CompareToHash(hash2))
    Console.WriteLine("hash1 and hash2 match.");
else
    Console.WriteLine("hash1 and hash2 do not match.");

if (password1.CompareToHash(hash3))
    Console.WriteLine("hash1 and hash3 match.");
else
    Console.WriteLine("hash1 and hash3 do not match.");

【讨论】:

    【解决方案2】:

    我最终得到了以下结果:

    public async Task<IUser> Authenticate(ICredentials credentials)
    {
        using var connection = _databaseGateway.Connection;
        connection.Open();
        var users = await connection.GetAllAsync<User>();
        const int expectedUserCount = 1;
        var existingUsers = users
            .Where(u => u.Username == credentials.Username);
    
        if (existingUsers.Count() != expectedUserCount)
            throw new AuthenticationException(Error.UserDoesNotExist,
                "Error: There is no single user exists with the username given.");
        var existingUser = existingUsers.Single();
    
        var salt = existingUser.PasswordSalt;
        // TURN ON FOR REGISTERING USER PW
        // salt = PasswordHash.ScryptGenerateSalt();
        var password = Encoding.UTF8.GetBytes(credentials.Password);
        var hash = PasswordHash.ScryptHashBinary(password, salt);
        // TURN ON FOR REGISTERING USER PW
        // await connection.ExecuteAsync(
        //    "UPDATE user SET PasswordHash=@hash, PasswordSalt=@salt WHERE UserName=@username;",
        //    new {hash, salt, username = credentials.Username});
    
        var wrongPassword = !hash.SequenceEqual(existingUser.PasswordHash);
    
        if (wrongPassword)
            throw new AuthenticationException(Error.WrongPassword,
                "Error: Incorrect Password.");
    
        return AddToken(existingUser);
    }
    

    我认为here 的怀疑是正确的。似乎每次都使用 string 方法的重载生成随机盐。


    然后我最终添加了一个辅助类:

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sodium;
    
    namespace Services
    {
        public static class HashingService
        {
            public static bool IsGeneratedBy(
                this IEnumerable<byte> existingHash,
                string plaintextPassword,
                byte[] salt
            )
            {
                var password = Encoding.UTF8.GetBytes(plaintextPassword);
                var generatedHash = PasswordHash.ScryptHashBinary(password, salt);
                var passwordIsCorrect = generatedHash.SequenceEqual(existingHash);
                return passwordIsCorrect;
            }
        }
    }
    

    并像这样使用它:

    public async Task<IUser> Authenticate(ICredentials credentials)
    {
        using var connection = _databaseGateway.Connection;
        connection.Open();
        var users = await connection.GetAllAsync<User>();
        const int expectedUserCount = 1;
        var existingUser = users
            .Single(u => u.Username == credentials.Username);
    
        var wrongPassword = !existingUser.PasswordHash.IsGeneratedBy(
            credentials.Password, existingUser.PasswordSalt);
    
        if (wrongPassword)
            throw new AuthenticationException(Error.WrongPassword,
                "Error: Incorrect Password.");
    
        return AddToken(existingUser);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-06
      • 2014-02-15
      • 2015-12-17
      • 1970-01-01
      • 2020-07-06
      • 2011-03-04
      • 2019-05-25
      • 1970-01-01
      相关资源
      最近更新 更多