如何让C#匹配MySQL数据库中IPS/Invision Community的哈希密码
问题
现有运行IPS/Invision Community的网站,正在开发配套C#应用以共享同一数据库与认证系统。数据库中密码以哈希形式存储,示例哈希值如下:
$2y$10$Tu9ImODbXvVVtievNhkEIODyonQgclnDXQ.8N8mRPd2JMQYSDStqK
需要复现该哈希流程实现用户认证,已获取原网站的PHP代码(包含哈希生成、对比逻辑),目前配套C#代码已能检索用户记录,但未实现HashPassword方法导致认证失败,现有C#代码如下:
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); }
解决方案
1. 理解IPS密码哈希机制
示例哈希以$2y$开头,这是PHP中bcrypt哈希的标识(与标准bcrypt的$2a$兼容)。IPS使用PHP的crypt()函数生成该哈希,哈希值中已包含盐和算法参数(比如$10$代表工作因子为10),无需单独存储盐。
2. 安装BCrypt库
C#无内置bcrypt实现,需通过NuGet安装第三方库,推荐BCrypt.Net-Next(支持$2y$哈希,安全性更高):
- 在Visual Studio中右键项目 → 管理NuGet程序包 → 搜索
BCrypt.Net-Next并安装。
3. 修改C#认证逻辑
原代码错误在于试图生成哈希后直接在SQL中匹配,正确逻辑应为:
- 先根据用户名查询用户的
hashed_password字段; - 用BCrypt验证输入密码与存储的哈希是否匹配;
- 兼容IPS定义的无效哈希规则(
*0/*1)。
修改后的完整代码:
using BCrypt.Net; using System.Data; using MySql.Data.MySqlClient; // ... 其他代码 ... 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};"; // 先查询用户的哈希,而非直接对比 string SQL = "SELECT hashed_password FROM `core_members` WHERE name = @name;"; string storedHash = null; try { using (var connection = new MySqlConnection(connectionString)) { connection.Open(); using (var command = new MySqlCommand(SQL, connection)) { command.Parameters.AddWithValue("@name", maskedTextBox1.Text); // 执行查询获取存储的哈希 object result = command.ExecuteScalar(); storedHash = result?.ToString(); } } if (string.IsNullOrEmpty(storedHash)) { MessageBox.Show("Login Failed: User not found."); return; } // 验证密码:BCrypt.Verify自动处理盐和工作因子,且为时间恒定对比 bool isPasswordValid = BCrypt.Net.BCrypt.Verify(maskedTextBox2.Text, storedHash, true); // 兼容IPS的无效哈希规则 if (storedHash == "*0" || storedHash == "*1" || !isPasswordValid) { MessageBox.Show("Login Failed."); } else { MessageBox.Show("Login Successful."); } } catch (Exception ex) { MessageBox.Show(ex.Message); }
4. 可选:自行实现PHP兼容的哈希对比
若不想依赖第三方库,可实现与PHPcompareHashes一致的时间恒定对比逻辑(仍需bcrypt哈希生成/验证,推荐用库):
private static bool CompareHashes(string expected, string provided) { // 排除IPS定义的无效哈希 if (string.IsNullOrEmpty(expected) || string.IsNullOrEmpty(provided) || expected == "*0" || expected == "*1" || provided == "*0" || provided == "*1") { return false; } if (expected.Length != provided.Length) { return false; } int status = 0; for (int i = 0; i < expected.Length; i++) { status |= expected[i] ^ provided[i]; } return status == 0; }
内容的提问来源于stack exchange,提问作者Ryan Ellis
相关产品推荐
相关产品推荐

