.NET進階篇04-Serialize序列化、加密解密
知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑 這篇很輕鬆,沒有什麼費腦子的,所以解析較少,程式碼較多,為數不多的拿來即用篇 整個章節分佈請移步 .NET開篇總括
一、概述
序列化是把一個記憶體中的物件
的資訊轉化成一個可以持久化儲存(二進位制資料)
的形式,以便於儲存或傳輸,序列化的主要作用是不同平臺之間進行通訊,常用的有序列化有json、xml、檔案等,反序列化是將進位制資料還原為物件
,記憶體中的物件稍縱即逝,序列化反序列化就是為了保持物件的持久化。就像用DV錄影,用播放器播放一樣。
加密是通過對訊息進行編碼
,建立一種安全的交流方式
,使得只有你和你所期望的接收者能夠理解。
二、序列化
1、二進位制檔案
內建的BinaryFormatter
二進位制序列化器用於將物件序列化和反序列化二進位制檔案。要引用System.Runtime.Serialization.Formatters.Binary。
假設我們有以下物件
[Serializable] //必須新增序列化特性
public class Person
{
[NonSerialized]
public int Id = 1;
public string Name { get; set; }
public string Sex { get; set; }
}
複製程式碼
建立一個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幫助類
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;
}
}
}
複製程式碼
2、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,Sex = "男" };//物件
XmlSerializer xmlFormat = new XmlSerializer(typeof(Person));//建立XML序列化器,需要指定物件的型別
xmlFormat.Serialize(fStream,p);
}
using (Stream fStream = new FileStream(fileName,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,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,FileMode.OpenOrCreate))
{
XmlReader xmlReader = new XmlTextReader(stream);
T result = formatter.Deserialize(xmlReader) as T;
return result;
}
}
}
複製程式碼
關於XML,在以前也被濃墨重彩的使用,.NET對其解析用到的類庫在System.Xml下,包括XmlDocument,XmlElement,XmlNode等類可以實現對xml檔案的完全控制。
3、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
}
複製程式碼
三、加解密
字面意思,加密解密,我們經常需要對不想要別人輕鬆看見的東西加密,之所以說是不能輕鬆看見,因為安全都是相對的,沒有絕對的安全,我們加密只是提高別人破解的難度,勸退大多數人。
加密是通過對訊息進行編碼,建立一種安全的交流方式,使得只有你和你所期望的接收者能夠理解。
那麼怎麼樣才能叫安全呢?訊息在接收方和傳送方進行安全傳遞,一般要滿足下面三個要點:
- 訊息的傳送方能夠確定訊息只有預期的接收方可以解密(不保證第三方無法獲得,但保證第三方無法解密)。
- 訊息的接收方可以確定訊息是由誰傳送的(訊息的接收方可以確定訊息的傳送方)。
- 訊息的接收方可以確定訊息在途中沒有被篡改過(必須確認訊息的完整性)。確保訊息由A發出沒有被篡改到達預期的B手中。
加密通常分為三種方式:不可逆加密、對稱可逆加密和非對稱可逆加密
1、不可逆加密MD5
MD5(單向雜湊演演算法)的全稱是Message-Digest Algorithm 5(資訊-摘要演演算法),經MD2、MD3和MD4發展而來。MD5演演算法的使用不需要支付任何版權費用。
MD5特點: 輸入任意長度的資訊,經過處理,輸出為128位的資訊(數字指紋); 不同的輸入得到的不同的結果(唯一性); 根據128位的輸出結果不可能反推出輸入的資訊(不可逆);
MD5是不可逆的,就是根據加密後資訊得不到加密前的。
1、防止被篡改
比如一個電子檔案,傳送過程中如果被篡改,則前後的md5不一樣
2、防止直接看到明文
比如使用者密碼訊息,如果資料庫中儲存的明文,則洩漏後別人就直接登入。用MD5加密後儲存,即使洩密得到的也是加密後的,無法還原加密前的密碼,但現在網上有很多MD5解密的,都是通過撞庫實現,因為密碼一般也就數字生日字母等組合,目前已知的MD5庫已經有幾百億樣本了,如果簡單密碼還是容易通過撞庫破解的,所以一般還會在使用者密碼基礎上再加上一些自定義的資訊後再MD5(俗稱加鹽)。
3、防止抵賴(數字簽名)
A寫了一個檔案,然後在第三方認證機構備案,第三方利用MD5形成摘要資訊,如果日後A抵賴不是他寫的,第三方就對檔案重新MD5然後與記錄在冊的比對。
4、急速秒傳
網盤應用,記錄第一次上傳檔案的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();
}
}
複製程式碼
2、對稱可逆加密
借用一下這張圖。對稱加密的思路非常簡單,就是含有一個稱為金鑰(密碼學中發音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(0,8));
/// <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,CryptoStreamMode.Write);
crypStream.Write(buffer,buffer.Length);
crypStream.FlushFinalBlock();
return ASCIIEncoding.UTF8.GetString(memStream.ToArray());
}
}
}
複製程式碼
3、非對稱可逆加密
非對稱加密的接收者和傳送者都持有兩個金鑰,一個是對外公開的,稱為公鑰,一個是自行保管的,稱為私鑰。非對稱加密的規則是由某人A的公鑰加密的訊息,只能由A的私鑰進行解密;由A的私鑰加密的訊息只能由A的公鑰解密。
加密模式
。
如果使用傳送方的私鑰加密,傳送方的公鑰解密,可以確保訊息是由傳送方A發出的,但拿到傳送方公鑰的都可以解密,這就是認證模式
。因為無論加密模式,認證模式都無法同時滿足加解密的三要點,然後引入了數字簽名
數字簽名
就是上面非對稱加密的認證模式基礎上做了改進,加入了像MD5的雜湊演演算法,多一條路對原始資訊進行雜湊加密,訊息仍以明文傳遞,最後比較接收到的訊息的雜湊值和接受到原始訊息雜湊值,確定訊息是否被中間篡改。但很明顯,明文傳遞,不安全,第三方可以直接檢視訊息
C#版本的RSA(Rivest Shamir Ad1eman)加解密如下所示
public class RsaEncrypt
{
/// <summary>
/// 獲取加密/解密對
/// 給你一個,是無法推算出另外一個的
/// Encrypt Decrypt
/// </summary>
/// <returns>Encrypt Decrypt</returns>
public static KeyValuePair<string,string> GetKeyPair()
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
string publicKey = RSA.ToXmlString(false);
string privateKey = RSA.ToXmlString(true);
return new KeyValuePair<string,string>(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);
}
}
複製程式碼
4、一些組合應用
1、CA證書
CA(Certification Authority)證書是由CA權威機構
(國內像阿里),我們在網際網路裡要信任一個東西,如果有個權威機構來背書,我們可能更加信任。CA證書自己也可以頒發,就看大家認不認了。
2、單邊認證https
上面驗證了證書,然後得到百度的公鑰(申請證書時提供的公鑰,包含在證書中),用此百度公鑰加密一個訊息給伺服器,伺服器如果有正確的私鑰解密返回正確的訊息,則驗證伺服器是百度。確認之後,瀏覽器則隨機提供一個加金鑰,記得用百度公鑰加密後傳輸(避免被中間擷取),然後百度伺服器得到這個新生成的加金鑰,以後雙方就通過這個加金鑰加密傳輸資料,只有瀏覽器和伺服器知道的。
3、雙邊認證
上面單邊認證只是證明瞭伺服器是那個伺服器,雙邊認證就再證明瀏覽器是那個瀏覽器,一般銀行經常會用U盾,K寶等,和單邊認證類似,將證書發給伺服器下期見~拜了個拜拜(づ ̄3 ̄)づ╭❤~
vx:xishaobb
,互動或獲取更多訊息。當然這裡也一直更新de,下期見,拜了個拜拜。