0021. 應用於C# ASP.Web網站的RSA加解密 Query String傳遞帳號範例

情境說明: 1. 透過Query String 傳遞帳號

                   2. 為了避免有人記住QueryString的帳號,導致帳號可以讓任意人登入

                   3. 利用RSA每次產生唯一性的Query String帳號 。

目的:本篇介紹應用於C# ASP.Web網站的RSA加解密 Query String傳遞帳號範例

 

本篇分為三部分:

一、建立網站 - Vs-2015 ASP.Net

二、網站的C# 程式

三、Azure網站的結果瀏覽  & GitHub程式下載

四、參考文獻與資料備註


一、建立網站 - Vs-2015 ASP.Net


開啟Visual Studio -> 檔案 -> 新增 -> 專案 


二、網站的C# 程式


項目

重點

說明

Controller 驗證、傳遞資料 從前端傳遞QueryString ,然後由後端驗證RSA加解密是否正確,正確與失敗導向不同頁面
Helper RSA加解密的方法 參考軟體主廚的整理,將RSA加解密的工具寫於此
Models 前端ViewModel資料 分成登入頁的LoginViewModel 、 與首頁的HomeViewModel
Views 分Loing(登入)、Home(主頁) Login : 攜帶加密後的帳號 (解密失敗導向Login的Error頁面)  Home:正確與私有金鑰解密後可以導向該頁面
Global 公開金鑰、私有金鑰暫存區 因為只有一個WebSite程式,為了快速示範,將兩種金鑰的資料放於此。如果IIS回收或重建,記憶體資料才會重置。

以下是MVC網站的架構,我們逐一說明程式

Controller :

Step 1:  Login Controller ,每次進入該頁面都會產生一次新的公開金鑰 、私有金鑰,並且將公開金鑰 + 帳號 的密文傳到前端,

※帳號這邊假設是 10820

※為了快速解釋範例,ValidateInput 設為false ,但是實際架設的時候必須設為True,避免XSS攻擊

[ValidateInput(false)]//防止XSS攻擊先關閉
public ActionResult Index()
{
            //初始化
            if (RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList == null)
            {
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList = new Models.LoginViewModel();
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPrivateKey = "";
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPublicKey = "";
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.privateKey = new List<string>();
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.publicKey = new List<string>();
                RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.EncryptConsn = new List<string>();
            }
            //每次都產生一組RSA 非對稱密碼
            var getNewRSA = RSAWebSideRedirectorPage.MvcApplication.RsaHelperTool.GenerateRSAKeys();
            //紀錄當前公開金鑰
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPublicKey = getNewRSA.Item1;
            //紀錄當前私有金鑰
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPrivateKey = getNewRSA.Item2;
            //紀錄當前加密後的顧問編號
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowEncryptConsn = RSAWebSideRedirectorPage.MvcApplication.RsaHelperTool.Encrypt(getNewRSA.Item1, "10820");
            //增加公開金鑰
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.publicKey.Add(getNewRSA.Item1);
            //增加私有金鑰
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.privateKey.Add(getNewRSA.Item2);
            //增加加密後的顧問編號
            RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.EncryptConsn.Add(RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowEncryptConsn);

            //傳回Login頁面
            return View(RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList);
}

Step 2: Login Controller ,建立Error的頁面

/// <summary>
/// 導向登入錯誤頁面
/// </summary>
/// <returns></returns>
public ActionResult Error() 
{

    return View();
}

Step 3: HomeController ,由LoginContorller 傳進來的資料進行驗證。正確導向 Home/Index  失敗導向 Login/Error

[ValidateInput(false)]//防止XSS攻擊先關閉
public ActionResult LoginValidation(string EnconSn)
{
     try
     {
          string privateKey = RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPrivateKey;
          string DeCrytionString = RSAWebSideRedirectorPage.MvcApplication.RsaHelperTool.Decrypt(privateKey, EnconSn);

          if (DeCrytionString == "10820")//解密字串與原字串相同
          {
              return RedirectToAction("Index", "Home");
          }

      }
      catch(Exception ex)
      {
          //解密失敗
          return RedirectToAction("Error", "Login");
      }
      //解密字串不對
      return RedirectToAction("Error", "Login");

}

Step4: HomeController,成功登入將資訊傳回前端

※10820 帳號是範例寫成Hard Code,實際使用應該這是用RSA解密的明文

public ActionResult Index()
 {
            HomeViewModel result = new HomeViewModel();
            result.ConSn = "10820";
            result.Message = "歡迎登入";
            result.PrivateKey = RSAWebSideRedirectorPage.MvcApplication.PrivateKey;
            result.PublicKey = RSAWebSideRedirectorPage.MvcApplication.pulbicKeyList.nowPublicKey;
            return View(result);
 }

 

Helper:

這邊參考是點部落範例(軟體主廚) : https://dotblogs.com.tw/supershowwei/2015/12/23/160510

Step 1: GenerateRSAKeys() =>產生publicKey 與 privateKey 

/// <summary>
/// 產生公開金鑰 與 私有金鑰
/// </summary>
/// <returns></returns>
public Tuple<string, string> GenerateRSAKeys()
{
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

            var publicKey = rsa.ToXmlString(false);
            var privateKey = rsa.ToXmlString(true);

            return Tuple.Create<string, string>(publicKey, privateKey);
}

Step 2: Encrypt => 使用 公開金鑰publickKey + 明文 建立加密資訊 (用於頁面的QueryString傳遞)

/// <summary>
/// RSA加密
/// </summary>
/// <param name="publicKey">公開金鑰</param>
/// <param name="content">加密文本</param>
/// <returns></returns>
public string Encrypt(string publicKey, string content)
{
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(publicKey);

            var encryptString = Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(content), false));

            return encryptString;
}

Step 3: Decrypt => 使用 私有金鑰privateKey + 密文 還原解密的資訊

/// <summary>
/// 解密RSA
/// </summary>
/// <param name="privateKey">放進私有金鑰</param>
/// <param name="encryptedContent">已經加密的文本,欲進行解密的資料</param>
/// <returns></returns>
public string Decrypt(string privateKey, string encryptedContent)
{
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(privateKey);

            var decryptString = Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(encryptedContent), false));

            return decryptString;
}

Models:

Step 1:  Home頁面的前端顯示資料

public class HomeViewModel
{
        /// <summary>
        /// 前端顯示訊息
        /// </summary>
        public string Message { get; set;}

        /// <summary>
        /// 帳號
        /// </summary>
        public string ConSn { get; set; }

        /// <summary>
        /// 當前公開金鑰
        /// </summary>
        public string PublicKey { get; set;}

        /// <summary>
        /// 當前私有金鑰
        /// </summary>
        public string PrivateKey { get; set; }
}

Step 2:  Login 頁面的前端顯示資料 (為了展示,這邊將私有金鑰傳到前端,還有歷史紀錄)

public class LoginViewModel
{
        /// <summary>
        /// 公開金鑰清單
        /// </summary>
        public List<string> publicKey { get; set; }

        /// <summary>
        /// 私有金鑰清單
        /// </summary>
        public List<string> privateKey { get; set; }

        /// <summary>
        /// 加密後的密文資料清單
        /// </summary>
        public List<string> EncryptConsn { get; set; }

        /// <summary>
        /// 現存的公開金鑰
        /// </summary>
        public string nowPublicKey { get; set;}
        
        /// <summary>
        /// 現存的私有金鑰
        /// </summary>
        public string nowPrivateKey { get; set;}

        /// <summary>
        /// 現在加密後的帳號
        /// </summary>
        public string nowEncryptConsn { get; set;}
}

 Views:

step 1: Home/index.cshtml 當登入成功時會顯示的資料

@model RSAWebSideRedirectorPage.Models.HomeViewModel
@{
    ViewBag.Title = "Consultant Page";
    Layout = null;
}

<div class="jumbotron">
    <h1>ASP.NET</h1>
    <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
    <p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
</div>

<div class="row">
    <div>登入訊息:@Model.Message</div>
    <div>登入帳號:@Model.ConSn</div>
    <div>公開Key:@Model.PublicKey</div>
    <div>私有Key:@Model.PrivateKey</div>
    <div></div>
</div>

step 2: Login/Index.cshtml 登入時提供給使用者的資訊 

※這邊為了展示,直接將QueryString 製作成<a>登入</a>

@model RSAWebSideRedirectorPage.Models.LoginViewModel
@{
    ViewBag.Title = "Login Page";
    Layout = null;
}

<div class="row">
    <h2>RSA非對稱加密顧問SN應用</h2>
    <div>
        <table>
            <tr>
                <td>帳號:</td>
            </tr>
            <tr>
                <td>10820</td>
            </tr>
        </table>
    </div>
    <p>consn = 10820 加密後的字串(Publickey + consn 加密後的結果)</p>
    <p>@Model.nowEncryptConsn</p>
    <br />
    <p>當前連結金鑰(PublicKey): </p>
    <p>@Model.nowPublicKey</p>
    <br />
    <p>當前連結私有金鑰(PrivateKey): </p>
    <p>@Model.nowPrivateKey</p>
    <br />
    <p>完整的Href: </p>
    <p>@(Url.Action("LoginValidation", "Home", new { EnconSn = Model.nowEncryptConsn }))</p>
    <br />
    <a href="@(Url.Action("LoginValidation", "Home", new { EnconSn = Model.nowEncryptConsn}))">當前有效的登入連結</a>
    <hr />
    <div>以下為歷史連結 (最新的Key在最上方)</div>

    @for(int i = Model.publicKey.Count()-1;i>=0;i--)
    {
        <hr />
        <div>序號: @i</div>
        <a href="@(Url.Action("LoginValidation", "Home", new { EnconSn = Model.EncryptConsn[i]  }))">產生登入連結</a>
        <br />
        <p>序號: @i 完整的Href: </p>
        <p>@(Url.Action("LoginValidation", "Home", new { EnconSn = Model.EncryptConsn[i] }))</p>
        <br />
        <hr />
    }
</div>

step 3: Login/error.cshtml 登入失敗時的資訊 

@model RSAWebSideRedirectorPage.Models.LoginViewModel
@{
    ViewBag.Title = "登入失敗";
    Layout = null;
}
<div class="row">
   <h2>登入失敗,當前的 公開金鑰(Public Key)與 私有金鑰 (Private Key)無法正確解密</h2>
</div>

 Global:

step 1: Global 存放資料,該資料會一值保存,直到IIS回收、重建才清空記憶體。

public class MvcApplication : System.Web.HttpApplication
{
        public static RSAHelper RsaHelperTool = new RSAHelper();//RSA解密工具
        public static RSAWebSideRedirectorPage.Models.LoginViewModel pulbicKeyList;//Login頁面顯示的資料
        public static string PrivateKey;//當前私有金鑰
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
}

 


三、Azure網站的結果瀏覽  & GitHub程式下載


Azure網站的結果瀏覽 : http://rsawebsideredirectorpage.azurewebsites.net/

項目

說明

1.  帳號,這邊是Hard Code 為 10820
2. PublickKey + 10820 後的 密文(每次都是隨機唯一碼)
3. Query String 的導向連結
4. 當前有效的登入連結 (有效的PrivateKey 與 密文) 
5. 歷史的連結,已經失效了

step 1:  按下當前有效的登入連結 - 導向到正確的頁面,並且提供當前資訊

step 2: 按下失效的歷史連結 - 導向Error 的頁面

GitHub程式下載 : 下載連結

 


四、參考文獻與資料備註


1. QueryString 的長度 (GIPI): 

https://dotblogs.com.tw/jimmyyu/2010/03/25/asp-net-40-max-query-string-length

2. C# RSA的加解密工具程式(軟體主廚):

https://dotblogs.com.tw/supershowwei/2015/12/23/160510

3. RSA 加密原理 (Wiki):

https://zh.wikipedia.org/zh-tw/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95