情境說明: 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 »</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