programing

암호 재설정을 구현하는 방법

stoneblock 2023. 6. 8. 19:14

암호 재설정을 구현하는 방법

NET에서 ASP를 할 수 .NET에서 애플리케이션을 작업하고 있으며, 구체적으로 어떻게 구현할 수 있는지 궁금합니다.Password Reset내가 내 것을 굴리고 싶다면 기능.

구체적으로 다음과 같은 질문이 있습니다.

  • 크래킹하기 어려운 고유 ID를 생성하는 좋은 방법은 무엇입니까?
  • 타이머가 달려 있어야 하나요?그렇다면, 얼마나 걸릴까요?
  • IP 주소를 기록해야 합니까?그게 무슨 상관이에요?
  • "비밀번호 재설정" 화면에서 어떤 정보를 요청해야 합니까? 이메일 주소만?아니면 이메일 주소와 그들이 '알고 있는' 정보를 추가할 수도 있습니까? (좋아하는 팀, 강아지 이름 등)

제가 알아야 할 다른 고려 사항이 있습니까?

NB: 다른 질문들은 기술적 구현을 완전히 무시했습니다.실제로 받아들여진 대답은 잔인한 세부사항을 얼버무립니다.저는 이 질문과 이후의 답변들이 잔인한 세부 사항들로 들어가길 바라며, 저는 이 질문을 훨씬 더 좁게 표현함으로써 답변들이 덜 '엉뚱한' 것이고, 더 '고어'이기를 바랍니다.

편집: SQL Server 또는 ASP.NET MVC 링크에서 이러한 테이블을 모델링하고 처리하는 방법에 대한 답변도 감사합니다.

EDIT 2012/05/22: 이 일반적인 답변에 대한 후속 조치로, 저는 더 이상 이 절차에서 GUID를 사용하지 않습니다.다른 유명한 답변과 마찬가지로, 저는 이제 URL로 보낼 키를 생성하기 위해 저만의 해시 알고리즘을 사용합니다.이것은 또한 더 짧다는 장점이 있습니다.시스템을 조사합니다.보안.그것들을 생성하기 위한 암호화, 저도 보통 SALT를 사용합니다.

첫째, 사용자의 암호를 즉시 재설정하지 마십시오.

첫째, 사용자가 비밀번호를 요청할 때 즉시 재설정하지 마십시오.이는 누군가가 원하는 대로 전자 메일 주소(즉, 회사의 전자 메일 주소)를 추측하고 암호를 재설정할 수 있기 때문에 보안 위반입니다.오늘날의 모범 사례에는 일반적으로 사용자의 전자 메일 주소로 전송되는 "확인" 링크가 포함되어 있으며, 이 링크를 재설정할지 여부를 확인합니다.이 링크는 고유한 키 링크를 보낼 위치입니다.다음과 같은 링크와 함께 내 것을 보냅니다.example.com/User/PasswordReset/xjdk2ms92

예, 링크에 시간 초과를 설정하고 키와 시간 초과를 백엔드에 저장합니다(사용하는 경우 소금도 포함).3일의 제한 시간이 일반적이며, 사용자가 재설정을 요청할 때 웹 레벨에서 3일의 시간을 알려주어야 합니다.

고유한 해시 키 사용

나의 이전 답변은 GUID를 사용하라고 했습니다.저는 이제 모든 사람들에게 무작위로 생성된 해시를 사용하도록 조언하기 위해 이것을 편집하고 있습니다. 예를 들어,RNGCryptoServiceProvider그리고 해시에서 "진짜 단어"를 제거해야 합니다.저는 오전 6시에 한 여성이 "무작위일 것으로 예상되는" 해시 키에서 특정 "c" 단어를 받았던 특별한 전화를 기억합니다.도!

전체 절차

  • 사용자가 "비밀번호 재설정"을 클릭합니다.
  • 사용자에게 전자 메일을 요청합니다.
  • 사용자가 전자 메일을 입력하고 보내기를 클릭합니다.전자 메일을 확인하거나 거부하지 마십시오. 전자 메일도 좋지 않습니다.단순히 "전자 메일이 확인되면 암호 재설정 요청을 보냈습니다." 또는 이와 유사한 암호화된 메시지를 보냅니다.
  • 생다니합성에서 .RNGCryptoServiceProvider별도의 개체로 저장합니다.ut_UserPasswordRequests사용자에게 다시 연결할 수 있습니다.따라서 오래된 요청을 추적하고 오래된 링크가 만료되었음을 사용자에게 알릴 수 있습니다.
  • 전자 메일에 대한 링크를 보냅니다.

사용자가 링크를 가져옵니다.http://example.com/User/PasswordReset/xjdk2ms92클릭합니다.

링크가 확인되면 새 암호를 입력하라는 메시지가 표시됩니다.간단하며 사용자는 자신의 암호를 설정할 수 있습니다.또는 여기에 암호화된 암호를 설정하고 여기에 새 암호를 알리고 전자 메일로 보내십시오.

여기 좋은 답변들이 많이 있습니다. 반복하지 않겠습니다.

여기서 거의 모든 답변에 의해 반복되는 한 가지 문제를 제외하면, 그것은 잘못된 것임에도 불구하고:

가이드는 (현실적으로) 고유하며 통계적으로 추측할 수 없습니다.

이것은 사실이 아닙니다. GUID는 매우 약한 식별자이므로 사용자의 계정에 대한 액세스를 허용하는 데 사용해서는 안 됩니다.
128의 를 조을를트사면하최대▁which비▁is다▁cons니idered▁a.요즘에는 많이 고려되지 않는 것입니다.
그 중 첫 번째 절반은 (생성 시스템의 경우) 일반적인 불변성이고, 남은 것의 절반은 시간 의존적입니다(또는 다른 유사한 것).
대체로, 그것은 매우 약하고 쉽게 폭력적인 메커니즘입니다.

그러니까 그거 쓰지 마!

생성기, 암호적로으, 난강다한니사용합수를생성기력신학▁()를 사용하면 됩니다.System.Security.Cryptography.RNGCryptoServiceProvider의 원시 엔트로피를 및 최소 256비트의 원시 엔트로피를 가져옵니다.

나머지 모든 것들은, 다른 수많은 대답들이 제공한 것처럼.

먼저, 우리는 당신이 사용자에 대해 이미 알고 있는 것을 알아야 합니다.분명히, 사용자 이름과 이전 암호를 가지고 있습니다.그 밖에 또 뭘 알아요?이메일 주소 있으세요?사용자가 좋아하는 꽃에 대한 데이터가 있습니까?

사용자 이름, 암호 및 작업 전자 메일 주소가 있다고 가정하면 데이터베이스 테이블이라고 가정하여 new_passwd_expire라는 날짜와 new_passwd_id 문자열 두 개의 필드를 사용자 테이블에 추가해야 합니다.

사용자의 전자 메일 주소가 있다고 가정하면 다른 사용자가 암호 재설정을 요청하면 다음과 같이 사용자 테이블을 업데이트합니다.

new_passwd_expire = now() + some number of days
new_passwd_id = some random string of characters (see below)

그런 다음 해당 주소로 사용자에게 전자 메일을 보냅니다.

아무개에게

누군가가 <사용자의 웹 사이트 이름>에 있는 사용자 계정 <사용자 이름>에 대한 새 암호를 요청했습니다.이 암호 재설정을 요청한 경우 다음 링크를 따르십시오.

http://example.com/yourscript.lang?update=&lt;new\_password\_id>

해당 링크가 작동하지 않으면 다음으로 이동할 수 있습니다.http://example.com/yourscript.lang <라는 입니다. <new_password_id>

암호 재설정을 요청하지 않은 경우 이 전자 메일을 무시할 수 있습니다.

감사합니다, 야다야다

이제 스크립트를 코딩합니다.lang:이 스크립트에는 양식이 필요합니다.var 업데이트가 URL을 전달한 경우 양식은 사용자의 사용자 이름과 전자 메일 주소만 요청합니다.업데이트가 통과되지 않으면 사용자 이름, 전자 메일 주소 및 전자 메일로 보낸 ID 코드를 묻는 메시지가 표시됩니다.또한 새 암호를 요청합니다(물론 두 번).

사용자의 새 암호를 확인하려면 사용자 이름, 전자 메일 주소 및 ID 코드가 모두 일치하는지, 요청이 만료되지 않았는지, 두 개의 새 암호가 일치하는지 확인합니다.성공하면 사용자의 비밀번호를 새 비밀번호로 변경하고 사용자 테이블에서 비밀번호 재설정 필드를 지웁니다.또한 사용자를 로그아웃/로그인 관련 쿠키를 지우고 사용자를 로그인 페이지로 리디렉션해야 합니다.

기본적으로 new_passwd_id 필드는 암호 재설정 페이지에서만 작동하는 암호입니다.

한 가지 잠재적인 개선 사항: 이메일에서 <사용자 이름>을 제거할 수 있습니다."누군가가 이 전자 메일 주소의 계정에 대한 암호 재설정을 요청했습니다..." 따라서 사용자 이름은 전자 메일을 가로채야 사용자만 알 수 있습니다.누군가 계정을 공격하고 있다면 이미 사용자 이름을 알고 있기 때문에 저는 그렇게 시작하지 않았습니다.이렇게 추가된 모호성은 악의적인 사람이 전자 메일을 가로채는 경우에 대비하여 중간자 공격을 방지합니다.

질문에 대해서는:

임의 문자열 생성:극단적으로 무작위일 필요는 없습니다.GUID 생성기 또는 md5(concat(salt, current_timestamp())로도 충분합니다. 여기서 salt는 타임스탬프 계정이 생성된 것과 같은 사용자 레코드의 무언가입니다.사용자가 볼 수 없는 것이어야 합니다.

타이머: 예, 데이터베이스를 정상적으로 유지하기 위해 이것이 필요합니다.일주일 이상은 정말로 필요하지 않지만 이메일 지연이 얼마나 지속될지 모르기 때문에 적어도 2일은 필요합니다.

IP 주소:전자 메일이 며칠 지연될 수 있으므로 IP 주소는 유효성 검사가 아닌 로깅에만 유용합니다.기록하려면 기록하십시오. 그렇지 않으면 기록할 필요가 없습니다.

화면 재설정:위를 참조하십시오.

기록의 전자 메일 주소로 전송되는 GUID는 대부분의 일반 응용 프로그램에 충분하며, 시간 제한이 훨씬 더 좋습니다.

결국 사용자 전자 메일 상자가 손상된 경우(즉, 해커가 전자 메일 주소에 대한 로그온/암호를 가지고 있음), 이에 대해 할 수 있는 일은 많지 않습니다.

링크를 사용하여 사용자에게 이메일을 보낼 수 있습니다.이 링크에는 추측하기 어려운 문자열(예: GUID)이 포함됩니다.서버 측에서도 사용자에게 보낸 것과 동일한 문자열을 저장합니다.이제 사용자가 링크를 누르면 동일한 비밀 문자열로 DB 항목을 찾고 암호를 재설정할 수 있습니다.

고유 ID를 생성하려면 보안 해시 알고리즘을 사용할 수 있습니다.타이머 부착?재설정 pwd 링크에 대한 만료를 말씀하신 건가요?예, 만료 세트를 가질 수 있습니다. 3) 유효성 검사할 emailId 이외의 추가 정보를 요청할 수 있습니다.생년월일 또는 몇 가지 보안 질문과 같은 4) 임의의 문자를 생성하고 요청과 함께 입력하도록 요청할 수도 있습니다.비밀번호 요청이 일부 스파이웨어 또는 유사한 것에 의해 자동화되지 않도록 하기 위해.

저는 ASP.NET Identity에 대한 Microsoft 가이드가 좋은 시작이라고 생각합니다.

https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

ASP.NET ID에 사용하는 코드:

Web.Config:

<add key="AllowedHosts" value="example.com,2.example" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning.
    //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

언급URL : https://stackoverflow.com/questions/664673/how-to-implement-password-resets