zoukankan      html  css  js  c++  java
  • .NET进阶篇04-Serialize序列化、加密解密

    知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂
    这篇很轻松,没有什么费脑子的,所以解析较少,代码较多,为数不多的拿来即用篇
    整个章节分布请移步 .NET开篇总括

    内容目录

    一、概述二、序列化1、二进制文件2、XML3、JSON三、加解密1、不可逆加密MD52、对称可逆加密3、非对称可逆加密4、一些组合应用1、CA证书2、单边认证https3、双边认证

    一、概述

    序列化是把一个内存中的对象的信息转化成一个可以持久化保存(二进制数据)的形式,以便于保存或传输,序列化的主要作用是不同平台之间进行通信,常用的有序列化有json、xml、文件等,反序列化是将进制数据还原为对象,内存中的对象稍纵即逝,序列化反序列化就是为了保持对象的持久化。就像用DV录像,用播放器播放一样。

    加密是通过对消息进行编码,建立一种安全的交流方式,使得只有你和你所期望的接收者能够理解。

    二、序列化

    1、二进制文件

    内置的BinaryFormatter二进制序列化器用于将对象序列化和反序列化二进制文件。要引用System.Runtime.Serialization.Formatters.Binary。
    假设我们有以下对象

    [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帮助类

     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, 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文档的完全控制。

    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
    }

    三、加解密

    字面意思,加密解密,我们经常需要对不想要别人轻松看见的东西加密,之所以说是不能轻松看见,因为安全都是相对的,没有绝对的安全,我们加密只是提高别人破解的难度,劝退大多数人。
    加密是通过对消息进行编码,建立一种安全的交流方式,使得只有你和你所期望的接收者能够理解。
    那么怎么样才能叫安全呢?消息在接收方和发送方进行安全传递,一般要满足下面三个要点:

    1. 消息的发送方能够确定消息只有预期的接收方可以解密(不保证第三方无法获得,但保证第三方无法解密)。
    2. 消息的接收方可以确定消息是由谁发送的(消息的接收方可以确定消息的发送方)。
    3. 消息的接收方可以确定消息在途中没有被篡改过(必须确认消息的完整性)。确保消息由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(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());
                }
            }
        }

    3、非对称可逆加密

    非对称加密的接收者和发送者都持有两个密钥,一个是对外公开的,称为公钥,一个是自行保管的,称为私钥。非对称加密的规则是由某人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);
            }
        }

    4、一些组合应用

    1、CA证书

    CA(Certification Authority)证书是由CA权威机构(国内像阿里),我们在互联网里要信任一个东西,如果有个权威机构来背书,我们可能更加信任。CA证书自己也可以颁发,就看大家认不认了。

    像上图所示。我们网站需要去CA机构申请一个证书,CA机构根据我们提供的网站一些信息,然后CA机构对这些信息用CA机构的私钥加密,形成CA证书(包括加密后的基本信息和数字签名)安装到IIS等服务器上,浏览器内置了CA解密钥,浏览器访问服务器时,服务器把证书给浏览器,,通过发证机关找到次机关的解密钥(浏览器也内置了发证机关的CA证书(根证书,包含CA机构和解密公钥)),然后浏览器通过解密钥就获得图中百度公司的MD5信息,通过对比服务器端传递过来的基本信息生成MD5后比较解密了数字证书后的MD5一致性,确保证书没问题,没有被篡改。

    2、单边认证https

    上面验证了证书,然后得到百度的公钥(申请证书时提供的公钥,包含在证书中),用此百度公钥加密一个消息给服务器,服务器如果有正确的私钥解密返回正确的消息,则验证服务器是百度。确认之后,浏览器则随机提供一个加密钥,记得用百度公钥加密后传输(避免被中间截取),然后百度服务器得到这个新生成的加密钥,以后双方就通过这个加密钥加密传输数据,只有浏览器和服务器知道的。

    3、双边认证

    上面单边认证只是证明了服务器是那个服务器,双边认证就再证明浏览器是那个浏览器,一般银行经常会用U盾,K宝等,和单边认证类似,将证书发给服务器

     

    如果手机在手边,也可以关注下vx:xishaobb,互动或获取更多消息。当然这里也一直更新de,下期见,拜了个拜拜。

  • 相关阅读:
    HTML的基本知识
    java script后续
    java script
    CSS
    DAY 33 进程理论与进程方法
    DAY 32 UDP协议、Socketserver模块,并发编程基础
    DAY 30 网络编程基础
    DAY 25多态、反射、异常处理
    DAY 24继承与组合
    DAY 23 面向对象(二)
  • 原文地址:https://www.cnblogs.com/xibei/p/11745106.html
Copyright © 2011-2022 走看看