ASP.NET MVC中使用JS实现不对称加密密码传输

来源:互联网 发布:mac os x10.11安装 编辑:程序博客网 时间:2024/06/05 05:55

摘要:ASP.NET MVC中登录页面中点击登录后,用户名、密码将被明文传输到Controller中,使用Fiddler等工具可以轻松截获并获取密码, 这是不安全的。 使用对称加密,如AES,密钥将被暴露前端代码,也是不安全的。使用不对称加密能够较好解决这个问题。本文以RSA不对称加密的形式,在JS端通过公钥对密码进行加密,将密文传输到后端后通过密钥进行解密。

关键字: 不对称加密;对称加密;RSA 算法;AES; 密钥;公钥


0 背景

登录是最常见的需求之一,在这个环节,安全问题不可避免,明文传输很容易被截获并暴露密码原文。如下图使用Fiddle中出现的情况。


为了避免这种情况,通常办法有1 使用HTTPS形式解决; 2 使用公钥和不对称加密对密文进行加密;3使用对称加密,比如AES。

这3种方案中,方案1是终极方案,但是需要克服证书获取和配置的问题, 本方案不是本文讨论重点,请有兴趣的自行查阅https://letsencrypt.org/。方案3, 以AES加密为例,必须把加密密钥存放在前端。 而前端对用户来说是开源的,很多开发者尝试把密钥藏的路径很深,但无疑这还是自欺欺人的。

方案2中,在JS端进行密码的RSA加密是有必要的,因为密码需要在用户点击“登录”按钮后被提交到服务器,这个过程被截获是很容易的。同时,防范CSRF类型攻击的特性也必须保留。这就要求:必须使用AJAX在JS端对密码加密,并向后台的AccountController中的LoginAction发起Post请求。而不能使用传统的FormSubmit的方案。

 

AJAX post请求中,需要注意问题: 由于需要防范CSRF攻击的同时保障密文传输安全。需要同时顾及如下问题

问题1: 如何通过AJAX向Controller发起ajax请求?

问题2:如何在ajax请求中加入AntiForgeryToken? 

问题3: AJAX请求前,如何对密码进行RSA加密?

问题4: RSA的key format 有两种, pem格式和C#所支持的XML格式,通常JS支持pem, C#支持xml, 如何转换?

带着问题,进入操作步骤;

 

1 操作步骤

1.1 新建Web Application


1.2 选择MVC, Authentication中选择”IndividualUser Accounts”



1.3 Views-> Account->Login.cshtml中代码修改如下

需要将目标URL设置为隐藏字段,供JS读取

@model WebApplication_JS_RSA.ViewModels.LoginViewModel@{    ViewBag.Title = "Log in";    Layout = "~/Views/Shared/_Layout.cshtml";}<div class="wrapper--login">    <div class="wrapper--login__body">        <h1>Login</h1>        @using (@Html.BeginForm("Login", "Account", FormMethod.Post, new { id = "loginForm" }))        {            <div id="AccountLoginURL" class="hidden" data-url="@Url.Action("login", "account")"></div>            @Html.AntiForgeryToken()            <div class="val">                @Html.LabelFor(m => m.UserName)                <div class="val__field">                    @Html.TextBoxFor(model => model.UserName, new { @class = "form-control", id = "userNameTextBox" })                    @Html.ValidationMessageFor(model => model.UserName)                </div>            </div>            <div class="val">                @Html.LabelFor(m => Model.EncryptedPassword)                <div class="val__field">                    @Html.PasswordFor(model => model.EncryptedPassword, new { @class = "form-control", id = "passwordTextBox" })                    @Html.ValidationMessageFor(model => model.EncryptedPassword)                </div>            </div>            @Html.HiddenFor(model => model.ReturnUrl)            @Html.HiddenFor(model => model.RedirectDomain)            <div class="val__message" id="errorMsg"></div>            <div class="row">                <input type="button" id="LoginButton" value="Login" class="button--primary">            </div>        }        <div id="PublicKey" class="hidden" data-val="@Model.PublicKey"></div>        <div class="row line">            <span class="line__1"></span>or<span class="line__2"></span>        </div>        <div class="row">            <a href="@Url.Action("forgotpassword", "Account")" class="button--secondary">Forgot Password ?</a>        </div>    </div></div>@section Scripts {    @Scripts.Render("~/Scripts/jquery-1.10.2.min.js")        @Scripts.Render("~/Scripts/jquery.validate.min.js")    @Scripts.Render("~/bundles/jqueryval")    @Scripts.Render("~/Scripts/jsencrypt.js")    @Scripts.Render("~/Scripts/Views/Account/Login.js")}

1.4           Scripts文件夹下新增 Login.js,  jsencrypt.js文件

jsencrypt.js文件请从“参考链接3”中获取. Login.js代码如下:

var login = (    function ($) {        $(document).ready(            function () {                $('#LoginButton').click(function () {                    var publicKey = $('#PublicKey').data("val");                    var plainpassword = $('#passwordTextBox').val();                    var AccountLoginURL = $('#AccountLoginURL').data("url");                    var encryptedPassword;                                        var formSelector = "#loginForm";                    var form = $(formSelector);                    form.validate();                    var isFormValid = form.valid();                    //encrypt password                    if (plainpassword !== null && plainpassword !== "") {                        var crypt = new JSEncrypt();                        crypt.setPublicKey(publicKey);                        encryptedPassword = crypt.encrypt(plainpassword);                        console.log(encryptedPassword);                        $('#passwordTextBox').val(encryptedPassword);                    }                                        debugger;                    if (isFormValid) {                        //blockUI                        //showSpinner();                        $.ajax({                            type: "POST",                            url: AccountLoginURL,                            data: form.serialize(),                            success: function (data, textStatus, jqXHR) {                                if (data.RedirectUrl !== null) {                                    window.location.href = data.RedirectUrl;                                }                                else {                                    $('#errorMsg').text(data.ErrorMessage);                                }                            },                            error: function (jqXhr, textStatus, errorThrown) {                                console.log('error: ' + jqXhr.responseText);                            },                            complete: function (jqXHR, textStatus) {                                //hideSpinner();                            }                        });                    }                });            });    }(jQuery));

1.5 配置publicKey和PrivateKey

通过“3参考链接“中连接2,生成公钥和私钥。 公钥保持pem格式,因为JS类库使用的需要。 把私钥通过“3参考链接“中连接4(转换器)转换成XML格式,因为.NET能够识别XML格式的私钥。




1.6           Controller端

Controllers->AccountController->Login()

        // POST: /Account/Login        [HttpPost]        [AllowAnonymous]        [ValidateAntiForgeryToken]        public async Task<ActionResult> Login()        {            var form = Request.Form;            string plainTextPassword = form["plainTextPassword"].ToString();            string encryptedPassword = form["encryptedPassword"].ToString();            //decrypt            String privateKeyPathFile = AppDomain.CurrentDomain.BaseDirectory + @"\Content\PrivateKey.xml";            string RSAprivateKey = System.IO.File.ReadAllText(privateKeyPathFile);            RSAEncryption rsaCryption = new RSAEncryption();            string decryptedPwd = rsaCryption.RSADecrypt(RSAprivateKey, encryptedPassword);            return View();                  }

1.7        RSAEncryption

如需代码,请参考链接: https://github.com/memoryfraction/CommonUsedFunctions/tree/master/Encryption%26Decryption


2 小结

2.1 小结

本文提出了在ASP.NETMVC中,密码传输安全问题,提出了3种可行解决方案。重点讲述了RSA不对称加密的实现方式,同时保留了微软自带的AntiForgeryToken, 以防止CSRF攻击。达到了密文传输密码的效果,即使被人截获,也无法得知密码明文。

作者知识和精力都有限,如有不足,欢迎指正。

 

2.2 补充

在更新版的3.1 范例代码中,更新使用了Form Serialization技术,优点: 可以直接对表单序列化,传输到后端; 能够使用C# Decoration验证; 建议前端后端同时验证, 双保险; 相见代码;

 

3 参考链接

1 本文详细代码,请参考GitHub:https://github.com/memoryfraction/CommonUsedFunctions/tree/master/WebApplication_JS_RSA

2 JS Encrypt的Demo,连接: http://travistidwell.com/jsencrypt/demo/

3 JSEncrypt的主页:http://travistidwell.com/jsencrypt/

4 Key的格式转换:     https://superdry.apphb.com/tools/online-rsa-key-converter

5 《什么是CSRF攻击,如何在ASP.NET MVC网站中阻止这种攻击?》http://blog.csdn.net/fanrong1985/article/details/71701301


阅读全文
0 0
原创粉丝点击