从 SHA1 迁移到 SHA2 ASP.net 4.5,C#

本文关键字:net ASP SHA1 迁移 SHA2 | 更新日期: 2023-09-27 18:31:17

我们有一个 ASP.NET Web应用程序,它是在.NET Framework 4.5版本中构建的。目前正在生产中,此应用程序正在使用 SHA1 加密算法。此算法在应用程序的 web.config 文件的"机器密钥"标签中设置。此应用程序使用成员资格概念 ASP.Net 维护登录凭据。

由于 SHA1

算法处于降级的边缘,因此我们希望将应用程序从 SHA1 更新到 SHA2。为此,我们在应用程序的web.config文件的"MachineKey"标签中设置了"HMACSHA256"。

使用上述设置将应用程序升级到 SHA2 后,我们预计旧用户的密码(使用 SHA1 加密并已存在于成员数据库中)将无法与 SHA2 算法一起使用。但它允许年长用户登录而无需修改以前加密的密码。

问题 1:在应用程序的 web.config 文件的"MachineKey"标记中所做的更改是否足以/推荐用于此迁移?

问题 2:由于我们仍然能够使用以前加密的密码登录应用程序,因此成员资格数据库是否真的使用 web.config 文件中设置的 SHA2 加密?或者我们需要添加一些其他设置以在成员资格数据库级别启用 SHA2 加密?请指教。

请建议是否有任何最佳方法可以在成员资格数据库级别启用 SHA2 加密。

从 SHA1 迁移到 SHA2 ASP.net 4.5,C#

我不知道

是否可以使用会员资格处理此类迁移,而无需强制用户进行密码重置过程。

但是,您可以通过同时将成员身份迁移到 Asp.Net 身份来做到这一点:Asp.Net 身份具有扩展点,允许您处理"回退"密码签名匹配以支持旧签名。此时,您仍然可以在内存中未散列登录名和密码,然后您可以顺便将签名转换为新格式。

所有这一切都在下面的博客中用细节和代码解释,包括SQL迁移,我只是在下面的代码中添加的注释中稍微解释一下。

以下是实现此目的的主要类:

public class BackCompatPasswordHasher : PasswordHasher
{
    public override string HashPassword(string password)
    {
        return base.HashPassword(password);
    }
    public override PasswordVerificationResult VerifyHashedPassword(
        string hashedPassword, string providedPassword)
    {
        // Relies on SQL migration having formatted old hashes as
        // (aspnet_Membership.Password + '|' + 
        // CAST(aspnet_Membership.PasswordFormat as varchar) + '|'
        // + aspnet_Membership.PasswordSalt)
        string[] passwordProperties = hashedPassword.Split('|');
        if (passwordProperties.Length != 3)
        {
            return base.VerifyHashedPassword(hashedPassword, 
                providedPassword);
        }
        else
        {
            string passwordHash = passwordProperties[0];
            int passwordformat = 1;
            string salt = passwordProperties[2];
            if (String.Equals(EncryptPassword(providedPassword,
                passwordformat, salt), 
                passwordHash, StringComparison.CurrentCultureIgnoreCase))
            {
                return PasswordVerificationResult.SuccessRehashNeeded;
            }
            else
            {
                return PasswordVerificationResult.Failed;
            }
        }
    }
    //This is copied from the existing SQL providers and is provided only 
    // for back-compat.
    private string EncryptPassword(string pass, int passwordFormat, 
         string salt)
    {
        if (passwordFormat == 0) // MembershipPasswordFormat.Clear
            return pass;
        byte[] bIn = Encoding.Unicode.GetBytes(pass);
        byte[] bSalt = Convert.FromBase64String(salt);
        byte[] bRet = null;
        if (passwordFormat == 1)
        { // MembershipPasswordFormat.Hashed 
            HashAlgorithm hm = HashAlgorithm.Create("SHA1");
            if (hm is KeyedHashAlgorithm)
            {
                KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
                if (kha.Key.Length == bSalt.Length)
                {
                    kha.Key = bSalt;
                }
                else if (kha.Key.Length < bSalt.Length)
                {
                    byte[] bKey = new byte[kha.Key.Length];
                    Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                    kha.Key = bKey;
                }
                else
                {
                    byte[] bKey = new byte[kha.Key.Length];
                    for (int iter = 0; iter < bKey.Length; )
                    {
                        int len = Math.Min(bSalt.Length, bKey.Length - iter);
                        Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
                        iter += len;
                    }
                    kha.Key = bKey;
                }
                bRet = kha.ComputeHash(bIn);
            }
            else
            {
                byte[] bAll = new byte[bSalt.Length + bIn.Length];
                Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                bRet = hm.ComputeHash(bAll);
            }
        }
        return Convert.ToBase64String(bRet);
    }
}

然后,在您的用户管理器中:

public class IdentityUserManager : UserManager<IdentityUser>
{
    public IdentityUserManager(IUserStore<IdentityUser> store)
        : base(store)
    {
        PasswordHasher = new BackCompatPasswordHasher();
    }
}

在我的实际代码库中,我有一些用于处理重新哈希的补充,但不幸的是,没有评论为什么。也许这是原始实现者的一些多余代码,或者确实需要它。我没有调查过它,所以这里是 IdentityUserManager 中的附加代码:

    private ConcurrentDictionary<string, string> UserRehashed = 
        new ConcurrentDictionary<string, string>();
    private bool CanRehash(IdentityUser user)
    {
        return UserRehashed.TryAdd(user.Id, user.Id);
    }
    protected async override Task<bool> VerifyPasswordAsync(
        IUserPasswordStore<IdentityUser, string> store, IdentityUser user,
        string password)
    {
        var hash = await store.GetPasswordHashAsync(user).ConfigureAwait(false);
        var verifPassRes = PasswordHasher.VerifyHashedPassword(hash, password);
        if (verifPassRes == PasswordVerificationResult.SuccessRehashNeeded &&
            // avoid rehash loop.
            CanRehash(user))
        {
            var chPassRes = await this.ChangePasswordAsync(user.Id,
                password, password).ConfigureAwait(false);
            if (!chPassRes.Succeeded)
            {
                // throw or log, whatever.
            }
        }
        return verifPassRes != PasswordVerificationResult.Failed;
    }

通过我的其他答案技术细节,我只是意识到为什么您可以更改哈希算法而不会导致旧密码"丢失"。

成员资格确实存储了它对每个密码使用的哈希算法,因此允许它在哈希算法更改后仍验证旧密码。

这解释了您所看到的行为。要仔细检查它(并确认您的问题 1 和 2 问题),请检查数据库中的数据,检查应根据使用的哈希算法而更改的PasswordFormat列。

您还应该检查是否只需使用旧帐户登录就足以通过会员资格将其重新散列到 SHA-2。如果是这种情况,所有普通用户将在完成更改后快速重新散列。

我不会删除我之前的答案,因为如果也考虑迁移身份验证框架,它可能仍然有帮助。

SHA1 升级到 SHA2 的全部意义在于实际上减少多年前被黑客入侵的 SHA1 的已知安全问题。 因此,尝试拥有一个混合系统大多毫无意义。

短期:99% 的用户使用 SHA1 .. 并且只有新用户 1% 的所有用户都在 SHA2 上?? :(

您只需要强制执行密码更改,这使得 100% 的人使用 SHA2