加密和解密算法

数字签名、信息加密是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、oauth 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的签名加密算法来达到业务目标。这里简单的介绍几种常见的签名加密算法和一些典型场景下的应用。

加密和解密算法

消息在接收方和发送方进行安全传递,一般要满足下面三个要点:

  1. 消息的发送方能够确定消息只有预期的接收方可以解密(不保证第三方无法获得,但保证第三方无法解密)。
  2. 消息的接收方可以确定消息是由谁发送的(消息的接收方可以确定消息的发送方)。
  3. 消息的接收方可以确定消息在途中没有被篡改过(必须确认消息的完整性)。
    数据加密的基本过程,就是对原来为明文的文件或数据按某种算法 进行处理,使其成为不可读的一段代码,通常称为 “密文”。通过这样的途径,来达到保护数据不被非法人窃取、阅读的目的。加密的逆过程为解密,即将该编码信息转化为其原来数据的过程。
    加密算法分对称加密和非对称加密,其中对称加密算法的加密与解密密钥相同,非对称加密算法的加密密钥与解密密钥不同,此外,还有一类不需要密钥的散列算法。

常见的散列算法主要有 SHA-1、MD5 等,常见的对称加密算法主要有 DES、3DES、AES 等,常见的非对称算法主要有 RSA、DSA 等。

散列算法

散列算法为不可逆加密算法主要用于验证,防止信息被修。具体用途如:文件校验、数字签名、鉴权协议。常用的有:

  • MD5:MD5是一种不可逆的加密算法,尚没有能够逆运算的程序被开发出来,它对应任何字符串都可以加密成一段唯一的固定长度的代码。
  • SHA1:是由NISTNSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1设计时基于和MD4相同原理,并且模仿了该算法。SHA-1是由美国标准技术局(NIST)颁布的国家标准,是一种应用最为广泛的Hash函数算法,也是目前最先进的加密技术,被政府部门和私营业主用来处理敏感的信息。而SHA-1基于MD5,MD5又基于MD4。
  • HMAC:是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。也就是说HMAC是需要一个密钥的。所以,HMAC_SHA1也是需要一个密钥的,而SHA1不需要。

MD5(单向散列算法)目前是最牢靠的加密算法之一,全称是Message-Digest Algorithm 5(信息-摘要算法),经MD2、MD3和MD4发展而来。输入任意长度的信息,经过MD5处理,输出为128位的信息,其用途为:
1、防止被篡改
如果提供文件下载,为了防止不法分子在安装程序中添加木马,我可以在网站上公布由安装文件得到的MD5输出结果。SVN在检测文件是否在CheckOut后被修改过,也是用到了MD5.
2、防止直接看到明文
在UNIX系统中用户的密码就是以MD5(或其它类似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被破解的难度。
3、防止抵赖(数字签名)
需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这就是所谓的“数字签名”。

对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。C#可以通过Cryptography库快速实现加密与解密,如下:

using System.IO;
using System.Security.Cryptography;
using System.Text;

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
    }
}

普遍认为MD5是很安全,因为暴力破解的时间是一般人无法接受的。实际上如果把用户的密码MD5处理后再存储到数据库,其实是很不安全的。因为用户的密码是比较短的,而且很多用户的密码都使用生日,手机号码,身份证号码,电话号码等等。或者使用常用的一些吉利的数字,或者某个英文单词。如果我把常用的密码先MD5处理,把数据存储起来,然后再跟你的MD5结果匹配,这时我就有可能得到明文。

对称性加密算法

常用的对称性加密算法有:AES、DES、3DES,通常用来对敏感数据等信息进行加密
对称加密的思路非常简单,就是含有一个称为密钥的东西,在消息发送前使用密钥对消息进行加密,在对方收到消息之后,使用相同的密钥进行解密。根据密钥来产生加密后的消息(密文)的这一加工过程,由加密算法来完成,加密算法通常是公开的,以下:

  • DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。
  • 3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。
  • AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别高;AES是一个使用128为分组块的分组加密算法,分组块和128、192或256位的密钥一起作为输入,对4×4的字节数组上进行操作。众所周之AES是种十分高效的算法,尤其在8位架构中,这源于它面向字节的设计。AES 适用于8位的小型单片机或者普通的32位微处理器,并且适合用专门的硬件实现,硬件实现能够使其吞吐量(每秒可以到达的加密/解密bit数)达到十亿量级。同样,其也适用于RFID系统。

常用的DES算法实现如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace MyEncrypt
{
    /// <summary>
    /// DES AES Blowfish
    ///  对称加密算法的优点是速度快,
    ///  缺点是密钥管理不方便,要求共享密钥。
    /// 可逆对称加密  密钥长度8
    /// </summary>
    public class DesEncrypt
    {
        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());
            }
        }
    }
}

但对称加密存在这样两个问题:

  1. 虽然可以通过密钥来保证消息安全地进行传递,但是如何确保密钥安全地进行传递?因为发送者和接收者总有一次初始的通信,用来传递密钥,此时的安全如何保证?
  2. 接收者虽然可以根据密钥来解密消息,但因为存在上面的问题,消息有可能是由第三方(非法获得密钥)发来的,而接收方无法辨别。
    为了解决上面两个问题,就需要介绍一下非对称加密。

非对称性加密算法

非对称性算法有:RSA、DSA、ECC.

  • RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的。RSA在国外早已进入实用阶段,已研制出多种高速的RSA的专用芯片。
  • DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准),严格来说不算加密算法。
  • ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。ECC和RSA相比,具有多方面的绝对优势,主要有:抗攻击性强。相同的密钥长度,其抗攻击性要强很多倍。计算量小,处理速度快。ECC总的速度比RSA、DSA要快得多。存储空间占用小。ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多,意味着它所占的存贮空间要小得多。这对于加密算法在IC卡上的应用具有特别重要的意义。带宽要求低。当对长消息进行加解密时,三类密码系统有相同的带宽要求,但应用于短消息时ECC带宽要求却低得多。带宽要求低使ECC在无线网络领域具有广泛的应用前景。

下面是RSA加密算法的示例:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace MyEncrypt
{
    /// <summary>
    /// RSA ECC
    /// 可逆非对称加密
    /// 非对称加密算法的优点是密钥管理很方便,缺点是速度慢。
    /// </summary>
    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);
        }
    }
}

非对称加密的接收者和发送者都持有两个密钥,一个是对外公开的,称为公钥,一个是自行保管的,称为私钥。非对称加密的规则是由某人A的公钥加密的消息,只能由A的私钥进行解密;由A的私钥加密的消息只能由A的公钥解密。此时我们可以得出接收方、发送方有两个公钥两个私钥一共四个密钥,我们先看看两种简单的方式,这两种方式都是只使用两个密钥。
第一种模式只使用接收方的公钥和私钥,称为加密模式。

加密模式

在加密模式中,由消息的接收方发布公钥,持有私钥。比如发送方要发送消息“hello,jimmy”到接收方,它的步骤是:

  1. 发送方使用接收者的公钥进行加密消息,然后发送。
  2. 接收方使用自己的私钥对消息进行解密。

在这种模式下,如果第三方截获了发送者发出的消息,因为他没有接收者的私钥,所以这个消息对他来说毫无意义。可见,它能够满足本文最开始提出的消息安全传递的要点一:消息的发送方能够确定消息只有预期的接收方可以解密(不保证第三方无法获得,但保证第三方无法解密)。
除此以外,因为接收方的公钥是公开的,任何人都可以使用这个公钥来加密消息并发往接收者,而接收者无法对消息进行判别,无法知道是由谁发送来的。所以,它不满足我们开始提出的消息安全传递的要点二:消息的接收方可以确定消息是由谁发送的(消息的接收方可以确定消息的发送方)。
这个问题可以在下面的认证模式中得到解决。

认证模式

在认证模式中,由消息的发送方发布公钥,持有私钥。比如发送者要发送消息“Welcome to Tracefact.net”到接收者,它的步骤是:

  1. 发送者使用自己的私钥对消息进行加密,然后发送。
  2. 接收者使用发送者的公钥对消息进行解密。

在这种模式下,假如发送方叫做Ken,接收方叫做Matthew,因为Matthew只能使用Ken的公钥对消息进行解密,而无法使用Molly、Sandy或者任何其他人公钥对消息进行解密,所以他一定能够确定消息是由Ken发送来的。因此,这个模式满足了前面提出的消息安全传递的要点二。
与此同时,因为Ken的公钥是公开的,任何截获了该消息的第三方都能够使用Ken的公钥来对消息进行解密,换言之,消息现在是不安全的。因此,与加密模式正好相反,它无法满足前面提出的消息安全传递的要点一。

而不管是采用加密模式还是认证模式,都没有解决加密解密中的要点三:接收方必须能够确认消息没有被改动过。为了解决这个问题,又引入了数字签名。

数字签名(Digital Signature)

我们先来看看现实生活中的签名是如何实现的。比如为信用卡账单签名,商家会打印一张消费单子给你,你看过以后觉得没有问题,于是在这张纸上签上自己的大名,表示你承认了这笔消费,并同意商家从你的信用卡账户扣钱。而商家可以对比你的签名和信用卡背后的签名是否一致来验证你是否冒用别人的信用卡(事实上很多商家不看的哦)。这个流程是基于一个假设的: 只有你自己能重现你的签名。虽然我们不能每次都签的一摸一样,但是通过笔迹鉴定,我们可以确定这个签名是否出自你手。分析一下,签名具有哪些特点呢?

  1. 不可伪造 - 通过笔记鉴定来保证。
  2. 不可移植,复制 - 复印,剪贴的签名当然无效咯!
  3. 不可否认 – 因为不可伪造,不可移植,不可复制,所以不可否认。
    相似的,在虚拟世界里,我们有数字签名来帮助证明某个文档是你创建的,或者是你认可的。 数字签名所用的技术是散列和非对称加密。数字签名的假设是: 只有你自己有你的私钥。根据前面对散列的介绍,我们先为你要签名的信息生成一个Hash字串,Hash1,然后用你的私钥加密得到Encrypted(Hash1),这就是你对这个文档的数字签名。当别人需要验证某个文档是否是你签名的时候,只需要用你的公钥解密你的签名得到Hash1,并和该文档计算出来的Hash2对比,查看是否一致。如果一致则说明你确实对该文档签过名,否则就是没有。下面来分析一下,数字签名是如何保证上面所讲的签名的特点的。
  4. 不可伪造
    因为只有你有你自己的私钥,所以任何其他人都无法产生用你的私钥加密过的Hash1。
  5. 不可移植,复制
    你对文档A的签名不可能对文档B也有效,因为你对文档B的签名必然和对A的签名不一样,这是由Hash的唯一性保证的。拿你对A的签名去验证B是不可能通过的。
  6. 不可否认
    因为不可伪造,不可移植,不可复制,所以不可否认。
    仔细想想数字签名和现实生活中的签名真的蛮像的,逻辑上是一样的。或许你在想,为什么要对Hash加密呢?我直接对文档用我的私钥加密不就完了嘛?对啊,效果是一样的,但是效率不一样哦~别忘了非对称算法是很慢的,加密一个100M的文件要算半天呢!
    这里要顺便提一下消息认证码( Message Authentication Code)。 它和数字签名很相似,只不过它是用对称加密的而数字签名用的是非对称加密。
    在现实生活中,各种加密手段往往是配合使用以达到最好的效果和效率。比如我将要介绍的SSL和数字证书,就是混合了各种的加密手段。

数字证书

上面讲了这么多都是前戏,现在该到主题了。前面提到的认证(Authentication)的时候说,现实生活中可以用身份证和护照来证明身份, 那么在虚拟世界里,数字证书就是身份证。和现实生活不同的是,并不是每个上网的用户都有数字证书的,往往只有当一个人需要证明自己的身份的时候才需要用到数字证书。那么什么时候需要证明自己的身份呢?普通用户一般是不需要的,网站并不关心是谁访问了网站,现在的网站只关心流量啊~反过来,网站就需要证明自己的身份了。比如你想要提交信用卡信息给预定航班的网站,那么你如何确定你正在访问的网站就是你所想要访问的那个呢?现在 钓鱼网站很多的。比如你想访问的是“www.ctrip.com”,但其实你访问的是“www.otrip.com”,所以在提交自己的信息之前你需要验证一下网站的身份,要求网站出示数字证书。一般正常的网站都会主动出示自己的数字证书。由于证书在网页浏览中最为常见,所以我下面举的例子都是基于浏览器的。

数字证书的构成

我们的身份证是由公安机关颁发的,并加有很多防伪技术,不能伪造(或者说很难)。同样的,数字证书也有专门的发证机关(Certificate Authority,简称CA,其实是一些商业公司啦)。比较常见的发证机关是 VeriSign。数字证书的发证机关会对自己发放的证书加上自己的数字签名,以保证证书不能被伪造。那数字证书到底包含了些什么呢?

  • 持有者姓名(Common Name)
  • 发证机关(Issuer)
  • 有效日期(Validity)
  • 证书持有人的公钥(Subject’s Public Key Info)
  • 扩展信息 (Extension)
  • 用发证机关对该证书的数字签名(Certificate Signature)

如何验证数字证书?

好了,现在我们有了虚拟世界的身份证了,那如何使用呢?和现实生活中检查身份证一样,包含三个步骤:

  1. 检查身份证防伪标记
    数字证书的防伪标记就是发证机关的私钥加密的那段内容。如何验证?首先我们是默认拥有发证机关的公钥的。如果是浏览器的话,常见的发证机关的公钥是内置的。
    虽然Firefox内置的是数字证书,但是有数字证书就有公钥,所以是一样的。当浏览器拿到一个数字证书,先看发证机关,然后找到相应的发证机关的证书,获得发证机关的公钥,用此公钥解密被加密的MD5,这样就获得了此证书的MD5值,我们称它为Hash1。然后浏览器用MD5算法对此证书重新计算一遍MD5,获得Hash2。然后比较Hash1和Hash2是否相等。如果相等就证明这张证书是由发证机关颁发的,并且没有被篡改过。回过头去看看上面讲Hash和MD5的部分,你应该能想明白为什么的。
  2. 核对相貌
    在现实生活中,你的身份证只有一张,你应该好好保管不被别人拿到。但难免钱包丢了,身份证跟着遭殃。所以我们在验证完身份证的真假之后我们要验证持证的人,和身份证上所声明的那个人是不是同一个,我们往往通过比较相貌来辨别。那在虚拟世界又是怎样的呢?你应该已经发现,任何人都可以拥有你的证书就像我们装的Firefox就有很多发证机关的证书。所以核对持有证书人的身份就很重要了。这就要依赖证书里面包含的公钥了。此公钥是这张证书所有者的公钥(注意,我这里指的是所有者,而不是持有者!),我们用此公钥加密一段信息发送给证书的持有者,如果持有者能发送回(可以是被私钥加密,也可以是明文,没有关系)被加密的这段信息的话就证明该持有者拥有该证书对应的私钥,也就是说,该持有者就是该证书的所有者。
  3. 核对姓名
    最后一步,也是最重要的一步。看清楚了,站在你面前的人的名字和登记在册的名字一样。举个例子,我拿着护照去机场登机,护照和人都没有问题,问题是我根本没有买机票。如果机场工作人员只核对了前面两步的话,我就可以登机了,岂不是很荒谬?同样的,在虚拟世界,比如那个“ctrip”的例子,你拿到了一个证书,并且验证没有问题,但是证书上的Common Name明明写的是“otrip”,你还继续吗?
    如果这三个步骤都没有问题,你就可以确信正在和你通信的对方是可以信任的,是你想要联系的那个人。

数字证书的级联(Certificate Chain)

根据上述讨论,我们可以知道,所有数字证书都是基于另外一张默认为可信任(浏览器内置)的数字证书的。也就是说,我们必须用一张已知合法的数字证书去验证另外一张未知的数字证书。第二节提到的发证机关的数字证书就是默认为可信任的。事实上,发证机关的证书是自己签发给自己的,验证没有意义。因为这些证书是人工配置在我们电脑上的,所以默认为安全的。这些证书称为“根证书”。
由于申请证书的人数众多,发证机关忙不过来,需要一些代理来帮忙签发证书,有可能代理也需要代理来帮忙。这样就产生了证书的层级关系,如下图所示:

级联的数字证书

这里的“www.paypal.com”是由二级代理“VeriSign Class 3 Extended Validation SSL SGC CA”签发的,而二级代理的证书又是由一级代理“VeriSign Class 3 Public Primary Certification Authority – G5”签发的,而一级代理的证书是由根证书机关“Buildin Object Token: Verisign Class 3 Public Primary Certification Authority”签发的。不必太关注这里的名字,名字而已,重要的是他们各自的位置。在验证这张证书的时候需要从下往上递归验证。先验证用户证书(最下面的证书,这里就是“www.paypal.com”),如果这样证书在浏览器的可信任列表里面那么验证到此结束,如果不是的话就要检查证书的防伪标记,这需要用到二级代理的证书,同样的,如果二级代理的证书在可信任列表里面,那么直接使用,否则就要检查二级代理证书的防伪标记,这需要用到一级代理的证书……直到根证书为止,如果根证书不在可信任列表里面,那么这张证书就没法验证了。这个过程的简单流程图是这样的:

级联证书的验证流程

需要注意的是,这是一个递归过程,所以这里的返回只是返回到上层递归。对于我们这个例子中的级联证书的验证可能是这样的:

级联证书验证流程示例

这个过程看起来没有什么问题。但是仔细一想,发现有一个大问题。任何一个拥有合法证书的人都可以给别人签发证书了,不就是在这个继承关系上面多加一层吗?黄粱大梦!事情没那么简单啦~能够签发证书的叫CA,不管是否是代理,它都是CA,只有CA的证书才能拥有下级,那如何判断一张证书是否是CA呢?还记得我们在第一节讲过的证书中包含的扩展信息吗?这里可以放很多东西,包括这张证书的合法用途
关于数字证书,我还有几点想要提一下:

  1. 数字证书本身不加密,加密的是数字证书的hash。数字证书加密了就很麻烦了,如何获知发证机关呢?不知道发证机关就不知道解密的公钥啊!还有,明文的数字证书可以显示数字证书的信息,即使不能验证数字证书的真伪,但是给了人为判断一个机会。
  2. 数字证书中的公钥可以是自己指定的,也可以是发证机关生成的。不同的发证机关可能有不同的要求。
  3. 申请证书的过程是安全的。如果申请证书的过程都不安全的话,后面的一切都免谈了。现实生活中,申请数字证书往往要求邮寄,或者电话,传真,甚至当面申请的。

SSL的基本原理

现在回到我们最原始的问题,由于Internet的架构问题,信息在网络上传输是很容易被别人获取的,那如何建立一个安全的传输网络呢?前面我们讨论了很多保证信息安全的技术,而SSL就是建立在这些技术的基础上的一套协议,用来保证通信的安全。SSL全称是 Secure Sockets Layer,它是一种间于传输层(比如TCP/IP)和应用层(比如HTTP)的协议。具体的SSL协议很复杂,我这里只讲一个大概。
最简单的方法来保证通信安全是用非对称加密。我们前面讲过数字证书的认证,如果双方都认证了对方的数字证书,那么每次传输信息的时候都用对方的公钥加密,这样就只有对方能解密,从而保证了信息的安全。但是对于日常应用(比如网页浏览)有两个问题:

  1. 非对称加密速度缓慢,消耗资源
    如果客户端和服务器之间传输文件用非对称加密的话,速度一定慢的忍无可忍。
  2. 不可能要求每个用户都去申请数字证书
    申请数字证书是一个相当麻烦的过程,要求每个上网的用户都拥有证书是不可能的事情。
    SSL通过“握手协议”和“传输协议”来解决上述问题。握手协议是基于非对称加密的,而传输协议是基于对称加密的。根据不同的应用,SSL对证书的要求也是不一样的,可以是单方认证(比如HTTP, FTP),也可以是双方认证(比如网上银行)。通常情况下,服务器端的证书是一定要具备的,客户端的证书不是必须的。下面两张图片显示了SSL握手的过程。

SSL握手,单方服务器认证

SSL握手,双方认证

握手协议可以看成是客户端和服务器协商的一个过程,结果就是一个对称密钥,然后就进入了传输协议的部分。也许,你在想,“现在还不简单吗?用这个对称密钥加密传输数据呗!”。否,没那么简单。先来看一下结果,等会儿再解释原因。在通信双方协商出一个对称密钥以后,他们用这个密钥来加密传输的数据。同时为每个消息生成时间戳,用此密钥为消息和相应的时间戳生成消息认证码(MAC)。也就是说,每次发送的内容包括 Encrypt(message) + MAC(message + timestamp)
这么做有几个好处:

  1. 防止消息的篡改
    所谓消息篡改就是有第三者插在通信双方之间,篡改往来的消息。由于消息是加密的,第三者不能获得消息的内容,但是他可以闭着眼睛瞎改。如果没有MAC的话,接受者就无法判断此消息是否被篡改过。
  2. 防止消息重放
    消息的重放是只第三者记录下通信双方的每一次发送的消息,虽然他不能获得消息的内容。但是它可以通过重新发送客户端或者服务端的信息来把自己装成是客户端或者服务端。如果在MAC里面加上了时间戳,消息接收方验证时间戳就可以阻止消息的重放攻击。
    SSL的基本思想是用非对称加密来建立链接(握手阶段),用对称加密来传输数据(传输阶段)。这样既保证了密钥分发的安全,也保证了通信的效率。
    通过上面对SSL的分析,我们可以看到,SSL并不能阻止别人获得你传输的数据,但是由于你传输的数据都是加密过的,别人拿到了毫无用处,一样可以保护信息的安全。还有一点需要强调一下,SSL并不依赖于TCP,它可以建立在任何可靠的传输层协议(比如TCP)之上。也就是说SSL是不能建立在UDP之上的。这是显然的,如果传输都不可靠,偶尔丢两个包或者包的顺序换一换的话,怎么保证安全呢?

参考:
加密解密算法介绍
常见的七种加密算法及实现
SSL 与 数字证书 的基本概念和工作原理

posted @ 2021-01-07 23:00  Jamest  阅读(1676)  评论(0编辑  收藏  举报