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

原创 2017年07月14日 05:25:44

摘要: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


版权声明:本文为博主原创文章,未经博主允许不得转载。

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Mvc中url地址栏base64加密

Mvc中url地址栏base64加密 1.引用base64.js包 eg: 2.前台页面上eg: 登录eg: function cmdEncrypt() { if (conf...

js地址栏加密传参

js加密地址栏参数传递(数字和字符中文皆可加密),后台解密,增加网站的安全性。...

给定A, B两个整数,不使用除法和取模运算,求A/B的商和余数

给定A, B两个整数,不使用除法和取模运算,求A/B的商和余数。 1.   最基本的算法是,从小到大遍历: for (i = 2 to A -1)          if (i * B > A)...

配置SOIL库,实现纹理加载

SOIL 是一个用于向OpenGL中加载

SceneManager.LoadScene的使用方法

SceneManager.LoadScene的使用方法
  • XYK0318
  • XYK0318
  • 2016年03月17日 09:52
  • 18831

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

C语言dup和dup2函数

功能:复制文件描述符 头文件:#include 函数原型:int dup(int oldfd)           int dup2(int oldfd,int newfd) 功能详解:dup和d...

weui loading效果实现

界面 --> ...

Logger日志级别说明及设置方法、说明

Logger日志级别说明及设置方法、说明
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ASP.NET MVC中使用JS实现不对称加密密码传输
举报原因:
原因补充:

(最多只允许输入30个字)