我的一位客户不想为2FA使用任何标准选项(SMS或电子邮件(,我想知道其他人已经实施了什么。
我觉得该网站仅使用用户名和密码组合,即使使用Max-Attempts和超时也会太脆弱。
一个简单的选项,将登录不确定性倍增是通过添加其他安全性问题作为登录页面的一部分。
我的答案在下面发布
在创建用户数据库中使用代码 - 优先方法,我在我的IdentityDbContext类中添加了一组安全问题。
public DbSet<SecurityQuestion> SecurityQuestions { get; set; }
这提供了一个简单的问题列表,例如"您最喜欢的食物"。这些问题应提供合理的通用答案。问题添加在配置类种子方法
中void AddSecurityQuestion(ApplicationDbContext db, string question)
{
db.SecurityQuestions.Add(new SecurityQuestion() { Question = question });
}
一个简单的表就足够
public class SecurityQuestion
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(128)]
[DisplayName("Question")]
public string Question { get; set; }
}
添加到身份用户类中的字段。这将包含null或安全问题和答案的哈希。为了完整,添加了属性以检查是否存在哈希。用户第一次登录时,就保存了哈希。在随后的登录中,检查哈希
public string SecurityQuestion { get; protected set; }
[NotMapped]
public bool HasSecurityQuestion
{
get
{
return this.SecurityQuestion != null;
}
}
哈希使用与内部身份方法相同的代码,并将种子和哈希存储在同一字符串中
public static string HashSecurityQuestion(string question, string answer)
{
if (question == null)
{
throw new ArgumentNullException("Question is null");
}
if (answer == null)
{
throw new ArgumentNullException("Answer is null");
}
string questionAndAnswer = question + "_" + answer;
// random salt and hash in save result
byte[] salt;
byte[] buffer2;
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(questionAndAnswer, 0x10, 0x3e8))
{
salt = bytes.Salt;
buffer2 = bytes.GetBytes(0x20);
}
byte[] dst = new byte[0x31];
Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
return Convert.ToBase64String(dst);
}
需要一种验证方法
public static bool VerifyHashedPassword(string hashedSecurityQuestion, string question, string answer)
{
if (hashedSecurityQuestion == null)
{
return false;
}
if (question == null)
{
throw new ArgumentNullException("Question is null");
}
if (answer == null)
{
throw new ArgumentNullException("Answer is null");
}
string questionAndAnswer = question + "_" + answer;
// has to retrieve salt
byte[] buffer4;
byte[] src = Convert.FromBase64String(hashedSecurityQuestion);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(questionAndAnswer, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return buffer3.SequenceEqual(buffer4);
}
在登录过程中,检查了一个额外的步骤来验证安全问题和答案。MVC视图显示了一个问题和答案的文本框,这两个值都在视图模型中
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
// if the user login is a success, check if a security question exists
if (result == SignInStatus.Success && user.HasSecurityQuestion)
{
// security question exists, so check it
if (!user.VerifySecrityQuestion(model.SecurityQuestion, model.SecurityQuestionAnswer))
{
result = SignInStatus.Failure;
}
}
authy/twilio开发人员在这里。您还有其他几个其他选择:
执行&amp;鼓励强密码
这包括最小长度,显示密码"强度"指示器,并包括人们使用密码管理器的简便方法。
我汇编了有关强密码建议的更多详细信息:https://github.com/robinske/betterpasswords
一次totp
形式的一次密码这就是您在诸如Authy或Google Authenticator之类的应用程序中看到的。TOTP(基于时间的一次时间密码(是标准,您可以在此处阅读。
Authy具有在此处实现OTP的API。
推动身份验证
这是2FA的另一种形式,允许您的用户以推送通知的形式"批准"或"拒绝"登录请求。这是具有无缝用户体验的2FA最安全的形式,您可以在此处阅读有关该如何做到的更多信息。
Authy具有在此处实现推动身份验证的API。
=========
注意:安全性问题很像可以更容易谷歌搜索的其他密码,因此我鼓励您的客户考虑使用真正的第二个因素。