我是如何一步步編碼完成萬倉網ERP系統的(三)登陸

  https://www.cnblogs.com/smh188/p/11533668.html(我是如何一步步編碼完成萬倉網ERP系統的(一)系統架構)html

  https://www.cnblogs.com/smh188/p/11534451.html(我是如何一步步編碼完成萬倉網ERP系統的(二)前端框架)前端

  https://www.cnblogs.com/smh188/p/11535449.html(我是如何一步步編碼完成萬倉網ERP系統的(三)登陸)git

  https://www.cnblogs.com/smh188/p/11541033.html(我是如何一步步編碼完成萬倉網ERP系統的(四)登陸的具體實現)github

  https://www.cnblogs.com/smh188/p/11542310.html(我是如何一步步編碼完成萬倉網ERP系統的(五)產品庫設計 1.產品類別)redis

  https://www.cnblogs.com/smh188/p/11546917.html(我是如何一步步編碼完成萬倉網ERP系統的(六)產品庫設計 2.百度Ueditor編輯器)json

  https://www.cnblogs.com/smh188/p/11572668.html(我是如何一步步編碼完成萬倉網ERP系統的(七)產品庫設計 3.品牌圖片跨域上傳)後端

  https://www.cnblogs.com/smh188/p/11576543.html(我是如何一步步編碼完成萬倉網ERP系統的(八)產品庫設計 4.品牌類別)跨域

  https://www.cnblogs.com/smh188/p/11578185.html(我是如何一步步編碼完成萬倉網ERP系統的(九)產品庫設計 5.產品屬性項) 緩存

  https://www.cnblogs.com/smh188/p/11589264.html(我是如何一步步編碼完成萬倉網ERP系統的(十)產品庫設計 6.屬性項和類別關聯) 安全

  https://www.cnblogs.com/smh188/p/11596459.html(我是如何一步步編碼完成萬倉網ERP系統的(十一)產品庫設計 7.發佈商品) 

  https://www.cnblogs.com/smh188/p/11610960.html(我是如何一步步編碼完成萬倉網ERP系統的(十二)庫存 1.概述) 

  https://www.cnblogs.com/smh188/p/11669871.html(我是如何一步步編碼完成萬倉網ERP系統的(十三)庫存 2.加權平均價) 

  https://www.cnblogs.com/smh188/p/11763319.html(我是如何一步步編碼完成萬倉網ERP系統的(十四)庫存 3.庫存日誌) 

  萬倉網ERP系統不開源,準備作一個系列,講一講主要的技術點,這些技術點會有源代碼。若是想看全部源代碼,能夠打道回府了,不必再閱讀下去了,浪費您寶貴的時間。

  首先用戶進入到一個後臺系統,確定是先要登陸,這篇我們就說說登陸(固然萬倉網ERP系統開發的第一個頁面並非登陸)。

  登陸頁面主要的是注意保護用戶的密碼不能被竊取,不能是明文,不能被窮舉撞庫(簡單密碼),那怎樣才能實現這3個小目標呢?

  1.不能被竊取,可使用證書(let's encrypt的免費證書)

  2.最好在前端加密後在經過網絡進行傳輸。

  3.最好有大小字母、數字和特殊符號組成8位及以上密碼,防止暴力破解。

  如今我們主要說第2中狀況,如何在前端使用js加密,後端.Net進行解密?密碼學和TLS的知識不在本文的介紹範圍以內,直接上硬核內容吧,前端使用基於X25519密鑰交換(RSA密鑰交換已通過時,最新的Tls1.3只有ecc橢圓曲線密鑰交換這一種,x25519就屬於ecc橢圓曲線的一種)的aes-128-gcm(加解密速度和安全與一身的加密方式,比上一版本aes-cbc加密安全,cpu硬件內置aes-gcm加密模塊)加密,後端.net進行解密,驗證用戶名和密碼是否正確。

  aes_128_gcm

  google的證書使用的就是基於X25519的AES_128_GCM證書,等於我們如今手工用js+.net實現一個基於X25519的AES_128_GCM的證書。

  引用js插件

  https://github.com/brix/crypto-js   sha512.min.js 主要用於原始密碼加密。

  https://github.com/bitwiseshiftleft/sjcl   sjcl.min.js 須要從新壓縮一下,主要用於aes-gcm加密。

  https://github.com/gimer/curve25519nxt  curve.js 可從新壓縮一下,主要用於X25519的密鑰交換。

  前端代碼:

function login() {
   //聲明一個橢圓曲線對象
  
var curve = new Curve25519();
  
   //隨機一個64位的hex數值,並轉化爲字節類型,作爲x25519的私鑰
var privateKey = new Key25519(hexToBytes(randomWord(64)));

   //獲得橢圓曲線的公鑰
var publickey = curve.genPub(privateKey).key;
  
   //把前端的私鑰傳入到後端,後端計算得出公用的aes加密的密鑰,同時獲得後端的公鑰 $.post(
"/Login/GetX25519PublicKey", { publicKey: bytesToHex(publickey) }, function (data) {
     
     //獲得後端的公鑰,結合前端的私鑰,計算得出前端aes加密的密鑰(先後端計算的密鑰是一致的)
var shareKey = bytesToHex(curve.genShared(privateKey, new Key25519(hexToBytes(data.PublicKey))).key);
     
     //聲明一個登陸對象
var loginUser = new Object();
    
     //用戶名 loginUser.UserName
= $.trim($("#txtUserName").val());
     //密碼兩次sha384加密
loginUser.Password
= sha384(sha384($.trim($("#txtPWD").val())));
   //驗證碼
     loginUser.ValidateCode
= $.trim($("#txtValidateCode").val());

     //使用公用密鑰shareKey進行ase加密(aes後邊的參數介紹下mode有gcm和ccm模式,這裏用gcm模式;ts長度gcm模式是128;iter輪詢次數默認10000,我們改成1000,10000次數太多了會卡頓;salt鹽隨機數,iv是向量)
var aesData = sjcl.encrypt(shareKey, JSON.stringify(loginUser), { mode: 'gcm', ts: 128, iter: 1000, salt: sjcl.random.randomWords(4), iv: sjcl.random.randomWords(3) });
    
     //傳入加密後的login字符串,同時須要傳入後端返回的key(用於後端方便查找對應的公用密鑰) $.post(
"/Login/UserLogin", { loginUser: aesData, key: data.Key }, function (data) { if (data.Result == false)
       {
        
alert("登陸失敗") } else { window.location = "/"; } }); }); }

//隨機必定長度的hex數值
function randomWord(len) { var str = "", arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; for (var i = 0; i < len; i++) { var pos = Math.round(Math.random() * (arr.length - 1)); str += arr[pos]; } return str; }

  後端代碼(傳入前端的公鑰,生成aes公用密鑰,返回後端的公鑰):

       //後端獲得前端傳過來的公鑰,生成aes公用密鑰,使用的是 BouncyCastle 類庫   
    public static Dictionary<string, string> GenerateX25519Keys(string publicKey)
        {
            SecureRandom secureRandom = new SecureRandom();
            byte[] privateByte = new byte[X25519.ScalarSize];
            byte[] publicByte = new byte[X25519.PointSize];
            byte[] shareByte = new byte[X25519.PointSize];
            secureRandom.NextBytes(privateByte);
            X25519.ScalarMultBase(privateByte, 0, publicByte, 0);
            X25519.ScalarMult(privateByte, 0, Hex.Decode(publicKey), 0, shareByte, 0);
            Dictionary<string, string> dc = new Dictionary<string, string>();
       //生成x25519 hex公鑰
            dc.Add("PublicKey", Hex.ToHexString(publicByte));
        //生成x25519 hex私鑰
            dc.Add("PrivateKey", Hex.ToHexString(privateByte));
           //生成aes公用密鑰
            dc.Add("ShareKey", Hex.ToHexString(shareByte));
           //返回一個dictionary對象
            return dc;
        }
    
        public ActionResult GetX25519PublicKey(string publicKey)
        {
        //獲得x25519key
            Dictionary<string, string> eccKeys = GenerateX25519Keys(publicKey);
      
       //Redis NewtonsoftSerializer serializer
= new NewtonsoftSerializer(); RedisConfiguration redisConfiguration = RedisCachingSectionHandler.GetConfig(); IRedisCacheConnectionPoolManager connectionPoolManager = new RedisCacheConnectionPoolManager(redisConfiguration); IRedisCacheClient redisClient = new RedisCacheClient(connectionPoolManager, serializer, redisConfiguration);
//使用guid key string key = Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n");
      
//把x25519字典用redis緩存起來 redisClient.GetDbFromConfiguration().Add(key, eccKeys, DateTime.Now.AddSeconds(30));
//返回guid key和x25519 公鑰 return Json(new { Key = key, PublicKey = eccKeys["PublicKey"] }); }

  後端代碼(解密前端傳過來的aes密文):

        //根據aes公用key 解密前端傳進來的aes密文,使用的是 BouncyCastle 類庫
     //傳進來的密文 {"iv":"r5idcq/NZ7VEpZv9","v":1,"iter":1000,"ks":128,"ts":128,"mode":"gcm","adata":"","cipher":"aes","salt":"rEwzN7o5UANsLvB4xf4bZg==","ct":"YI5Ubuvev8787ryV4+X+/1+ICXixZfqkhRxKg0zfi/27M24+Y8w9BOeIhe0tTEa1B1WP8tPYpcTYTHw58G/rpZUxSPNurhUXaAZKoiigl5eeaqOqNq9xHd0s+mKi+l1zuiL3qo5sxb0OcDxuL0clp46UyN0y8gr6xmimuszXalWdssfvCuoT8saJ4rwrcmM2TrjBMP/HG96VjAEzBD1q+teHFWJ50q4PLw=="} 
public static string DecryptString(string ciphertext, string key) { //把aes密文轉換爲json對象 JObject aesJObject = JObject.Parse(ciphertext); //把json對象的ct屬性轉換爲字節(ct就是加密後的密文) byte[] ciphertextByte = Convert.FromBase64String(aesJObject["ct"].ToString()); //把json的salt屬性轉換爲字節 byte[] salt = Convert.FromBase64String(aesJObject["salt"].ToString()); //把json的iv屬性轉換爲字節 byte[] iv = Convert.FromBase64String(aesJObject["iv"].ToString()); //聲明一個PBKDF2對象 Pkcs5S2ParametersGenerator pbkdf2 = new Pkcs5S2ParametersGenerator(new Sha256Digest()); //根據aes公用key,salt,iter輪詢次數(前面傳進來的是1000,這裏也是1000)初始化PBKDF2對象 pbkdf2.Init(Encoding.UTF8.GetBytes(key), salt, 1000); byte[] keyByte = ((KeyParameter)pbkdf2.GenerateDerivedMacParameters(16 * 8)).GetKey(); // 解密獲得字符串 return Encoding.UTF8.GetString(Decrypt(ciphertextByte, keyByte, iv)); } //解密aes密文 public static byte[] Decrypt(byte[] ciphertext, byte[] key, byte[] iv) { //聲明aes gcm對象 GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine()); KeyParameter keyParam = ParameterUtilities.CreateKeyParameter("AES", key); //根據key和IV初始化 ParametersWithIV cipherParameters = new ParametersWithIV(keyParam, iv); cipher.Init(false, cipherParameters); //解密 byte[] plaintext = new byte[cipher.GetOutputSize(ciphertext.Length)]; int length = cipher.ProcessBytes(ciphertext, 0, ciphertext.Length, plaintext, 0); cipher.DoFinal(plaintext, length); //返還前端加密前的login字節 return plaintext; } } //解密aes login密文 public ActionResult UserLogin(string loginUser, string key) {
      try { //Redis相關 NewtonsoftSerializer serializer = new NewtonsoftSerializer(); RedisConfiguration redisConfiguration = RedisCachingSectionHandler.GetConfig(); IRedisCacheConnectionPoolManager connectionPoolManager = new RedisCacheConnectionPoolManager(redisConfiguration); IRedisCacheClient redisClient = new RedisCacheClient(connectionPoolManager, serializer, redisConfiguration); //使用傳進來的來 Key獲取Redis緩存的x25519 Dictionary Dictionary<string, string> eccKey = redisClient.GetDbFromConfiguration().Get<Dictionary<string, string>>(key); if (eccKey == null) {return; } //移除redis中x25519 redisClient.GetDbFromConfiguration().Remove(key);
//解密獲得前端的login對象 LoginViewModel loginUserView = JsonConvert.DeserializeObject<LoginViewModel>(DecryptString(loginUser, eccKey["ShareKey"])); //業務邏輯 // ... return Json(new { Result = true }); } catch (Exception ex) { logError.Error(ex);
          return ; } }

  這樣一個還算完整的登陸就算完成了,有興趣的能夠本身敲敲代碼,作個小測試。

 

PS:客官有時間光臨個人小站 萬倉網

相關文章
相關標籤/搜索