谈谈对通信加密解密的理解。
MD5 不可逆加密
MD5严格意义上不算加密算法,加密算法应该能加密也能解密。
1 通过原文得到密文 ,但是密文不能得到原文。
自定义一个MD5算法类:
namespace MyEncrypt { /// <summary> /// 不可逆加密 /// 1 防止被篡改 /// 2 防止明文存储 /// 3 防止抵赖,数字签名 /// </summary> public class MD5Encrypt { #region MD5 /// <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")); } 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(); } #endregion MD5 #region MD5摘要 /// <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(); } #endregion } }
调用方法:
class Program { static void Main(string[] args) { try { //相同的原文加密的结果是一样的 //原文差别小,密文差别大 //文件加密:(文件摘要)无论文件多大,都能产生一个32位字符串 //无论原文如何,都能得到一个32位字符串 #region MD5 { Console.WriteLine(MD5Encrypt.Encrypt("1")); Console.WriteLine(MD5Encrypt.Encrypt("1")); Console.WriteLine(MD5Encrypt.Encrypt("123456小李")); Console.WriteLine(MD5Encrypt.Encrypt("113456小李")); Console.WriteLine(MD5Encrypt.Encrypt("113456小李113456小李113456小李113456小李113456小李113456小李113456小李")); string md5Abstract1 = MD5Encrypt.AbstractFile(@"D:RuanmouAdvanced13Encrypt 0test-副本.rar"); string md5Abstract2 = MD5Encrypt.AbstractFile(@"D:RuanmouAdvanced13Encrypt 0test.rar"); } #endregion } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.Read(); } }
结果:
c4ca4238a0b923820dcc509a6f75849b
c4ca4238a0b923820dcc509a6f75849b
c1ba74636b13e1295330daebb4138df7
f070404a26ec0e2449500a85530a8474
45f05e872c35233e2f7fec2934a800f8
e25fef391c1e8758bae27e5c1b3ee958
e25fef391c1e8758bae27e5c1b3ee958
总结:
(1)相同的原文加密的结果是一样的:
(2)原文差别小,密文差别大
(3)文件加密:(文件加密通常也被称之为文件摘要)无论文件多大,都能产生一个32位字符串。文件内容相同,文件名不同,加密之后的密文是一样的,比如上面的md5Abstract2和md5Abstract1。
(4)无论原文如何,都能得到一个32位字符串。
2 MD5有什么作用呢?
(1)可以防止文件被更改
因为通过MD5加密可判断2个文件的密文是否一致,不一致的话就说明文件更改过。比如git,svn等代码管理工具都是这个原理。还有百度网盘的极速秒传也是这个原理,上传的文件会通过MD5加密来和云盘上的文件做对比,如果存在一样的 话可能直接更改一下云盘上一样文件路径,,或者直接复制一份,所以有时候就算上传的文件比较大但存在很快就上传完毕的情况。
(2)密码保存
避免加密之前的密码被别人看到,密码只让用户自己保留。这也是MD5不可逆特点的用法之一。还有一点在使用MD5加密用户密码的时候最好设置密码的规则,字母数字大小写,不要单纯的使用数字,因为有的网站是能够使用穷举法(MD5是没法破解的,只能通过穷举法暴力破解)反编译出来的,他们拥有大量的数据,对数据加密之后和你的密文进行对比,万一有一样的那就麻烦了。MD5在线解密
(3)数字签名/防止抵赖
比如一个人发表了一遍文章,他要证明这个博客是他原创的,可以通过MD5生成一个文件摘要。所以只要其它地方穿出现这个文件,通过对比文件摘要就能知道这个文章是谁的。
3 如何防止暴力破解MD5的密文呢?
(1)MD5加盐或者多次加密
加盐:就是在加密的原文后面再加上一段自定义的值然后加密,也可以将这个盐值保存到数据库中,作对比的时候再加上然后加密,看看密文是否一致。
多次加密:就是加密之后的密文接着再加密。
对称可逆加密
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256,这里只讨论DES。
原文加密以后得到密文 ,密文解密得到原文,在这个过程中通过密钥实现。
再加密和解密的时候 需要一个密钥!
对称:加密解密都是同一个密钥;
获取一个key值的类
public static class Constant { public static string DesKey = AppSettings("DesKey", "Richard2019"); private static T AppSettings<T>(string key, T defaultValue) { var v = ConfigurationManager.AppSettings[key]; return String.IsNullOrEmpty(v) ? defaultValue : (T)Convert.ChangeType(v, typeof(T)); } }
自定义的DES加密解密类:
namespace MyEncrypt { /// <summary> /// DES AES Blowfish /// 对称加密算法的优点是速度快, /// 缺点是密钥管理不方便,要求共享密钥。 /// 可逆对称加密 密钥长度8 /// </summary> public class DesEncrypt { //Constant.DesKey.Substring(0, 8)这个方法是获取了设置的密钥值,并且转换成二进制 private static byte[] _rgbKey = ASCIIEncoding.ASCII.GetBytes(Constant.DesKey.Substring(0, 8)); private static byte[] _rgbIV = ASCIIEncoding.ASCII.GetBytes(Constant.DesKey.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, _rgbIV), CryptoStreamMode.Write); crypStream.Write(buffer, 0, buffer.Length); crypStream.FlushFinalBlock(); return ASCIIEncoding.UTF8.GetString(memStream.ToArray()); } } } }
调用:
class Program { static void Main(string[] args) { try { //Des: //对称可逆加密 //原文---加密以后--密文 密文---解密--原文 //再加密和解密的时候 需要一个密钥! //对称:加密解密都是同一个密钥; //速度快 //加密算法也是公开! //安全性 传输加密解密Key的时候存在的安全性问题 #region Des { string desEn = DesEncrypt.Encrypt("Richard老师"); string desDe = DesEncrypt.Decrypt(desEn); string desEn1 = DesEncrypt.Encrypt("张三李四"); string desDe1 = DesEncrypt.Decrypt(desEn1); } #endregion } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.Read(); } }
安全问题:因为加密算法是公开的,并且加密解密的密钥是同一个,所以存在key被暴露后导致的风险问题,但是速度快。
非对称可逆加密
比如RSA 就是非对称可逆加密算法。加密和解密都有自己的密钥,他们是一对,但是不能相互推导出来。
公钥:把Key公开
私钥:不公开
要注意的是加密的Key可以是公钥也可以是私钥,解密的Key可以是私钥也可以是公钥。
首先要明确一点,那就是RSA的公钥和私钥都是它本身封装的方法生成的,是一对。
场景1:加密的key不公开,解密的key公开。
如果张三对原文进行加密,并且公开解密的Key,此时加密的Key是私钥,解密的Key是公钥,比如此时我接收到加密之后的密文,并且根据公开的解密的Key 解密成功了,那就能够确定一件事:我接收到的信息一定是张三发出来的。
场景2:加密的key公开,解密的key不公开。
比如说很多人都有加密的key,他们都把自己的信息加密成了密文,发送了出去,希望和自己一伙的人接收到真正的信息,此时只有我有解密的Key,我根据key解密出来原文得到真正的信息,别人没有解密的key,所以就算得到密文也没有用。
公钥保证数据安全,私钥对原文加密生成密文,传输过程中的密文也被称之为数字签名。
下面是定义的一个RSA加密解密类,需要注意的是在c#中它只支持使用公钥进行加密,使用私钥进行解密。除非引用第三方dll
下面是使用公钥加密私钥解密:
public class RsaEncrypt { /// <summary> /// 获取加密/解密对 /// 给你一个,是无法推算出另外一个的 /// /// Encrypt Decrypt /// </summary> /// <returns>Encrypt Decrypt</returns> public static KeyValuePair<string, string> GetKeyPair() { RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); // RSA.ToXmlString(bool) : 创建并返回包含当前 RSA 对象的密钥的 XML 字符串。true 表示同时包含 RSA 公钥和私钥;false 表示仅包含公钥。 string publicKey = RSA.ToXmlString(false); //在C#中只实现了 公钥加密 私钥解密 如果有需要其他场景的,需要引入一个第三方dll 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); } }
调用:
#region Rsa { KeyValuePair<string, string> encryptDecrypt = RsaEncrypt.GetKeyPair(); string rsaEn1 = RsaEncrypt.Encrypt("net", encryptDecrypt.Key); string rsaDe1 = RsaEncrypt.Decrypt(rsaEn1, encryptDecrypt.Value); } #endregion
SSL
1 为什么网络是不安全的?
计算机世界是基于网络的,根据目前网络的结构和实现,数据包在世界各地的路由器之间游荡,任何人都可以获得你的发送的数据包,从而获得你发送的数据。局域网内就更方便了,只要你开个Sniffer在那里监听,别人QQ聊天的信息一览无余啊~为什呢?我来简单解释一下计算机网络是如何传输数据的。
现实生活中,如果你请快递帮你寄东西,一般情况下,快递会把东西送到目的地,而不是其他地方。路由器就像是快递,在Internet上负责送数据。但和真实的快递不同的是,路由器会把你要发送的信息广播给离目的地更近的路由器,可能是一个路由器,也可能是多个路由器 。这样你的信息就变成多份的了。复制虚拟的信息不值钱那~现实生活中的快递可不能复制你要寄的东西。一般情况下,只有一份数据会被目的计算机接收到,其他的拷贝在网络上游荡一段时间以后就被抛弃了。但是,这给黑客们有了很多可乘之机。他们在网络上监听很多垃圾信息,过滤掉没用的,留下他们感兴趣的,然后就可以偷窥别人隐私了。
正是因为网络有这样的问题,人们就发明了很多加密通信的手段,来保证自己的通信的内容不会被泄露。SSL和数字证书就是用来干这个的。
2 CA和SSL证书区别
证书颁发机构英文简称为CA,负责签发、作废和保存证书,CA签发的证书称为CA证书,CA证书的本质是利用SSL/TLS协议保护传输数据的安全,因此又称为SSL证书。CA机构除了可以颁发SSL证书之外,还可以颁发其他数字证书,比如:代码签名证书和电子邮件证书等等。
流程:网站向CA机构申请购买证书,网站提交相关信息,将用户的基本信息,先使用md5生成一个摘要,CA使用加密key,加密用户的基本信息,将密文和用户信息放入颁发的证书中,将证书安装到网站上,使用浏览器访问https站点,浏览器会自动获取证书,并使用浏览器预置的CA机构的解密证书进行解密,并与证书中的明文进行比对,如果一样,则验证通过
单边认证HTTPS
整体的流程如下图:
首先客户端浏览这个地址的时候需要看服务器有没有证书,此时服务器会返回一个证书(证书包含了公司相关信息以及一个公开的加密key)给客户端, 客户端验证证书,得到签名和公钥(加密),因为客户端内置了一个根证书,这个证书里有CA机构解密key(下载的这些浏览器都会内置一些根证书),验证证书没问题之后就要验证是不是我指定的服务器发来的信息,而不是其他钓鱼网站发来的信息,所以需要服务器发来的一个公开的加密key(非对称可逆加密)进行加密,向服务器发送一段密文,服务器根据解密key进行解析,解开之后的明文发回给客户端,如果一样,那就证明了这个服务器是指定的服务器。有一点需要知道,非对称可逆加密其实性能比较差,所以客户端这边使用服务器传来的公开的加密key进行加密(此时使用对称可逆加密,性能优)。之后是通过对称可逆加密算法来加密解析。
第二次理解:
首先浏览器是要访问服务器的,第一次请求服务器的时候问服务器要证书,因为浏览器不确定访问的服务器是不是自己要访问的,服务器返回证书信息(公司相关信息以及一个公开的加密key),客户端需要进行验证,就是通过浏览器内置的解密证书中的解密key进行验证,验证没问题的话只能确定返还的这个证书是没有问题的,但是我不确定到底是不是我想要访问的那个服务器站点发来的,万一是钓鱼网站怎么办,所以还需要验证这个服务器有没有问题。因为服务器返还的证书中存在加密key,所以浏览器会根据这个加密key来加密一段信息,发送到服务器,服务器根据解密key进行解密,将解密后的明文返回给浏览器,浏览器将接收到的明文和自己加密的信息进行对比,发现是一样的,所以这个服务器就是自己想找的那个服务器,接下来2者就可以进行信息交互了。这个过程使用的是非对称的加密算法,性能差。所以后面的信息交互是使用对称加密算法。此时浏览器会自己生成一个key,通过服务器返还的那个加密key对其加密,发送到服务器,服务器解密之后得到浏览器生成的这个key值,之后的信息传输就是使用这个key值进行对称加密算法。
双边认证
在真正涉及到敏感信息的时候还需要进行双边认证,比如和钱相关的信息,必须U盾,还比如登录游戏的时候的令牌(这些都要额外在浏览器安装证书,不是浏览器内置的):
双向认证其实和单边认证没什么太大的差异,在客户端证明了这个服务器就是自己指定的服务器之后就要给服务器发证书,服务器这边也要做验证,服务器验证没问题之后才会真正建立连接。
双边认证第二次理解(这时候浏览器这边需要额外安装相关证书了):
首先浏览器是要访问服务器的,第一次请求服务器的时候问服务器要证书,因为浏览器不确定访问的服务器是不是自己要访问的,服务器返回证书信息(公司相关信息以及一个公开的加密key),客户端需要进行验证,就是通过浏览器内置的解密证书中的解密key进行验证,验证没问题的话只能确定返还的这个证书是没有问题的,但是我不确定到底是不是我想要访问的那个服务器站点发来的,万一是钓鱼网站怎么办,所以还需要验证这个服务器有没有问题。因为服务器返还的证书中存在加密key,所以浏览器会根据这个加密key来加密一段信息,发送到服务器,服务器根据解密key进行解密,将解密后的明文返回给浏览器,浏览器将接收到的明文和自己加密的信息进行对比,发现是一样的,所以这个服务器就是自己想找的那个服务器。这时候服务器还要确认浏览器,所以浏览器这边将证书(包含自己的加密key)发到服务器,服务器将加密的一段信息发送到浏览器,浏览器进行解密,解密后的明文再次发给服务器,服务器对比发现浏览器没有问题,2者就可以达成通信要求,那服务器将自己生成一个key,经过客户端证书中的加密key加密之后发到浏览器,浏览器解析之后得到通信使用的对称加密算法的key,使用这个key通过对称加密算法来进行信息交互。