You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 22:10:24