求助:C#实现Dovecot兼容的SSHA512及Argon2id加盐哈希
实现与Dovecot兼容的SSHA512和Argon2id哈希(C# + NHibernate)
一、SSHA512 正确实现
Dovecot的SSHA512格式规则为:{SSHA512}<Base64编码的(哈希值+盐),其中哈希是SHA512(密码字节 + 盐字节),最终将哈希结果与盐拼接后做Base64编码。
代码实现
using System; using System.Security.Cryptography; using System.Text; public class Ssha512Hasher { private const int SaltLength = 16; // 对齐Dovecot默认盐长度 public string GenerateHash(string password) { byte[] passwordBytes = Encoding.UTF8.GetBytes(password); byte[] salt = GenerateSalt(SaltLength); using (var sha512 = SHA512.Create()) { // 拼接密码与盐字节 byte[] combinedBytes = new byte[passwordBytes.Length + salt.Length]; Buffer.BlockCopy(passwordBytes, 0, combinedBytes, 0, passwordBytes.Length); Buffer.BlockCopy(salt, 0, combinedBytes, passwordBytes.Length, salt.Length); byte[] hashBytes = sha512.ComputeHash(combinedBytes); // 拼接哈希与盐后Base64编码 byte[] hashPlusSalt = new byte[hashBytes.Length + salt.Length]; Buffer.BlockCopy(hashBytes, 0, hashPlusSalt, 0, hashBytes.Length); Buffer.BlockCopy(salt, 0, hashPlusSalt, hashBytes.Length, salt.Length); return $"{{SSHA512}}{Convert.ToBase64String(hashPlusSalt)}"; } } public bool VerifyHash(string password, string hashedPassword) { if (!hashedPassword.StartsWith("{SSHA512}")) throw new ArgumentException("无效的SSHA512哈希格式"); string base64Part = hashedPassword.Substring("{SSHA512}".Length); byte[] hashPlusSalt = Convert.FromBase64String(base64Part); // 拆分哈希(固定64字节)与盐 byte[] hashBytes = new byte[64]; byte[] salt = new byte[hashPlusSalt.Length - 64]; Buffer.BlockCopy(hashPlusSalt, 0, hashBytes, 0, 64); Buffer.BlockCopy(hashPlusSalt, 64, salt, 0, salt.Length); using (var sha512 = SHA512.Create()) { byte[] passwordBytes = Encoding.UTF8.GetBytes(password); byte[] combinedBytes = new byte[passwordBytes.Length + salt.Length]; Buffer.BlockCopy(passwordBytes, 0, combinedBytes, 0, passwordBytes.Length); Buffer.BlockCopy(salt, 0, combinedBytes, passwordBytes.Length, salt.Length); byte[] computedHash = sha512.ComputeHash(combinedBytes); // 固定时间比较防止时序攻击 return CryptographicOperations.FixedTimeEquals(computedHash, hashBytes); } } private byte[] GenerateSalt(int length) { byte[] salt = new byte[length]; RandomNumberGenerator.Fill(salt); return salt; } }
NHibernate 集成方式
在用户实体类中封装密码处理逻辑,避免直接操作哈希字段:
public class MailUser { public virtual int Id { get; set; } public virtual string Email { get; set; } public virtual string PasswordHash { get; protected set; } public virtual void SetPassword(string plainPassword, Ssha512Hasher hasher) { PasswordHash = hasher.GenerateHash(plainPassword); } public virtual bool VerifyPassword(string plainPassword, Ssha512Hasher hasher) { return hasher.VerifyHash(plainPassword, PasswordHash); } }
映射文件直接映射PasswordHash字段即可,无需额外配置。
二、Argon2id 与 Dovecot 兼容实现
Dovecot的Argon2id格式规则为:{argon2id}$v=19$m=65536,t=3,p=1$<盐的Base64>$<哈希的Base64>,需严格对齐参数(内存、迭代次数、并行度),且Base64编码需去掉填充字符=。
代码实现(使用Konscious.Security.Cryptography.Argon2)
using System; using System.Security.Cryptography; using System.Text; using Konscious.Security.Cryptography; public class Argon2idHasher { // 对齐Dovecot默认参数 private const int MemorySize = 65536; private const int Iterations = 3; private const int DegreeOfParallelism = 1; private const int SaltLength = 16; public string GenerateHash(string password) { byte[] passwordBytes = Encoding.UTF8.GetBytes(password); byte[] salt = GenerateSalt(SaltLength); var argon2 = new Argon2id(passwordBytes) { Salt = salt, MemorySize = MemorySize, Iterations = Iterations, DegreeOfParallelism = DegreeOfParallelism }; byte[] hashBytes = argon2.GetBytes(32); // Dovecot默认哈希长度32字节 // 生成无填充的Base64编码 string saltBase64 = Convert.ToBase64String(salt).TrimEnd('='); string hashBase64 = Convert.ToBase64String(hashBytes).TrimEnd('='); return $"{{argon2id}}$v=19$m={MemorySize},t={Iterations},p={DegreeOfParallelism}${saltBase64}${hashBase64}"; } public bool VerifyHash(string password, string hashedPassword) { if (!hashedPassword.StartsWith("{argon2id}$")) throw new ArgumentException("无效的Argon2id哈希格式"); string[] parts = hashedPassword.Split('$'); if (parts[1] != "v=19") throw new NotSupportedException("仅支持Argon2id v19版本"); // 解析参数 string[] paramsParts = parts[2].Split(','); int memorySize = int.Parse(paramsParts[0].Split('=')[1]); int iterations = int.Parse(paramsParts[1].Split('=')[1]); int parallelism = int.Parse(paramsParts[2].Split('=')[1]); // 补回Base64填充字符 string saltBase64 = RestoreBase64Padding(parts[3]); string hashBase64 = RestoreBase64Padding(parts[4]); byte[] salt = Convert.FromBase64String(saltBase64); byte[] expectedHash = Convert.FromBase64String(hashBase64); // 计算验证哈希 byte[] passwordBytes = Encoding.UTF8.GetBytes(password); var argon2 = new Argon2id(passwordBytes) { Salt = salt, MemorySize = memorySize, Iterations = iterations, DegreeOfParallelism = parallelism }; byte[] computedHash = argon2.GetBytes(expectedHash.Length); return CryptographicOperations.FixedTimeEquals(computedHash, expectedHash); } private byte[] GenerateSalt(int length) { byte[] salt = new byte[length]; RandomNumberGenerator.Fill(salt); return salt; } private string RestoreBase64Padding(string unpaddedBase64) { int paddingNeeded = 4 - (unpaddedBase64.Length % 4); return paddingNeeded == 4 ? unpaddedBase64 : unpaddedBase64 + new string('=', paddingNeeded); } }
NHibernate 集成方式
同样在实体类中封装处理逻辑:
public class MailUser { // ... 已有属性 public virtual void SetPasswordArgon2id(string plainPassword, Argon2idHasher hasher) { PasswordHash = hasher.GenerateHash(plainPassword); } public virtual bool VerifyPasswordArgon2id(string plainPassword, Argon2idHasher hasher) { return hasher.VerifyHash(plainPassword, PasswordHash); } }
关键注意事项
- SSHA512必须保证哈希在前、盐在后的拼接顺序,盐长度保持16字节与Dovecot默认一致。
- Argon2id需严格对齐Dovecot的参数配置,Base64编码需处理填充字符的增删。
- 始终使用
RandomNumberGenerator生成盐,验证哈希时用CryptographicOperations.FixedTimeEquals防止时序攻击。 - NHibernate层避免直接操作哈希逻辑,将处理放在实体方法中,便于维护与测试。
内容的提问来源于stack exchange,提问作者Ocatvius2112
相关产品推荐
相关产品推荐

