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

4
我们有一个ASP.NET Web应用程序,使用的是.NET Framework 4.5版本。目前在生产环境中,该应用程序使用SHA1加密算法。此算法设置在应用程序的web.config文件的“MachineKey”标记中。该应用程序使用ASP.Net成员身份概念来维护登录凭据。
由于SHA1算法即将被淘汰,因此我们希望将我们的应用程序从SHA1更新到SHA2。为此,我们在应用程序的web.config文件的“MachineKey”标记中设置了“HMACSHA256”。
在以上设置的基础上升级我们的应用程序到SHA2后,我们期望旧用户的密码(这些密码使用SHA1加密并已经存在于成员身份数据库中)将无法使用SHA2算法。但是它允许旧用户在以前加密的密码没有任何修改的情况下登录。
问题1:应用程序的web.config文件中“MachineKey”标记所做的更改是否足够/推荐进行此迁移?
问题2:由于我们仍然能够使用以前加密的密码登录应用程序,成员身份数据库是否真正使用了web.config文件中设置的SHA2加密?或者我们需要添加一些额外的设置来启用成员身份数据库级别的SHA2加密?请给出建议。
请建议如果有任何最佳方法可以在成员身份数据库级别启用SHA2加密。
3个回答

2
我不知道是否可能使用Membership来处理这样的迁移,而不强制用户通过密码重置流程。

但是,您可以在同时将Membership迁移到Asp.Net Identity时完成此操作:Asp.Net Identity具有扩展点,允许您处理支持旧签名的“回退”密码签名匹配。此时,您仍然在内存中拥有登录名和密码未散列的状态,因此您可以通过该方式将签名转换为新格式。

所有这些都在以下博客中详细说明了代码,包括我在下面代码中添加的一条注释中略微解释的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;
    }

2
通过我的 其他答案 技术细节,我才意识到为什么可以更改哈希算法而不会导致旧密码“丢失”。
Membership 会存储每个密码所使用的哈希算法,因此在哈希算法更改后仍然可以验证旧密码。
这解释了您所看到的行为。要双重检查它(并驳斥您的问题1和2的疑虑),请检查数据库中的数据,检查 PasswordFormat 列,它应该根据使用的哈希算法而变化。
您还应该检查是否仅使用旧帐户登录就足以让 Membership 将其重新哈希为 SHA-2。如果是这样,所有常规用户在进行更改后很快就会被重新哈希。 我没有删除我的先前的答案,因为如果考虑迁移身份验证框架,它仍可能有用。

0

SHA1升级到SHA2的整个目的是为了实际上减少您已知的SHA1安全问题,该问题在多年前就被黑客攻击了。因此,尝试拥有混合系统大多是毫无意义的。

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

您只需要强制执行密码更改,这将使所有人都使用SHA2


SHA1漏洞是关于可能生成“冲突”的问题:从不同的内容中,获得相同的签名。据我所知,在登录密码内部签名上利用SHA1碰撞仍未完成,并且很可能永远不会完成,因为生成冲突的技术需要长度较长的输入,这应该被登录/密码输入禁止使用。 - Frédéric
@Frédéric 我正在阅读有关“碰撞”的内容,但我不确定自己是否真正理解了这个问题。 - Tom Stickel
碰撞原理是改变某些内容(例如文件下载,添加恶意软件或证书等),但以一种方式使其SHA-1哈希值保持不变,从而诱骗依赖SHA-1签名的安全系统。这已经多次使用MD5哈希完成,而SHA-1具有相同的漏洞(越来越容易访问,几年前仍需要巨大的计算能力)。因此,SHA-1签名证书在2017年结束。关于私有持有的SHA-1签名,例如DB中的哈希密码,利用漏洞是另一回事。 - Frédéric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接