應用安全除了用戶許可權認證外,還要考慮到數據安全,傳輸安全、系統漏洞等方面。本篇文章重點討論數據存儲安全和傳輸安全,主要技術手段就是加密和解密。 ...
系列目錄
本系列涉及到的源碼下載地址:https://github.com/seabluescn/Blog_WebApi
一、概述
應用安全除了用戶許可權認證外,還要考慮到數據安全,傳輸安全、系統漏洞等方面。本篇文章重點討論數據存儲安全和傳輸安全,主要技術手段就是加密和解密。
二、基本概念
信息在傳輸和存儲的過程中有泄密的風險,加密的目的就是解決這些風險。
1、信息存儲在資料庫中,如果資料庫泄露會造成敏感數據泄露,如用戶密碼,手機號碼、工資信息;
2、數據傳輸過程中數據遭到劫持,泄露用戶名、密碼等信息。
針對不同的應用場景,對應採用的加密手段:
1、密碼存儲:任何人都不可以看到密碼,驗證是只要比對加密後的字元串是否一致即可,所以要採用非可逆加密演算法;
2、敏感信息存儲:存儲的信息是加密的,同時需要解密還原數據,所以採用可逆的演算法,加密後可以解密,加密的密碼和解密的密碼可以一致。
3、敏感信息傳輸:在瀏覽器向伺服器傳輸數據的過程,建議優先採用HTTPS傳輸模式即可解決問題。如果不採用https的傳輸模式,用戶提交的數據會有被竊取的風險,由於客戶端加密是採用js進行操作,分析其網頁源碼可以查詢到加密的密鑰,所以就不能採用上面的對稱加密演算法,而是必須採用非對稱加密演算法,即:加密密碼和解密密碼不一樣,加密密碼是可以公開的,有加密密碼無法解密信息,這樣通過客戶端加密、服務端解密解決數據被竊取的風險。
總結如下:
問題場景 |
採用加密手段 |
加密演算法 |
用戶密碼存儲過程泄密 |
非可逆加密演算法 |
MD5 |
敏感數據存儲過程泄密 |
可逆對稱加密演算法 |
DES/AES |
敏感數據傳輸過程泄密 |
可逆非對稱加密演算法 |
RSA |
三、MD5加密
首先需要通過NuGet獲取包:NETCore.Encrypt,下同。
加密代碼:
var srcString = "ghc123456"; var hashed = EncryptProvider.Md5(srcString);
非可逆加密演算法,不好解密。
四、DES加密與解密
DES的加密與解密:
//需要加密的信息 var srcString = "ghc123456"; //密碼:24位字元串,通過EncryptProvider.CreateDesKey()來生成。 var desKey = "i4FftwEAP7enqKRl8JhBC7gv"; //加密 var encrypted = EncryptProvider.DESEncrypt(srcString, desKey); //解密 var decrypted = EncryptProvider.DESDecrypt(encrypted, desKey);
五、RSA客戶端加密、服務端解密
1、利用工具生成密鑰
RSA加密與解密需要用到一對密鑰,可以通過第三方工具來生成密鑰,我這裡用的是阿裡螞蟻金服的一個簽名工具:
工具下載:https://docs.open.alipay.com/291/105971
密鑰格式選擇PKCS1
公鑰給客戶端加密使用,私鑰給伺服器端解密使用。
2、前端加密
首先通過Bower引入兩個js的庫:jQuery、jsencrypt
前端加密代碼:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>Using jQuery</title> <script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/jsencrypt/bin/jsencrypt.min.js"></script> <script> $(document).ready(function () { $("#query").click(function (event) { //需要加密的信息 var pwd = "ghc123456"; //公鑰 var publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr8a8EjoY4op+k/BF0hjkaOgMDAmSl+VCc2rU6j/Ji9BsX/tRWM4o3wZgkMdAWim9vwM0Ll4x/jTsVr1+Qe7ffnBl6ZLpMs77MbVVG5wowVvie1rMi2fB7akdi+Id+R3SHZNSTnbtEATXtY8nGV5ZDcHMrw6PI30+HlaclxmhFSr+UGERyi6/mLKnC3Y5elxu8Zlj8eHPZhV6DV87ngkGdp4aLdWWfqP1Iz0cIMAJ7hiYP4o4/W/vn1YSyJeTs/oQvUeEaMVGxGIXOY+m9ToZbPwjXWPjeZJ1RLRKfhYoobguyGezQNC0xlBdWNU9gfVw7mk0+q1eCe5XfA45O54MbwIDAQAB"; var encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); var encrypttext = encrypt.encrypt(pwd); $("#text").val(encrypttext); }); }); </script> </head> <body> <h1>Query</h1> <button id='query'>Qyery</button> <textarea id="text"></textarea> </body> </html>
3、後端解密
把前端加密獲得的密文Copy到下麵代碼中,驗證解密後的信息。
using System.Security.Cryptography;namespace Encrypt { class Program { static void Main(string[] args) { Console.WriteLine("Study Encrypt!"); //從前端傳來的加密後的密文 var encryptedMessage = "Qw0W7P3dScUe3isXIVkumhgTxKk0fcaxzyvyCVnFEHBH6qw/daMKt0BYK1SrFZrqmcX1tRx/yeAa0ea2QsYmLL2OynffoUnPtjLjNHZd9R7+Uqh8e6nmD1r4yK6o37tfb9SmPxJtgLR45dnxcFAIt3xRY5eqXFCMCJKdKwV7aLMayG1+Rg7YBhTfUoNbJoLTODupCweaUVwZdvcxOZrqd5x3SRw3x+bPkQpchJRXc4QoLhEfh/Hw5tiNkuMUzPt/3iyx7iuKQR4q+9Wnd81UaenLaihVlkiU5MEjZJOnz1HaEDhMc9rMlTXV36jAOOAVTufb2jILbNycuhGQkFh9SA=="; //私鑰 var privateKey = "MIIEowIBAAKCAQEAr8a8EjoY4op+k/BF0hjkaOgMDAmSl+VCc2rU6j/Ji9BsX/tRWM4o3wZgkMdAWim9vwM0Ll4x/jTsVr1+Qe7ffnBl6ZLpMs77MbVVG5wowVvie1rMi2fB7akdi+Id+R3SHZNSTnbtEATXtY8nGV5ZDcHMrw6PI30+HlaclxmhFSr+UGERyi6/mLKnC3Y5elxu8Zlj8eHPZhV6DV87ngkGdp4aLdWWfqP1Iz0cIMAJ7hiYP4o4/W/vn1YSyJeTs/oQvUeEaMVGxGIXOY+m9ToZbPwjXWPjeZJ1RLRKfhYoobguyGezQNC0xlBdWNU9gfVw7mk0+q1eCe5XfA45O54MbwIDAQABAoIBAH0lfVlsy7Le7+fcNZmz50tZito3JovGylzqPtTYvWIx7jcX837KqQbAv5fUhNisx09rtIcewXE/tNS87Vt7+ttGowh9dFKcUvO9Ku8Ra2LfTIyOxPqr0MKomUSypKxssuAjt4Ht4jJ5gCrf1PKW3ciRpm0sbHTUApoPCEX8FVe/qlxDVv+9S7chJxsGuzIXNDGMW4XfHw+RaUvjOFQgy6R9HPtvEw3EWY53OY+Mvd7Pr0o2ATaetFnBwyZa3AJF+SU1wzqtJZhxd9/ACly1s4yXmG+rvD+2k7bhAXWQRxwxSDXDCd/ibRM4YklNRkAD+IWJrbZ1rh3uoHisiBv3O2ECgYEA2BHdBDKD2Odd2861kkVck5Ar0ii883y4vNKLNPGQaUmPbHCo+i6CREUDW0SxKXkViaIAFjj0Kmhqa6XASu4SCCc8s+1/2Pyrxb5qgSSJY2LvbZ8SeZi8DfBOXKPdCtUqyHMIYBw3rPyzXwoJEAdHSCrk83tt4urm7qKSqgYpRx0CgYEA0EKcQ3mT+Hd0cx+ISJ2D2X01Z1+zb+Dusn23fr5qs5Nox+VE71+9DN9GVAlBFNg4oz7FCsFS3mJRbvQmmybw0KwU/rKHdnvk15B6IglPgshkVar7j7G51UVntHMW4IBU7jTUoMs1yQ6PPzb9E2Z6UeHbGaoRZCVYZidt55rDL/sCgYEApbI9PcTHW4VCcxg4Ie3TKs567HWVQVw6B4OmgXlmd3eT52MWEpWsDFKoWkt5WQakP6HeUyxmAkeEpPy9VDjx1xLP+GN/kZVi3QhDgLnWKkNqvTQp5Nn+DOpmDaEUGASVBJdCqwG4qI45t/5oKMSMI4nRfe7/u+7MHeDKfFyxNvkCgYBYJPkybdC9BwIYf64U3eYiNSZXPGAb6B3fGeqCEGHk420jvdvxXJoNSqrfgpMzGVjPbw/Cv5QtX3uL9HYqkM634z13l2RSN5nhytqGcV5fwiUFRTr31IcMxzVfYJ68IlTQBThBXgDDug/S95khjuwSn/81249EzbGeeu2/avdV5QKBgGhTmFO9/1HRq0F538XlL8WVvUeyM+XUUNT1qQQsc22anUkM09cv+64ZyQZEqDNX46Tbl2BfTwFhL50MMj7Edjp4L+2dROiuffzcuEQCBCkCoV6B58a9NQehwu8h1j1kxNjDDPSeZnmC0ZJ/Eum1TVcnNa2Zb6II5R72qXThwJM9"; //根據私鑰生成RSA提供者 RSACryptoServiceProvider rsaCryptoServiceProvider = CreateRsaProviderFromPrivateKey(privateKey); byte[] decrypted = rsaCryptoServiceProvider.Decrypt(Convert.FromBase64String(encryptedMessage), false); Console.WriteLine($"decrypted = {Encoding.UTF8.GetString(decrypted)}"); Console.ReadLine(); } private static RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey) { var privateKeyBits = System.Convert.FromBase64String(privateKey); var RSA = new RSACryptoServiceProvider(); var RSAparams = new RSAParameters(); using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits))) { byte bt = 0; ushort twobytes = 0; twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) binr.ReadByte(); else if (twobytes == 0x8230) binr.ReadInt16(); else throw new Exception("Unexpected value read binr.ReadUInt16()"); twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) throw new Exception("Unexpected version"); bt = binr.ReadByte(); if (bt != 0x00) throw new Exception("Unexpected value read binr.ReadByte()"); RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.D = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.P = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr)); RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr)); } RSA.ImportParameters(RSAparams); return RSA; } private static int GetIntegerSize(BinaryReader binr) { byte bt = 0; byte lowbyte = 0x00; byte highbyte = 0x00; int count = 0; bt = binr.ReadByte(); if (bt != 0x02) return 0; bt = binr.ReadByte(); if (bt == 0x81) count = binr.ReadByte(); else if (bt == 0x82) { highbyte = binr.ReadByte(); lowbyte = binr.ReadByte(); byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; count = BitConverter.ToInt32(modint, 0); } else { count = bt; } while (binr.ReadByte() == 0x00) { count -= 1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); return count; } } }
註意:+號的處理:因為數據在網路上傳輸時,非字母數字字元都將被替換成百分號(%)後跟兩位十六進位數,而base64編碼在傳輸到後端的時候,+會變成空格,因此先替換掉。後端再替換回來。
前端js代碼:
var str = encodeURI(pwddata).replace(/\+/g, '%2B')
後端C#代碼:
var str = pwd.Replace("%2B","+")