رمزنگاری کلمه عبور(Password) با استفاده از Salt Hashing در MVC

در این مقاله یک نمونه صفحه ثبت نام را طراحی می کنیم که کلمه عبور را با استفاده از Salted hashing رمزنگاری کرده و آن را در قسمت ورود(Login) رمزگشایی می کند.

رمزنگاری کلمه عبور(Password) با استفاده از Salt Hashing در MVC

در این نوع رمزنگاری، Salt به طور تصادفی برای هر پسورد تولید می شود. در یک محیط معمولی، Salt و کلمه عبور به هم الحاق شده و با یک تابع هش رمزنگاری پردازش می شوند و نتیجه خروجی(اما نه پسورد اصلی) به همراه Salt در بانک اطلاعاتی ذخیره می شود. هش کردن(Hashing) امکان احراز هویت بعدی را ایجاد می کند.

روش کار به چه صورت است؟

در اینجا می خواهیم یک فرم ثبت نام ساده را نشان دهیم که در آن پسورد با استفاده از فرمت رمزنگاری شده در پایگاه داده ذخیره می شود و هنگامی که کاربری Login شود پسورد رمزگشایی شده و به کاربر اجازه ورود می دهد.

همچنین، در این رمزنگاری یک Salt تصادفی تولید خواهیم کرد، که باعث می شود همان پسورد زمانی که با استفاد از رمزنگاری ذخیره می شود متفاوت شود.

برای این کار ما از روش Entity Framework code-first و MVC4 استفاده کرده ایم.

مرحله 1: ایجاد پایگاه داده با استفاده از روش Code First

یک پروژه MVC خالی ایجاد کرده و همچنین یک پروژه دیگر به صورت class library تحت Visual C# اضافه کنید.

در این مرحله یک کلاس User با فیلدهای زیر ایجاد کرده ایم:

public class User  
{  
    [Key]  
    public int RegistrationId  
    {  
        get;  
        set;  
    } //This will be primary key column with auto increment  
    public string FirstName  
    {  
        get;  
        set;  
    }  
    public string LastName  
    {  
        get;  
        set;  
    }  
    public string UserName  
    {  
        get;  
        set;  
    }  
    public string EmailId  
    {  
        get;  
        set;  
    }  
    public string Password  
    {  
        get;  
        set;  
    }  
    public string Gender  
    {  
        get;  
        set;  
    }  
    public string VCode  
    {  
        get;  
        set;  
    }  
    public DateTime CreateDate  
    {  
        get;  
        set;  
    }  
    public DateTime ModifyDate  
    {  
        get;  
        set;  
    }  
    public bool Status  
    {  
        get;  
        set;  
    }  
}

یک کلاس context به صورت زیر ایجاد می کنیم. قبل از ایجاد این کلاس Entity Framework را از NuGet Packages نصب کنید:

public class CmsDbContext : DbContext  
{  
    public DbSet<User> ObjRegisterUser { get; set; } // Here User is the class
}

 

مرحله 2: ایجاد کلاس Helper

ما از یک کلاس Helper به جای اضافه کردن متدها در کنترلر استفاده کرده ایم:

public static class Helper  
{  
    public static string ToAbsoluteUrl(this string relativeUrl) //Use absolute URL instead of adding phycal path for CSS, JS and Images     
    {  
        if (string.IsNullOrEmpty(relativeUrl)) return relativeUrl;  
        if (HttpContext.Current == null) return relativeUrl;  
        if (relativeUrl.StartsWith("/")) relativeUrl = relativeUrl.Insert(0, "~");  
        if (!relativeUrl.StartsWith("~/")) relativeUrl = relativeUrl.Insert(0, "~/");  
        var url = HttpContext.Current.Request.Url;  
        var port = url.Port != 80 ? (":" + url.Port) : String.Empty;  
        return String.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, VirtualPathUtility.ToAbsolute(relativeUrl));  
    }  
    public static string GeneratePassword(int length) //length of salt    
    {  
        const string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";  
        var randNum = new Random();  
        var chars = new char[length];  
        var allowedCharCount = allowedChars.Length;  
        for (var i = 0; i <= length - 1; i++)  
        {  
            chars[i] = allowedChars[Convert.ToInt32((allowedChars.Length) * randNum.NextDouble())];  
        }  
        return new string(chars);  
    }  
    public static string EncodePassword(string pass, string salt) //encrypt password    
    {  
        byte[] bytes = Encoding.Unicode.GetBytes(pass);  
        byte[] src = Encoding.Unicode.GetBytes(salt);  
        byte[] dst = new byte[src.Length + bytes.Length];  
        System.Buffer.BlockCopy(src, 0, dst, 0, src.Length);  
        System.Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);  
        HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");  
        byte[] inArray = algorithm.ComputeHash(dst);  
        //return Convert.ToBase64String(inArray);    
        return EncodePasswordMd5(Convert.ToBase64String(inArray));  
    }  
    public static string EncodePasswordMd5(string pass) //Encrypt using MD5    
    {  
        Byte[] originalBytes;  
        Byte[] encodedBytes;  
        MD5 md5;  
        //Instantiate MD5CryptoServiceProvider, get bytes for original password and compute hash (encoded password)    
        md5 = new MD5CryptoServiceProvider();  
        originalBytes = ASCIIEncoding.Default.GetBytes(pass);  
        encodedBytes = md5.ComputeHash(originalBytes);  
        //Convert encoded bytes back to a 'readable' string    
        return BitConverter.ToString(encodedBytes);  
    }  
    public static string base64Encode(string sData) // Encode    
    {  
        try  
        {  
            byte[] encData_byte = new byte[sData.Length];  
            encData_byte = System.Text.Encoding.UTF8.GetBytes(sData);  
            string encodedData = Convert.ToBase64String(encData_byte);  
            return encodedData;  
        }  
        catch (Exception ex)  
        {  
            throw new Exception("Error in base64Encode" + ex.Message);  
        }  
    }  
    public static string base64Decode(string sData) //Decode    
    {  
        try  
        {  
            var encoder = new System.Text.UTF8Encoding();  
            System.Text.Decoder utf8Decode = encoder.GetDecoder();  
            byte[] todecodeByte = Convert.FromBase64String(sData);  
            int charCount = utf8Decode.GetCharCount(todecodeByte, 0, todecodeByte.Length);  
            char[] decodedChar = new char[charCount];  
            utf8Decode.GetChars(todecodeByte, 0, todecodeByte.Length, decodedChar, 0);  
            string result = new String(decodedChar);  
            return result;  
        }  
        catch (Exception ex)  
        {  
            throw new Exception("Error in base64Decode" + ex.Message);  
        }  
    }  
}

مرحله 3: تغییر فایل Web.Config

<add name="CmsDbContext" connectionString="Data Source=(local);Initial Catalog=WebCMS;User ID=sa;Password=Admin@321;" providerName="System.Data.SqlClient" />  

همانطور که می بینید نام انتخاب شده همان نامی است که در کلاس context داده شده است، برای مثال در اینجا  می باشدCmsDbContext.

بعد از آن پایگاه داده با نام WebCMS و جدولی با نام User با ستون هایی برای پارامترهای هر کلاس ایجاد خواهد شد و این بعد از انجام عملیات Insert / Update / Delete انجام می شود.

مرحله 4: طراحی صفحه ثبت نام(Register)

<div class="panel panel-default mb0">  
    <div class="panel-heading ui-draggable-handle">  
        <h3 class="panel-title"><strong>New User </strong>  Registration</h3> </div> @using (Html.BeginForm("Registration", "Admin", null, FormMethod.Post)) { @Html.AntiForgeryToken()  
    <div class="panel-body">  
        <div class="form-group pt20">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">First Name</label>  
            <div class="col-md-6 col-xs-12">  
                <div class="input-group"> <span class="input-group-addon"><span class="fa fa-pencil"></span></span>  
                    <input type="text" class="form-control form-group" required="" name="FirstName"> @*Getting value by name and the name should be the name given in database column*@ </div> <span class="help-block">First Name field sample</span> </div>  
            <div class="clearfix"></div>  
        </div>  
        <div class="form-group">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">Last Name</label>  
            <div class="col-md-6 col-xs-12">  
                <div class="input-group"> <span class="input-group-addon"><span class="fa fa-pencil"></span></span>  
                    <input type="text" class="form-control form-group" required="" name="LastName"> @*Getting value by name and the name should be the name given in database column*@ </div> <span class="help-block">Last Name field sample</span> </div>  
            <div class="clearfix"></div>  
        </div>  
        <div class="form-group">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">User Name</label>  
            <div class="col-md-6 col-xs-12">  
                <div class="input-group"> <span class="input-group-addon"><span class="fa fa-pencil"></span></span>  
                    <input type="text" class="form-control form-group" required="" name="UserName"> @*Getting value by name and the name should be the name given in database column*@ </div> <span class="help-block">User Name field sample</span> </div>  
            <div class="clearfix"></div>  
        </div>  
        <div class="form-group">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">Email Id</label>  
            <div class="col-md-6 col-xs-12">  
                <div class="input-group"> <span class="input-group-addon"><span class="fa fa-pencil"></span></span>  
                    <input type="text" class="form-control form-group" required="" name="EmailId"> @*Getting value by name and the name should be the name given in database column*@ </div> <span class="help-block">Email-Id field sample</span> </div>  
            <div class="clearfix"></div>  
        </div>  
        <div class="form-group">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">Password</label>  
            <div class="col-md-6 col-xs-12">  
                <div class="input-group"> <span class="input-group-addon"><span class="fa fa-unlock-alt"></span></span>  
                    <input type="password" class="form-control" required="" name="Password"> </div> <span class="help-block">Password field sample</span> </div>  
            <div class="clearfix"></div>  
        </div>  
        <div class="form-group">  
            <label class="col-md-3 col-xs-12 control-label align-right pt7">Gender</label>  
            <div class="col-md-6 col-xs-12">  
                <label class="radio-inline">  
                    <input type="radio" checked="checked" value="Male" name="Gender">Male</label>  
                <label class="radio-inline">  
                    <input type="radio" value="Female" name="Gender">Female</label>  
            </div>  
            <div class="clearfix"></div>  
        </div>  
    </div>  
    <div class="panel-footer">  
        <input type="reset" value="Clear Form" name="btnReset" class="btn btn-default" />  
        <input type="submit" id="btnSubmit" name="btnSubmit" value="Submit" class="btn btn-primary pull-right" /> </div> }   
 </div> 

در اینجا یک کنترلر برای ثبت نام با نام Admin ایجاد کرده ایم و کدهای زیر را اضافه می کنیم:

public ActionResult Registration()  
{  
    return View();  
}  
[ValidateAntiForgeryToken]  
[HttpPost]  
public ActionResult Registration(User objNewUser)  
{  
    try  
    {  
        using(var context = new CmsDbContext())  
        {  
            var chkUser = (from s in context.ObjRegisterUser where s.UserName == objNewUser.UserName || s.EmailId == objNewUser.EmailId select s).FirstOrDefault();  
            if (chkUser == null)  
            {  
                var keyNew = Helper.GeneratePassword(10);  
                var password = Helper.EncodePassword(objNewUser.Password, keyNew);  
                objNewUser.Password = password;  
                objNewUser.CreateDate = DateTime.Now;  
                objNewUser.ModifyDate = DateTime.Now;  
                objNewUser.VCode = keyNew;  
                context.ObjRegisterUser.Add(objNewUser);  
                context.SaveChanges();  
                ModelState.Clear();  
                return RedirectToAction("LogIn", "Login");  
            }  
            ViewBag.ErrorMessage = "User Allredy Exixts!!!!!!!!!!";  
            return View();  
        }  
    }  
    catch (Exception e)  
    {  
        ViewBag.ErrorMessage = "Some exception occured" + e;  
        return View();  
    }  
}

بعد از این مرحله صفحه ثبت نام شبیه تصویر زیر خواهد شد:

بعد از این مرحله می توانید پایگاه داده خود را بررسی کرده و ستون پسورد را ببینید. همچنین پسورد مشابه را وارد کرده و ستون پسورد را بررسی کنید، می بینید که پسورد ها به صورت متفاوت در جدول پایگاه داده ذخیره شده است.

مرحله 5: طراحی صفحه Login

    <div class="form-horizontal"> @using (Html.BeginForm("LogIn", "Login", null, FormMethod.Post)) { @Html.AntiForgeryToken()  
        <div class="form-group">  
            <div class="col-md-12">  
                <input type="text" class="form-control" required="" placeholder="E-mail" name="UserName" /> </div>  
        </div>  
        <div class="form-group">  
            <div class="col-md-12">  
                <input type="password" class="form-control" required="" placeholder="Password" name="Password" /> </div>  
        </div>  
        <div class="form-group">  
            <div class="col-md-6"> <a href="#" class="btn btn-link btn-block">Forgot your password?</a> </div>  
            <div class="col-md-6">  
                <button class="btn btn-info btn-block">Log In</button>  
            </div>  
        </div>  
        <div class="login-subtitle"> Don't have an account yet?@Html.ActionLink("Create an account", "Registration", "Admin") </div> }   
     </div>  

در اینجا یک کنترلر برای ورود با نام Login ایجاد کرده و کد زیر را به آن اضافه می کنیم:

public ActionResult Login()  
{  
    return View();  
}  
[ValidateAntiForgeryToken]  
[HttpPost]  
public ActionResult LogIn(string userName, string password)  
{  
    try  
    {  
        using(var context = new CmsDbContext())  
        {  
            var getUser = (from s in context.ObjRegisterUser where s.UserName == userName || s.EmailId == userName select s).FirstOrDefault();  
            if (getUser != null)  
            {  
                var hashCode = getUser.VCode;  
                //Password Hasing Process Call Helper Class Method    
                var encodingPasswordString = Helper.EncodePassword(password, hashCode);  
                //Check Login Detail User Name Or Password    
                var query = (from s in context.ObjRegisterUser where(s.UserName == userName || s.EmailId == userName) && s.Password.Equals(encodingPasswordString) select s).FirstOrDefault();  
                if (query != null)  
                {  
                    //RedirectToAction("Details/" + id.ToString(), "FullTimeEmployees");    
                    //return View("../Admin/Registration"); url not change in browser    
                    return RedirectToAction("Index", "Admin");  
                }  
                ViewBag.ErrorMessage = "Invallid User Name or Password";  
                return View();  
            }  
            ViewBag.ErrorMessage = "Invallid User Name or Password";  
            return View();  
        }  
    }  
    catch (Exception e)  
    {  
        ViewBag.ErrorMessage = " Error!!! contact cms@info.in";  
        return View();  
    }  
} 

بعد از این مرحله می بینید که صفحه Login به صورت زیر می شود:

 

با یک نام کاربری و کلمه عبور مناسب می توانید وارد شوید.

فایل های ضمیمه
دانلود نسخه ی PDF این مطلب