ASP.NET MVC 4 實踐RSA非對稱式加密

近年來的Developer人數越來越多,可是在資料傳輸大多數人都忽略了加密的重要性。

因有SSL憑證的關係,很多人在處理資料後都是明碼就給了SSL層級來做加密的動作,那如果沒有了SSL憑證,資料要怎麼做比較安全。

以登入畫面為例,採用RSA非對稱式加密的方法來實作,對資料送出加密,以及後端SERVER接收後的解密。

前端會以JS來做加密動作,後端以C# 來做解密的處理。

非對稱式加密,也就是需要一把公開金鑰(Public Key),以及私有基金鑰(Private Key)。

會需要到的架構大概如下(隨意畫畫別太介意):

我們的Controller底下有一個Action來做畫面的顯示,以及POST帳號登入處理。

然後我們要先準備兩個兩把鑰匙的文件。一個為PEM檔案格式一個為XML檔案格式。

PEM檔案格式是給予前端做使用的公鑰,XML是給予C#作解密的私鑰,而為何不統一使用PEM格式,因為C#看不懂PEM的格式。

公開金鑰格式
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHGTSuKoFrGAblY0ftcXqEw4/BdX
gXyR/uNJPm1TZC9NGCLooIDy4QwYhoOlTUj9c5ucxE5IYWtYS/ssvnjki/YTreYc
R30L9O26FC1jLn/wr7VHp6LINbw6e1eTQnrjnnaJdifDO08vLimjnwefGuvzKDym
OiM2KQVCiRSf1JFdAgMBAAE=
-----END PUBLIC KEY-----
私有金鑰格式
<RSAKeyValue>
  <Modulus>cZNK4qgWsYBuVjR+1xeoTDj8F1eBfJH+40k+bVNkL00YIuiggPLhDBiGg6VNSP1zm5zETkhha1hL+yy+eOSL9hOt5hxHfQv07boULWMuf/CvtUenosg1vDp7V5NCeuOedol2J8M7Ty8uKaOfB58a6/MoPKY6IzYpBUKJFJ/UkV0=</Modulus>
  <Exponent>AQAB</Exponent>
  <P>tpc/1vLLJXg2Agz1wqsSQdVbgZw/Rb1e4ABurKZOBMwmeUdzipnwzJv+Gd1cB9FBR/jmBD2MJoycJfyNeEbwFw==</P>
  <Q>nzzIMRwp9Mb3rFBE3IB3y9PupCheKDXRS4U6UXK1ar/xMecIqfefOtCsz2mtz00fIB17FkI7g7rtbIeJiFyeqw==</Q>
  <DP>RuqrwuJ+AEmWQGmkMj2bU7J4Xfi/omiQptPEKI5XEwnvj38u4xAzNGUJ5iXRjr+5aSjEvbTh8D8Ajshucd6rdQ==</DP>
  <DQ>eXFjxICURwiPz60QN5MKyjsB39Shqs0QqCYdigyP67AjhUmMRASEPdj0UuNoGZfZyyZwv1MYDKk9de4QqBzrLQ==</DQ>
  <InverseQ>HjiApzfA1DhEodRlv9NI92XUNN2intTbz9XMXKBOl/+GLRNtMccIukft9z5XbmXkHWLfLOm7RE0SWvzF2AxQjg==</InverseQ>
  <D>MHFDopNRQppl9WzkoPedOA2iMI6JU0muLuGBt/22oJpAbjtMolN1+8PGNAZghX5dPgVKkZ07uB5sIhD+mO/aZiRH9fon9ov5GpWkjb1fCeOwTCei/hh2g+ipj7Jdgm7GmyIx7ZGnAzqAxH3UpBEVvasujbb2YK+0GB6Gsmqg690=</D>
</RSAKeyValue>

準備好兩份文件後我們就可以編寫Controller和View

首先進到登入畫面要將公鑰給前端網頁取得。

//顯示登入頁面
public ActionResult Login()
{
  var Data = new LogOn();
  Data.PublicKey = RSAService.GetPulicKey(); //將公開金鑰放置前端的頁面
  return View(Data);
}

這裡解釋一下,RSAService只是去讀取我的公鑰的檔案,然後存成字串的模式,然後放在ViewModel給予前端使用。

 @using (Html.BeginForm("Login", "Account", FormMethod.Post))
 {
  @Html.AntiForgeryToken()
  <div class="form-bottom">
    <form role="form" action="" method="post" class="login-form">
      <div class="form-group">
        <label class="sr-only" for="form-username">VENDORNO</label>
          <input type="text" name="Account"  class="form-username form-control" id="Account">
      </div>

      <div class="form-group">
        <label class="sr-only" for="form-password">PWD</label>
          <input type="password" name="PWD"  class="form-password form-control" id="PWD">
      </div>                  
     
      <div id="PublicKey" class="hidden" data-val="@Model.PublicKey"></div>

      <button type="submit" id="LoginBT" class="btn btn-Login">Singin</button>

    </form>
  </div>
}

這裡作法是將Public Key存在DIV裡面,當然也可以使用COOKIES的方式來放置PublicKey,就看實作的時候要採取什麼方式。

下一步就是前端的加密JS如下:

<script src="~/Scripts/RSA/jsencrypt.js"></script>
    <script>
        $('#LoginBT').click(function ()
        {
            $.blockUI({
                message: "<i class='fa fa-spinner fa-pulse orange' style='font-size:600%'></i>",
                //borderWidth:'0px' 和透明背景
                css: { borderWidth: '0px', backgroundColor: 'transparent' },
            });

            var publicKey = $('#PublicKey').data("val");
            var encryptedPassword;
            var pwd = $('#PWD').val();

            if (pwd !== null && pwd !== "") {
                var crypt = new JSEncrypt();
                crypt.setPublicKey(publicKey);
                encryptedPassword = crypt.encrypt(pwd);
                console.log(encryptedPassword);
                $('#PWD').val(encryptedPassword);
            }
        })
 
    </script>

這段會引用到JSEncryp來做加密的動作,此外還用了BLOCKUI來做遮罩的事件。

加密完再將資料放回TEXTBOX,採用原生的ASP.NET MVC @using (Html.BeginForm("Login", "Account", FormMethod.Post))將資料傳送至後端的Action。

後端接收到資料的時候密碼會是加密過的資料,然後用SERVER端的私有鑰匙進行解密,如下:

Controller

[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Login(LogOn LogOnModel)
{
  if (!ModelState.IsValid)
  {
    return View();
  }

  LogOnModel.PWD = RSAService.GetPlainText(LogOnModel.PWD); //解密取得明碼
}

Service解密實作:

public static string RSADecrypt(string xmlPrivateKey, string m_strDecryptString)
{                    
 byte[] PlainTextBArray;
 byte[] DypherTextBArray;
 string Result;
 var rsa = new RSACryptoServiceProvider();
 rsa.FromXmlString(xmlPrivateKey);
 PlainTextBArray = Convert.FromBase64String(m_strDecryptString);
 DypherTextBArray = rsa.Decrypt(PlainTextBArray, false);
 Result = (new ASCIIEncoding()).GetString(DypherTextBArray);
 return Result;
}

這裡採用.Net所提供的RSACryptoServiceProvider來做解密動作,需要將加密後的資料打成Byte[]的資料型態,取得私有鑰匙進行解密,在編碼回去看得懂的資料型態。

加解密大致上就到這邊,後續的應用就靠自己延伸出更多的想法吧,不僅僅是字串加密,資料檔案傳輸也都能加密。

前端的Public Key

在網路傳輸時候加密過的資料:(在此還保留了MVC的@Html.AntiForgeryToken()防偽機制)

後端解密前:

後端解密後:

新手發文,有誤麻煩用力鞭策,謝謝。

本篇參考:

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

JSEncryp