パソコン情報

【Udemy学習#7】ASP.NET MVCでパスワードのハッシュ化機能実装

CustomMembershipProvider.csの編集1

        public string GeneratePasswordHash(string username, string password)
        {
            string rawSalt = $"secret_{username}";
            var sha256 = new SHA256CryptoServiceProvider();
            var salt = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(rawSalt));

            var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
            var hash = pbkdf2.GetBytes(32);

            return Convert.ToBase64String(hash);
        }

 

上記のようなコードを追加しました。

この部分により、GeneratePasswordHashメソッドで、ユーザーとPBKDF2でハッシュ化されたパスワードを返すことを可能にしているようです。

PBKDF2は、HMACなどの疑似乱数関数や、ソルトを付加したパスワードやパスフレーズを用いる。また、鍵導出処理を何度も繰り返して、前回の処理で導出した鍵を次回の処理のパスワードとして用いることで、導出鍵を解読困難にする。この繰り返し処理は、ストレッチングと呼ばれる。(引用:Wiki様)

相変わらず何を言っているのかよくわかりませんが、攻撃されてハッシュ値からパスワードを解読されることを難解にしていると思っていれば間違いはない事でしょう。

 

string rawSalt = $"secret_{username}";

この部分は、短いパスワードを頭に文字を加えて長くしているそうです。

 

CustomMembershipProvider.csの編集2

        public override bool ValidateUser(string username, string password)
        {
          using (var db = new TodoesContext())
            {
                string hash = this.GeneratePasswordHash(username, password);

                var user = db.Users
                    .Where(u => u.UserName == username && u.Password == hash)
                    .FirstOrDefault();

                if (user != null)
                {
                    return true;
                }
            }

          if ("admin".Equals(username) && "password".Equals(password))
            {
                return true;
            }

            return false;
        }

 

string hash = this.GeneratePasswordHash(username, password);

hashを呼び出して、パスワードに使えるようにしています。

データベースの方はハッシュ化されてないパスワードなので、ログインできなくなっていまいますから、下記のコードも追加するようです。パスワードをハッシュ化したら、この部分は削除するようです。

 

if ("admin".Equals(username) && "password".Equals(password))
{
return true;
}

 

UsersController.csの編集

 

意味は理解してないが、インスタンスを保持するために下記のコードを入れるらしい。

readonly private CustomMembershipProvider membershipProvider = new CustomMembershipProvider();

 

次に下記のコードを編集しました。

        public ActionResult Create([Bind(Include = "Id,UserName,Password,RoleIds")] User user)
        {
            var roles = db.Roles.Where(role => user.RoleIds.Contains(role.Id)).ToList();
            
            if (ModelState.IsValid)
            {
                user.Roles = roles;

                user.Password = this.membershipProvider.GeneratePasswordHash(user.UserName, user.Password);

                db.Users.Add(user);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            this.SetRoles(roles);
            return View(user);
        }

 

ユーザーを新規作成する時に、ハッシュ化されたパスワードにするようです。

user.Password = this.membershipProvider.GeneratePasswordHash(user.UserName, user.Password);

 

Editの部分では、元あるパスワードがハッシュ化されてない場合は?という分岐にて処理をしているようです。

if (!dbUser.Password.Equals(user.Password))
{
dbUser.Password = this.membershipProvider.GeneratePasswordHash(user.UserName, user.Password);
}

 

上記のコードを入れたので、元あった 

dbUser.Password = user.Password;

は削除しました。

 

 

 

        public ActionResult Edit([Bind(Include = "Id,UserName,Password,RoleIds")] User user)
        {
            var roles = db.Roles.Where(role => user.RoleIds.Contains(role.Id)).ToList();

            if (ModelState.IsValid)
            {
                var dbUser = db.Users.Find(user.Id);
                if (dbUser == null)
                {
                    return HttpNotFound();
                }

                dbUser.UserName = user.UserName;

                if (!dbUser.Password.Equals(user.Password))
                {
                    dbUser.Password = this.membershipProvider.GeneratePasswordHash(user.UserName, user.Password);
                }

                dbUser.Roles.Clear();
                foreach (var role in roles)
                {
                    dbUser.Roles.Add(role);
                }

                db.Entry(dbUser).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            this.SetRoles(user.Roles);
            return View(user);
        }

Configuration.csの編集

ユーザー管理機能を実装したので前に使った個人のユーザー処理部分を削除しました。

User ma2n2n = new User()
{
Id = 2,
UserName = "ma2n2n",
Password = "password",
Roles = new List<Role>()
};

ma2n2n.Roles.Add(users);
users.Users.Add(ma2n2n);

 

そして下記の部分を追加しました。

var membershipProvider = new CustomMembershipProvider();
admin.Password = membershipProvider.GeneratePasswordHash(admin.UserName, admin.Password);

 

編集後の内容は下記のようになりました。

 

namespace TodoApp.Migrations
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    using TodoApp.Models;

    internal sealed class Configuration : DbMigrationsConfiguration<TodoApp.Models.TodoesContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
            ContextKey = "TodoApp.Models.TodoesContext";
        }

        protected override void Seed(TodoApp.Models.TodoesContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.
            User admin = new User()
            {
                Id = 1,
                UserName = "admin",
                Password = "Password",
                Roles = new List<Role>()
            };



            Role administrators = new Role()
            {
                Id = 1,
                RoleName = "Administrators",
                Users = new List<User>()
            };

            Role users = new Role()
            {
                Id = 2,
                RoleName = "Users",
                Users = new List<User>()
            };

            var membershipProvider = new CustomMembershipProvider();
            admin.Password = membershipProvider.GeneratePasswordHash(admin.UserName, admin.Password);

            admin.Roles.Add(administrators);
            administrators.Users.Add(admin);


            context.Users.AddOrUpdate(User => User.Id, new User[] { admin });
            context.Roles.AddOrUpdate(role => role.Id, new Role[] { administrators, users });

        }
    }
}

 

本当に泣きたいくらいになんとなくしか理解ができません。

自分の気持ちを無視して最後まで進んでみたいと思います。

 

動作確認

 

adminでログインすると、既にパスワードはハッシュ化されていました。

次に、ma2n2nを編集モードに入って、新たにパスワードを再設定した結果がエラーがでました。

 

 

どうも、UsersController.csにエラーがあるようなのですが、全くわかりませんので、レクチャー動画を見直しました。

db.Entry(user).Stateの部分が、db.Entry(dbUser).Stateのようです。

再度ma2n2nのパスワードを編集してみました。ちゃんとハッシュ化されていました。

更に、新しいuserを同じパスワードで登録しましたが、ハッシュ化されているので同じになっていない事を確認することができました。

 

 

動作確認後は、ハッシュ化前のadminでもログインできるようにしたCustomMembershipProvider.csの不要部分を削除して終了となります。

 

注意ポイント

じつは、不要部分を削除すると、adminでのログインができなくなりました。
試しに、ma2n2nのロールを管理者に変更するとログインできました。
一旦adminを削除して新規で追加しなおせば、ちゃんとadminでもログインできるようになりました。

 

動画の通りに進んでいるのでちゃんとできましたが、セキュリティも考えないといけないので参考になりました。

 

https://pctips.jp/security/udemy-aspnet-8/