【问题标题】:How to reduce password reset token length in Asp.Net Identity?如何减少 Asp.Net Identity 中的密码重置令牌长度?
【发布时间】:2015-06-29 04:56:52
【问题描述】:

我正在使用 Asp.Net Identity 生成密码重置令牌。

string Token = userManager.GeneratePasswordResetToken(userId);

上面的代码给了我一个大长度的令牌。 是否可以生成长度较短的密码重置令牌?

【问题讨论】:

  • 密码重置令牌应该很难猜到,因此,越长越好。我的问题是为什么你想要更短的令牌长度?
  • @phuzi 在这里我将使用较短的令牌生成链接文本,然后将其发送到客户的电子邮件地址。这样我们的客户将复制该链接文本并将其粘贴到其他界面而不是单击。
  • 在这里查看我的答案stackoverflow.com/a/40961708/4251431
  • 让我大吃一惊的是,微软认为向我们提供 244 个字符的重置令牌是可以的。这太荒谬了。我猜这是某种基于加密声明的令牌,但这种长度只会导致客户无法输入完整链接的剪切和粘贴问题。
  • 如果您将 url 放入 href <a> 标记中,则可以正常工作。

标签: c# asp.net asp.net-mvc-4 asp.net-identity


【解决方案1】:

您可以使用TotpSecurityStampBasedTokenProvider生成6位数字:

public class ResetPasswordTokenProvider : TotpSecurityStampBasedTokenProvider<OriIdentityUser>
{
    public const string ProviderKey = "ResetPassword";

    public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<OriIdentityUser> manager, OriIdentityUser user)
    {
        return Task.FromResult(false);
    }
}

并在启动类中添加:

services.AddIdentity<IdentityUser, IdentityRole>(options =>
    {
        options.Tokens.PasswordResetTokenProvider = ResetPasswordTokenProvider.ProviderKey;
    })
    .AddDefaultTokenProviders()
    .AddTokenProvider<ResetPasswordTokenProvider>(ResetPasswordTokenProvider.ProviderKey);

【讨论】:

    【解决方案2】:

    您的密码重置令牌需要是加密随机的 - 这意味着给定一组使用过的令牌,下一个令牌应该是不可能猜到的。它还需要覆盖足够大的可能值集,以使暴力尝试所有这些值非常慢。

    您可以进行更改以使后者使用较小的可能值集 - 例如,您可以在检查令牌之前向密码重置页面/操作添加 1 秒的延迟。您的用户几乎不会注意到很少使用的页面上的延迟,但攻击者将无法快速尝试大量令牌。

    所以,首先你需要得到一个加密随机数:

    var random = new byte[8];
    using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
        rng.GetBytes(random);
    

    我已将8 字节放在这里,但您可以将其设置为任何您想要的长度。

    接下来你需要把它变成一个漂亮的可复制粘贴的字符串。您可以通过 unicode 转换来做到这一点,但我发现 base 64 更可靠:

    Convert.ToBase64String(random).TrimEnd('=');
    

    将其与8 字节一起使用将为您提供 64 位可能的值和一个 10 字符的字符串。使用 4 将为您提供 32 位(在低安全性站点上进行缓慢的令牌检查可能就足够了)和 5 个字符的字符串。

    【讨论】:

    • 你读过OP吗? “生成长度短的密码重置令牌?”。有一个方法UserManager.GeneratePasswordResetToken——如何让它吐出更小的token
    • @Toolkit 我阅读了 OP。你不能让GeneratePasswordResetToken“吐出更小的令牌”,它会生成一个固定长度的令牌。您可以使用自己的实现生成更短的令牌,就像我在这里所做的那样,但是令牌越短,暴力破解就越容易,冲突的可能性就越大 - 您可以使用自己的实现来选择平衡。 GeneratePasswordResetToken 不允许您选择,它只是假设您需要具有长令牌的高安全性。你没有使用它。
    • 你在这里没有做任何实现,你只是展示了如何做一个随机字符串。您可以为此使用谷歌
    • 而且你还建议为了防止暴力破解你需要一个 244 字符长的令牌?伙计,你从哪里得到这些想法的?
    • @Toolkit 是的,随机字符串适用于令牌生成,只要它无法预测并且您将它存储在某个地方(理想情况下有超时)。我没有对微软的 244 char 实现提出任何建议,我认为不需要防止暴力破解。在一个非常粗略的猜测中,它们可能是扩展一些散列值的关键,这就是它这么长的原因,但这是一个猜测。 OP 要求使用更短的字符串,他们可以根据需要将其缩短,但更短 = 更强力,更长 = 更安全。有一个取决于他们的上下文的平衡。
    【解决方案3】:

    我有一个理想的解决方案,建立在此处找到的体面答案的基础上。首先,您必须在相关类中注入IMemoryCache

    public class ResetController : ControllerBase
    {
        private readonly IMemoryCache _memoryCache;
        public ResetController(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }
    }
    

    其次,内存缓存允许您创建一个键值对关系存储在缓存中,以便您以后可以使用键检索值。在这种情况下,value是实际生成的token,key是变量truncatedtoken

    var token = await _userManager.GeneratePasswordResetTokenAsync(user);
    string tokentruncated;
    
    using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
    {
        byte[] tokenData = new byte[32];
        rng.GetBytes(tokenData);
        tokentruncated = Convert.ToBase64String(tokenData);
    }
    
    var cacheEntryOptions = new MemoryCacheEntryOptions()
                        .SetSlidingExpiration(TimeSpan.FromDays(1));
    _memoryCache.Set(tokentrunc, token, cacheEntryOptions);
    

    一旦发送确认令牌 (truncatedtoken) 并且用户准备好重置密码。 truncatedtoken 键用于检索实际令牌。

    string token;
    _memoryCache.TryGetValue(truncatedtoken, out token);
     var result = await _userManager.ResetPasswordAsync(user, token, "New password");
    

    除了使用TryGetValue,您还可以使用以下方式检索令牌;

    string token = _memoryCache.Get<string>(truncatedtoken);
    

    【讨论】:

      【解决方案4】:

      用较短的代码替换长代码并存储在应用缓存中。

      生成时:

      var token = UserManager.GeneratePasswordResetToken(user.Id);
      var guidCode = GenerateCustomToken(); //use this https://stackoverflow.com/a/1668371/631527
      CacheHelper.AddToCache(guid, token); //add it to MemoryCache.Default
      var resetUrl = $"https://.....com/password-reset/{guidCode}/{userName}";
      

      检查时:

      //get guidCode from the request in your GET or POST controller
      var token = CacheHelper.GetValue(guidCode); //retrieve it from cache
      var result = UserManager.ResetPassword(user.Id, token, model.Password);
      

      【讨论】:

      • 在大多数情况下是次要的,但避免使用Guid.NewGuid() 生成安全令牌,GUID 看起来非常随机,但可以在很多硬件上预测。而是使用System.Security.Cryptography.RandomNumberGenerator.Create() 来获得更难预测的算法。
      【解决方案5】:

      一件事是生成您自己的 8 位 GUID 并将其发送给用户,并使用您自己的查找表来获取真正的令牌。

      【讨论】:

      • 在我编辑之前的第一段完全没有必要。请不要在此处发布此类内容。
      猜你喜欢
      • 1970-01-01
      • 2016-05-03
      • 2015-04-28
      • 2020-12-15
      • 2021-01-23
      • 1970-01-01
      • 2013-10-31
      • 2015-05-29
      相关资源
      最近更新 更多