php 如何让C#匹配MySQL数据库中的哈希密码

iyfjxgzm  于 2023-05-05  发布在  PHP
关注(0)|答案(1)|浏览(170)

我有一个运行IPS/Invision社区的现有网站。我正在创建一个配套的C#应用程序,它将共享相同的数据库和身份验证。
在现有网站的数据库中查找,我可以看到它遵循了良好的实践,并将密码存储为散列,而不是纯文本。一个哈希值的例子看起来像这样:

$2y$10$Tu9ImODbXvVVtievNhkEIODyonQgclnDXQ.8N8mRPd2JMQYSDStqK

我需要我的C#代码能够复制这个散列过程,这样我的配套应用程序就可以对用户进行身份验证。
我在原来的网站上找到了这个PHP代码:

public function getUniqueMemberHash()
{
    /* If this is a guest, just return a random string. */
    if ( !$this->member_id )
    {
        return \IPS\Login::generateRandomString();
    }
    
    /* Return the password hash if we already have it */
    if ( !empty( $this->members_pass_hash ) )
    {
        return md5( ( $this->members_pass_hash ?: $this->email ) . $this->members_pass_salt );
    }

    /* Do we already have one? */
    if ( !empty( $this->unique_hash ) )
    {
        return $this->unique_hash;
    }

    /* Otherwise, generate one */
    $this->unique_hash = md5( \IPS\Login::generateRandomString(32) );

    $this->save();

    return $this->unique_hash;
}

 /* Compare hashes in fixed length, time constant manner.
 *
 * @param   string  $expected   The expected hash
 * @param   string  $provided   The provided input
 * @return  boolean
 */
public static function compareHashes( $expected, $provided )
{
    // *0 and *1 are failures from crypt() - if we have ended up with an invalid hash anywhere, we will reject it to prevent a possible vulnerability from deliberately generating invalid hashes
    if ( !\is_string( $expected ) || !\is_string( $provided ) || $expected === '*0' || $expected === '*1' || $provided === '*0' || $provided === '*1' ) 
    {
        return FALSE;
    }

    $len = \strlen( $expected );
    if ( $len !== \strlen( $provided ) )
    {
        return FALSE;
    }

    $status = 0;
    for ( $i = 0; $i < $len; $i++ )
    {
        $status |= \ord( $expected[ $i ] ) ^ \ord( $provided[ $i ] );
    }
    
    return $status === 0;
}

/**
 * Return a random string
 *
 * @param   int     $length     The length of the final string
 * @return  string
 */
public static function generateRandomString( $length=32 )
{
    $return = '';

    if ( \function_exists( 'random_bytes' ) )
    {
        $return = \substr( bin2hex( random_bytes( $length ) ), 0, $length );
    }
    elseif( \function_exists( 'openssl_random_pseudo_bytes' ) )
    {
        $return = \substr( bin2hex( openssl_random_pseudo_bytes( ceil( $length / 2 ) ) ), 0, $length );
    }

    /* Fallback JUST IN CASE */
    if( !$return OR \strlen( $return ) != $length )
    {
        $return = \substr( md5( uniqid( '', true ) ) . md5( uniqid( '', true ) ), 0, $length );
    }

    return $return;
}

public static function compareHashes( $expected, $provided )
{
    // *0 and *1 are failures from crypt() - if we have ended up with an invalid hash anywhere, we will reject it to prevent a possible vulnerability from deliberately generating invalid hashes
    if ( !\is_string( $expected ) || !\is_string( $provided ) || $expected === '*0' || $expected === '*1' || $provided === '*0' || $provided === '*1' ) 
    {
        return FALSE;
    }

    $len = \strlen( $expected );
    if ( $len !== \strlen( $provided ) )
    {
        return FALSE;
    }

    $status = 0;
    for ( $i = 0; $i < $len; $i++ )
    {
        $status |= \ord( $expected[ $i ] ) ^ \ord( $provided[ $i ] );
    }
    
    return $status === 0;
}

到目前为止,我的配套应用程序有这个C#代码来检索用户记录,但当然它还不能工作,因为我不知道如何处理HashPassword()方法:

string server = "s570.use8.mysecurecloudhost.com";
string database = "nextgene_NGH";
string uid = "nextgene_NGH";
string password = "EXAMPLE PASSWORD";
string ssl = "None";

string connectionString = $"SERVER={server};DATABASE={database};UID={uid};PASSWORD={password};SSL Mode={ssl};";

// NEVER NEVER NEVER store an "exact password" in a database!!
string SQL = "SELECT * FROM `core_members` where name= @name AND hashed_password= @hash;";
var dt = new DataTable();

try
{
    using (var connection = new MySqlConnection(connectionString))
    using (var command = new MySqlCommand(SQL, connection))
    using (var da = new MySqlDataAdapter(command))
    {
        //NEVER NEVER NEVER use string concatenation to put user input in a query
        command.Parameters.AddWithValue("@name",  maskedTextBox1.Text);
        command.Parameters.AddWithValue("@hash", HashPassword(maskedTextBox2.Text));
  
        da.Fill(dt); //Fill opens and closes the connection
    }

    if (dt.Rows.Count == 0)
    {
        MessageBox.Show("Login Failed.");
        //button2.Visible = true;
    }
    else
    {
        MessageBox.Show("Login Successful.");
    }
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

如何让这段C#代码正确地验证用户?

q5lcpyga

q5lcpyga1#

哈希密码开头的$2y$10$是一个几乎确定的指示符,表明哈希是用bcrypt algorithm的变体计算的,最有可能是password_hash in PHP。您可以使用BCrypt.Net-Next NuGet包在C#中生成和验证这些。
例如,我进入https://phppasswordhash.com/,输入Passw0rd,并得到哈希字符串$2y$10$Tu9ImODbXvVVtievNhkEIODyonQgclnDXQ.8N8mRPd2JMQYSDStqK。(注意,算法使用了随机的salt,所以每次都得到不同的字符串。)然后我在C#中运行BCrypt.Net.BCrypt.Verify("Passw0rd", "$2y$10$Tu9ImODbXvVVtievNhkEIODyonQgclnDXQ.8N8mRPd2JMQYSDStqK")来验证密码,它返回true
你不能将哈希作为参数传递给MySQL语句,因为你不知道salt是什么。相反,从数据库中检索哈希密码并验证它:

using var connection = new MySqlConnection(connectionString);
connection.Open();
using var command = new MySqlCommand("SELECT members_pass_hash FROM core_members WHERE name = @name;", connection);
command.Parameters.AddWithValue("@name", maskedTextBox1.Text);
var hashedPassword = (string) command.ExecuteScalar();

if (hashedPassword is not null && BCrypt.Net.BCrypt.Verify(maskedTextBox2.Text, hashedPassword))
{
    MessageBox.Show("Login Successful");
}

相关问题