.NET進階篇04-Serialize序列化、加密解密

知識須要不斷積累、總結和沉澱,思考和寫做是成長的催化劑
這篇很輕鬆,沒有什麼費腦子的,因此解析較少,代碼較多,爲數很少的拿來即用篇
整個章節分佈請移步 .NET開篇總括html

內容目錄

1、概述2、序列化一、二進制文件二、XML三、JSON3、加解密一、不可逆加密MD5二、對稱可逆加密三、非對稱可逆加密四、一些組合應用一、CA證書二、單邊認證https三、雙邊認證web

1、概述

序列化是把一個內存中的對象的信息轉化成一個能夠持久化保存(二進制數據)的形式,以便於保存或傳輸,序列化的主要做用是不一樣平臺之間進行通訊,經常使用的有序列化有json、xml、文件等,反序列化是將進制數據還原爲對象,內存中的對象稍縱即逝,序列化反序列化就是爲了保持對象的持久化。就像用DV錄像,用播放器播放同樣。算法

加密是經過對消息進行編碼,創建一種安全的交流方式,使得只有你和你所指望的接收者可以理解。數據庫

2、序列化

一、二進制文件

內置的BinaryFormatter二進制序列化器用於將對象序列化和反序列化二進制文件。要引用System.Runtime.Serialization.Formatters.Binary。
假設咱們有如下對象json

[Serializable]  //必須添加序列化特性
public class Person
{
   [NonSerialized]
   public int Id = 1;
   public string Name { getset; }
   public string Sex { getset; }
}

建立一個BinaryFormatter實例,調用實例Serialize方法將對象寫入文件流中瀏覽器

string fileName = Path.Combine("D:\\", @"BinarySerialize.txt");//文件名稱與路徑
using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
{
    Person p = new Person() { Id = 1, Name = "jack", Sex = "男" };//對象
    BinaryFormatter binFormat = new BinaryFormatter();//建立二進制序列化器
    binFormat.Serialize(fStream, p);
}

經過調用實例Deserialize方法把二進制文本反序列化爲對象安全

using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
{
    BinaryFormatter binFormat = new BinaryFormatter();//建立二進制序列化器
    fStream.Position = 0;//重置流位置
    Person p = (Person)binFormat.Deserialize(fStream);//反序列化對象
}

但注意咱們必須在類上面標記Serializable特性,才能序列化該對象,默認屬性字段均可序列化了,固然咱們經過標記NonSerialized能夠要求某個字段不序列化,但這樣反序列後該字段就會沒有值(或默認值)服務器

結合我們前天學到的泛型,咱們包裝一下BinarySerializeHelper幫助類app

 public class BinarySerializeHelper
 {

    /// <summary>
    /// 將對象序列化爲字符串
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="t">實例</param>
    /// <returns>字符串</returns>
    public static string ObjectToString<T>(T t)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        using (MemoryStream stream = new MemoryStream())
        {
            formatter.Serialize(stream, t);
            string result = System.Text.Encoding.UTF8.GetString(stream.ToArray());
            return result;
        }
    }

    /// <summary>
    /// 將對象序列化爲文件
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="t">實例</param>
    /// <param name="path">存放路徑</param>
    public static void ObjectToFile<T>(T t, string path)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate))
        {
            formatter.Serialize(stream, t);
            stream.Flush();
        }
    }

    /// <summary>
    /// 將字符串反序列爲類型
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="s">字符串</param>
    /// <returns>對象</returns>
    public static T StringToObject<T>(string s) where T : class
    {
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(s);
        BinaryFormatter formatter = new BinaryFormatter();
        using (MemoryStream stream = new MemoryStream(buffer))
        {
            T result = formatter.Deserialize(stream) as T;
            return result;
        }
    }

    /// <summary>
    /// 將文件反序列化爲對象
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="path">路徑</param>
    /// <returns>對象</returns>
    public static T FileToObject<T>(string path) where T : class
    {
        using (FileStream stream = new FileStream(path, FileMode.Open))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            T result = formatter.Deserialize(stream) as T;
            return result;
        }
    }
}

二、XML

在沒有JSON(JavaScript Object Notation)以前,XML(Extensible Markup Language)做爲規範,輕量的數據存儲格式獲得大量應用,經常使用來做爲配置文件和數據傳輸。.NET內置了XmlSerializer類來將對象序列化爲xml,將XML反序列化爲對象。
用法和上面BinaryFormatter同樣,先實例化而後調用序列化、反序列化方法框架

string fileName = Path.Combine("D:\\"@"XmlSerialize.txt");//文件名稱與路徑
using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
{
    Person p = new Person() { Id = 1, Name = "jack", Sex = "男" };//對象
    XmlSerializer xmlFormat = new XmlSerializer(typeof(Person));//建立XML序列化器,須要指定對象的類型
    xmlFormat.Serialize(fStream, p);
}
using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
{
    XmlSerializer xmlFormat = new XmlSerializer(typeof(Person));//建立XML序列化器,須要指定對象的類型
    fStream.Position = 0;//重置流位置
    Person p = (Person)xmlFormat.Deserialize(fStream);//反序列化對象
}

不一樣的是實例化XmlSerializer時候須要指定待序列化對象的類型,並且對象無需標記Serializable特性

咱們也能夠像上面同樣封裝成泛型以共用

public class XmlSerializeHelper
{
    /// <summary>
    /// 將對象序列化爲xml文件
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="t">對象</param>
    /// <param name="path">xml存放路徑</param>
    public static void ObjectToXml<T>(T t, string path) where T : class
    {
        XmlSerializer formatter = new XmlSerializer(typeof(T));
        using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate))
        {
            formatter.Serialize(stream, t);
        }
    }

    /// <summary>
    /// 將對象序列化爲xml字符串
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="t">對象</param>
    public static string ObjectToXml<T>(T t) where T : class
    {
        XmlSerializer formatter = new XmlSerializer(typeof(T));
        using (MemoryStream stream = new MemoryStream())
        {
            formatter.Serialize(stream, t);
            string result = System.Text.Encoding.UTF8.GetString(stream.ToArray());
            return result;
        }
    }

    /// <summary>
    /// 將xml文件反序列化爲對象
    /// </summary>
    /// <typeparam name="T">類型</typeparam>
    /// <param name="t">對象</param>
    /// <param name="path">xml路徑</param>
    /// <returns>對象</returns>
    public static T XmlToObject<T>(T t, string path) where T : class
    {
        XmlSerializer formatter = new XmlSerializer(typeof(T));
        using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate))
        {
            XmlReader xmlReader = new XmlTextReader(stream);
            T result = formatter.Deserialize(xmlReader) as T;
            return result;
        }
    }
}

關於XML,在之前也被濃墨重彩的使用,.NET對其解析用到的類庫在System.Xml下,包括XmlDocument,XmlElement,XmlNode等類能夠實現對xml文檔的徹底控制。

三、JSON

JSON(JavaScript Object Notation)相比較XML更加輕量,傳輸有效減小帶寬,可讀性也差很少,在互聯網尤爲移動互聯網中獲得普遍應用。.NET後面的框架配置也多基於JSON格式。

.NE提供了DataContractJsonSerializer和JavaScriptSerializer兩個類來進行JSON的轉換,二者大體相同,DataContractJsonSerializer(命名空間System.Runtime.Serialization.Json)在wcf時代應用較多,你必須在DataContract和DataMember來特性標記成員。JavaScriptSerializer更多用在Web中通訊,能夠序列化任何類型,包括匿名類型。這裏以JavaScriptSerializer爲例,簡單使用一會兒。(命名空間System.Web.Script.Serialization)

此次咱們先直接封裝個泛型方法,後面兩個方法咱們用了比較經常使用的Newtonsoft.Json第三方的JSON轉換庫

public class JsonSerializeHelper
{
    #region Json
    /// <summary>
    /// 將對象序列化爲Json字符串
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static string ObjectToString<T>(T obj)
    {
        JavaScriptSerializer jss = new JavaScriptSerializer();
        return jss.Serialize(obj);
    }

    /// <summary>
    /// 將Json字符串反序列化爲對象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="content"></param>
    /// <returns></returns>
    public static T StringToObject<T>(string content)
    {
        JavaScriptSerializer jss = new JavaScriptSerializer();
        return jss.Deserialize<T>(content);
    }

    /// <summary>
    /// 使用Newtonsoft.Json序列化對象爲json字符串
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static string ToJson<T>(T obj)
    {
        return JsonConvert.SerializeObject(obj);
    }

    /// <summary>
    /// 使用Newtonsoft.Json將json字符串反序列化爲對象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="content"></param>
    /// <returns></returns>
    public static T ToObject<T>(string content)
    {
        return JsonConvert.DeserializeObject<T>(content);
    }
    #endregion Json
}

3、加解密

字面意思,加密解密,咱們常常須要對不想要別人輕鬆看見的東西加密,之因此說是不能輕鬆看見,由於安全都是相對的,沒有絕對的安全,咱們加密只是提升別人破解的難度,勸退大多數人。
加密是經過對消息進行編碼,創建一種安全的交流方式,使得只有你和你所指望的接收者可以理解。
那麼怎麼樣才能叫安全呢?消息在接收方和發送方進行安全傳遞,通常要知足下面三個要點:

  1. 消息的發送方可以肯定消息只有預期的接收方能夠解密(不保證第三方沒法得到,但保證第三方沒法解密)。
  2. 消息的接收方能夠肯定消息是由誰發送的(消息的接收方能夠肯定消息的發送方)。
  3. 消息的接收方能夠肯定消息在途中沒有被篡改過(必須確認消息的完整性)。確保消息由A發出沒有被篡改到達預期的B手中。

加密一般分爲三種方式:不可逆加密、對稱可逆加密和非對稱可逆加密

一、不可逆加密MD5

MD5(單向散列算法)的全稱是Message-Digest Algorithm 5(信息-摘要算法),經MD二、MD3和MD4發展而來。MD5算法的使用不須要支付任何版權費用。

MD5特色:
輸入任意長度的信息,通過處理,輸出爲128位的信息(數字指紋);
不一樣的輸入獲得的不一樣的結果(惟一性);
根據128位的輸出結果不可能反推出輸入的信息(不可逆);

MD5是不可逆的,就是根據加密後信息得不到加密前的。

一、防止被篡改
好比一個電子文檔,發送過程當中若是被篡改,則先後的md5不同
二、防止直接看到明文
好比用戶密碼消息,若是數據庫中存儲的明文,則泄漏後別人就直接登陸。用MD5加密後存儲,即便泄密獲得的也是加密後的,沒法還原加密前的密碼,但如今網上有不少MD5解密的,都是經過撞庫實現,由於密碼通常也就數字生日字母等組合,目前已知的MD5庫已經有幾百億樣本了,若是簡單密碼仍是容易經過撞庫破解的,因此通常還會在用戶密碼基礎上再加上一些自定義的信息後再MD5(俗稱加鹽)。
三、防止抵賴(數字簽名)
A寫了一個文件,而後在第三方認證機構備案,第三方利用MD5造成摘要信息,若是往後A抵賴不是他寫的,第三方就對文件從新MD5而後與記錄在冊的比對。
四、急速秒傳
網盤應用,記錄第一次上傳文件的MD5,而後別人再上傳,匹配MD5,一致時就可不用上傳,直接給個軟鏈接,達到急速秒傳。

如下是C#版本MD5加密

   public class MD5Encrypt
    {
        /// <summary>
        /// MD5加密,和動網上的16/32位MD5加密結果相同,
        /// 使用的UTF8編碼
        /// </summary>
        /// <param name="source">待加密字串</param>
        /// <param name="length">16或32值之一,其它則採用.net默認MD5加密算法</param>
        /// <returns>加密後的字串</returns>
        public static string Encrypt(string source, int length = 32)//默認參數
        
{
            if (string.IsNullOrEmpty(source)) return string.Empty;
            HashAlgorithm provider = CryptoConfig.CreateFromName("MD5"as HashAlgorithm;
            byte[] bytes = Encoding.UTF8.GetBytes(source);//這裏須要區別編碼的
            byte[] hashValue = provider.ComputeHash(bytes);
            StringBuilder sb = new StringBuilder();
            switch (length)
            {
                case 16://16位密文是32位密文的9到24位字符
                    for (int i = 4; i < 12; i++)
                    {
                        sb.Append(hashValue[i].ToString("x2"));//轉換爲小寫的16進制
                    }
                    break;
                case 32:
                    for (int i = 0; i < 16; i++)
                    {
                        sb.Append(hashValue[i].ToString("x2"));
                    }
                    break;
                default:
                    for (int i = 0; i < hashValue.Length; i++)
                    {
                        sb.Append(hashValue[i].ToString("x2"));
                    }
                    break;
            }
            return sb.ToString();
        }

        /// <summary>
        /// 獲取文件的MD5摘要
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static string AbstractFile(string fileName)
        
{
            using (FileStream file = new FileStream(fileName, FileMode.Open))
            {
                return AbstractFile(file);
            }
        }

        /// <summary>
        /// 根據stream獲取文件摘要
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static string AbstractFile(Stream stream)
        
{
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] retVal = md5.ComputeHash(stream);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < retVal.Length; i++)
            {
                sb.Append(retVal[i].ToString("x2"));
            }
            return sb.ToString();
        }
    }

二、對稱可逆加密

借用一下這張圖。對稱加密的思路很是簡單,就是含有一個稱爲密鑰(密碼學中發音yao)的東西,在消息發送前使用密鑰對消息進行加密,在對方收到消息以後,使用相同的密鑰進行解密。加密速度快,但密鑰安全是個問題,密鑰放在保密的地方。

對稱加密很明顯的問題就是要確保密鑰的安全,密鑰若是被第三方獲取,接受者便沒法區分正確的發送者。

以DES AES Blowfish爲表明。DES(Data Encryption Standard),像加密狗通常就是利用對稱可逆加密

C#語言版本的DES加解密以下所示,注意密鑰長度是8位

   public class DesEncrypt
    {
        /// <summary>
        /// 密鑰key
        /// </summary>
        private static byte[] _rgbKey = ASCIIEncoding.ASCII.GetBytes("miyaokey");
        /// <summary>
        /// 偏移量 爲了解決原文中有重複生成的密文中也有重複現象
        /// </summary>
        private static byte[] _rgbIV = ASCIIEncoding.ASCII.GetBytes("miyaokey".Insert(0"w").Substring(08));

        /// <summary>
        /// DES 加密
        /// </summary>
        /// <param name="text">須要加密的值</param>
        /// <returns>加密後的結果</returns>
        public static string Encrypt(string text)
        
{
            DESCryptoServiceProvider dsp = new DESCryptoServiceProvider();
            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream crypStream = new CryptoStream(memStream, dsp.CreateEncryptor(_rgbKey, _rgbIV), CryptoStreamMode.Write);
                StreamWriter sWriter = new StreamWriter(crypStream);
                sWriter.Write(text);
                sWriter.Flush();
                crypStream.FlushFinalBlock();
                memStream.Flush();
                return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length);
            }
        }

        /// <summary>
        /// DES解密
        /// </summary>
        /// <param name="encryptText"></param>
        /// <returns>解密後的結果</returns>
        public static string Decrypt(string encryptText)
        
{
            DESCryptoServiceProvider dsp = new DESCryptoServiceProvider();
            byte[] buffer = Convert.FromBase64String(encryptText);

            using (MemoryStream memStream = new MemoryStream())
            {
                CryptoStream crypStream = new CryptoStream(memStream, dsp.CreateDecryptor(_rgbKey, _rgbIV), CryptoStreamMode.Write);
                crypStream.Write(buffer, 0, buffer.Length);
                crypStream.FlushFinalBlock();
                return ASCIIEncoding.UTF8.GetString(memStream.ToArray());
            }
        }
    }

三、非對稱可逆加密

非對稱加密的接收者和發送者都持有兩個密鑰,一個是對外公開的,稱爲公鑰,一個是自行保管的,稱爲私鑰。非對稱加密的規則是由某人A的公鑰加密的消息,只能由A的私鑰進行解密;由A的私鑰加密的消息只能由A的公鑰解密。

 

可使用接收方公鑰加密,接受方私鑰解密,這樣能夠確保只有預期的接收方能接受消息,但沒法保證發送方是誰,由於誰均可以拿接收方的公鑰來加密,這其實就是加密模式

若是使用發送方的私鑰加密,發送方的公鑰解密,能夠確保消息是由發送方A發出的,但拿到發送方公鑰的均可以解密,這就是認證模式。由於不管加密模式,認證模式都沒法同時知足加解密的三要點,而後引入了數字簽名

數字簽名就是上面非對稱加密的認證模式基礎上作了改進,加入了像MD5的散列算法,多一條路對原始信息進行散列加密,消息仍以明文傳遞,最後比較接收到的消息的散列值和接受到原始消息散列值,肯定消息是否被中間篡改。但很明顯,明文傳遞,不安全,第三方能夠直接查看消息

就像上圖所示,咱們再對明文消息進行加密,確保被指望的接收方接受。那結合加密模式,發送方用接收方的公鑰加密信息,而後接收方只能用接收方本身的私鑰來解密。因此整個過程利用4個密鑰,加密鑰既能夠是公鑰也能夠是私鑰。

C#版本的RSA(Rivest Shamir Ad1eman)加解密以下所示

    public class RsaEncrypt
    {
        /// <summary>
        /// 獲取加密/解密對
        /// 給你一個,是沒法推算出另一個的
        /// Encrypt   Decrypt
        /// </summary>
        /// <returns>Encrypt   Decrypt</returns>
        public static KeyValuePair<stringstringGetKeyPair()
        
{
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            string publicKey = RSA.ToXmlString(false);
            string privateKey = RSA.ToXmlString(true);
            return new KeyValuePair<stringstring>(publicKey, privateKey);
        }

        /// <summary>
        /// 加密:內容+加密key
        /// </summary>
        /// <param name="content"></param>
        /// <param name="encryptKey">加密key</param>
        /// <returns></returns>
        public static string Encrypt(string content, string encryptKey)
        
{
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(encryptKey);
            UnicodeEncoding ByteConverter = new UnicodeEncoding();
            byte[] DataToEncrypt = ByteConverter.GetBytes(content);
            byte[] resultBytes = rsa.Encrypt(DataToEncrypt, false);
            return Convert.ToBase64String(resultBytes);
        }

        /// <summary>
        /// 解密  內容+解密key
        /// </summary>
        /// <param name="content"></param>
        /// <param name="decryptKey">解密key</param>
        /// <returns></returns>
        public static string Decrypt(string content, string decryptKey)
        
{
            byte[] dataToDecrypt = Convert.FromBase64String(content);
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSA.FromXmlString(decryptKey);
            byte[] resultBytes = RSA.Decrypt(dataToDecrypt, false);
            UnicodeEncoding ByteConverter = new UnicodeEncoding();
            return ByteConverter.GetString(resultBytes);
        }


        /// <summary>
        /// 能夠合併在一塊兒的,,每次產生一組新的密鑰
        /// </summary>
        /// <param name="content"></param>
        /// <param name="encryptKey">加密key</param>
        /// <param name="decryptKey">解密key</param>
        /// <returns>加密後結果</returns>
        private static string Encrypt(string content, out string publicKey, out string privateKey)
        
{
            RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
            publicKey = rsaProvider.ToXmlString(false);
            privateKey = rsaProvider.ToXmlString(true);

            UnicodeEncoding ByteConverter = new UnicodeEncoding();
            byte[] DataToEncrypt = ByteConverter.GetBytes(content);
            byte[] resultBytes = rsaProvider.Encrypt(DataToEncrypt, false);
            return Convert.ToBase64String(resultBytes);
        }
    }

四、一些組合應用

一、CA證書

CA(Certification Authority)證書是由CA權威機構(國內像阿里),咱們在互聯網裏要信任一個東西,若是有個權威機構來背書,咱們可能更加信任。CA證書本身也能夠頒發,就看你們認不認了。

像上圖所示。咱們網站須要去CA機構申請一個證書,CA機構根據咱們提供的網站一些信息,而後CA機構對這些信息用CA機構的私鑰加密,造成CA證書(包括加密後的基本信息和數字簽名)安裝到IIS等服務器上,瀏覽器內置了CA解密鑰,瀏覽器訪問服務器時,服務器把證書給瀏覽器,,經過發證機關找到次機關的解密鑰(瀏覽器也內置了發證機關的CA證書(根證書,包含CA機構和解密公鑰)),而後瀏覽器經過解密鑰就得到圖中百度公司的MD5信息,經過對比服務器端傳遞過來的基本信息生成MD5後比較解密了數字證書後的MD5一致性,確保證書沒問題,沒有被篡改。

二、單邊認證https

 

上面驗證了證書,而後獲得百度的公鑰(申請證書時提供的公鑰,包含在證書中),用此百度公鑰加密一個消息給服務器,服務器若是有正確的私鑰解密返回正確的消息,則驗證服務器是百度。確認以後,瀏覽器則隨機提供一個加密鑰,記得用百度公鑰加密後傳輸(避免被中間截取),而後百度服務器獲得這個新生成的加密鑰,之後雙方就經過這個加密鑰加密傳輸數據,只有瀏覽器和服務器知道的。

三、雙邊認證

 

上面單邊認證只是證實了服務器是那個服務器,雙邊認證就再證實瀏覽器是那個瀏覽器,通常銀行常常會用U盾,K寶等,和單邊認證相似,將證書發給服務器

 

若是手機在手邊,也能夠關注下vx:xishaobb,互動或獲取更多消息。固然這裏也一直更新de,下期見,拜了個拜拜。

相關文章
相關標籤/搜索