我有一个运行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#代码正确地验证用户?
1条答案
按热度按时间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是什么。相反,从数据库中检索哈希密码并验证它: