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

时间:2016-01-21 16:14:32

标签: c# asp.net .net sha

我们有一个ASP.NET Web应用程序,它是用.NET Framework 4.5版本构建的。目前正在生产中,该应用程序使用SHA1加密算法。该算法在应用程序的web.config文件的“MachineKey”标记中设置。此应用程序使用ASP.Net Membership概念来维护登录凭据。

由于SHA1算法即将退化,因此我们希望将应用程序从SHA1更新为SHA2。为此,我们在应用程序的web.config文件的“MachineKey”标记中设置了“HMACSHA256”。

使用上述设置将我们的应用程序升级到SHA2后,我们希望旧用户的密码(使用SHA1加密并已存在于成员资格数据库中)不能与SHA2算法一起使用。但它允许老用户登录而不对以前加密的密码进行任何修改。

问题1:应用程序的web.config文件的“MachineKey”标记中的更改是否足够/建议用于此迁移?

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

请建议是否有在会员数据库级别启用SHA2加密的最佳方法。

3 个答案:

答案 0 :(得分:2)

我不知道是否可以使用成员资格处理此类迁移,而不强制用户通过密码重置过程。

但是您可以通过同时将成员资格迁移到Asp.Net Identity来实现这一点:Asp.Net Identity具有扩展点,允许您处理支持旧签名的“后备”密码签名匹配。此时,您仍然在内存中保持未登录的登录名和密码,然后您可以将签名转换为新格式。

所有这些都通过以下blog中的详细信息和代码进行了解释,包括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)

使用我的other answer技术细节,我只知道为什么你可以更改散列算法而不会导致旧密码丢失&#34;

成员资格会存储它与每个密码一起使用的散列算法,从而允许它在散列算法更改后仍然验证旧密码。

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

您还应该检查是否只使用旧帐户登录就足以让它通过成员资格重新散列到SHA-2。如果是这种情况,所有常规用户将在完成更改后快速重新进行哈希处理。

我没有删除我以前的答案,因为如果考虑迁移身份验证框架,它可能仍然有用。

答案 2 :(得分:0)

SHA1 升级到 SHA2 的重点是实际减少已知多年前被黑客入侵的SHA1安全问题。所以尝试拥有一个混合系统大多没有意义。

短期:99%的用户使用SHA1 ..只有新用户1%的用户使用SHA2? :(

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