信息加密与身份认证

网上时不时会爆出某某大公司的产品用明文发送数据,啥啥啥。这种错误虽然很低级,但可以理解。技术再牛的大公司也有很多新人工程师,这些工程师在设计实现的时候,对安全性的考虑未必是能力问题,而是经验上有欠缺。

作为互联网工程师,要时刻意识到:网络传输是不靠谱的

数据在网络中来往,每个“节点”表面上都有自己的标示(通常是IP地址),但这个标示并不那么可信,有很多办法可以让一个节点“伪装”成另一个节点,从而替这个被伪装的节点发送消息或者接受消息。(这个话题可另开贴研究

更何况,对用户产品的Server来说,很多时候你也并不知道Client是谁,只要有人发送了符合协议的请求,就会返回相应结果。这就是很多“网络抓取”技术能够实现的原因。

因此,对Server工程师来说,要时刻记住,你发送的每个字节都可能被你不希望的人收到,由此就引申出各种安全性的问题。

对称加密算法

最容易想到的安全性办法,就是对要发送的内容进行“加密”,并且要让目标接受者能够顺利“解密”。

比如,Server工程师甲和Client工程师乙口头约定,彼此用Base64的方式对内容进行加密/解密处理。这样,不管是Server还是Client,在发送数据之前先对数据进行 Base64-encode 处理,把内容原文变成了一串人眼不可识别的编码,然后发送。在接收数据的时候,对收到的编码串再进行 Base64-decode ,就得到了原文。这样一来,如果有人试图通过抓包等方式偷听S/C之间的对话,那么他只能看到一大堆看似随机的字符串,并不能知道真实内容。

但是Base64过于简单,有经验的黑客看到字符串以后,会先尝试用Base64-decode的方式去解码,很容易被破解。于是,甲和乙又做了更复杂的约定:在Base64-encode之后,再对子串进行反序处理,并且按5取模,相同取模位上的两个字母对调,再啥啥啥。处理复杂了许多,不那么容易被破解了。

本质上,这种方式,相当于甲乙双方约定了既定的加密解密算法。这个算法必须是保密的,只有甲乙两个人知道。一旦被第三方获取,那么这个方法就完全不起作用了。

但是,在真实世界中,要做到“算法保密”是非常困难的。比如有一天,甲或者乙离职了,那么为了保证安全性,必须要重新修改算法,这个代价实在太大了。

有另一种更好的方式来解决这个问题,就是通过 秘钥 + 算法 的方式,来对内容进行加密。以上例来说,把算法当中“按5取模”这一步中的5,改为参数N,并且可以自由设置。这样,加密、解密过程可以表述为:

加密过程:密文 <= EncodeFunction(原文,N)
解密过程:原文 <= DecodeFunciton(密文,N)

其中,EncodeFunction/DecodeFunction 是实现了加密解密算法的函数,N是输入参数,只要甲和乙双方都知道约定好的算法和N值,他们就能够进行安全通信。一旦某天,乙离职了,那么甲只要单方面修改参数N的值为N‘,那么就能保证加密后的内容仍然是安全的。因为乙只知道加密算法和原来的参数N,并不知道N'是多少,因此他无法破解由新参数加密的内容。

在这个例子当中,EncodeFunction/DecodeFunction 就代表了加密/解密算法;而参数N,就被称为所谓“秘钥”。这种在加密和解密的过程中用到同一个秘钥(参数)的方法,就叫做:对称加密算法

在实际应用中,算法通常是很难保密的,但秘钥必须是严格保密的。在只有通信双方了解秘钥,而任何第三方都不知道秘钥的情况下,才能保证通信是安全的。

通常情况下,Server与Client之间是一对多的关系,并且Client与Client之间也需要信息隔离(比如网络银行)。这就意味着,对每个不同的Client,Server都需要保存一份独立的密钥,用来与该Client进行私密通信。

在银行系统这样的案例中,Server是不可能一开始就预估好Client数量的,因此对每个新到来的Client,Server需要重新生成秘钥,并将其“安全地”传送给Client,这就涉及到“秘钥下发”的问题。也就是,在初始化阶段,如何将秘钥安全地传递给通信双方?

秘钥下发,是为了进行安全通信,但是没有安全通信,就不能保证秘钥的安全下发。这似乎是一个鸡生蛋,蛋生鸡的问题。(真实世界中,通常采用“物理链路”的方式完成下发,比如银行发给用户的U盾)。那么有什么办法,可以避免在不安全的网络上传递“秘钥”的尴尬吗?

非对称加密算法

有人想到了一个聪明的办法,把加密和解密的秘钥分开,一个用来加密,一个用来解密。例如,使用两个不同的秘钥,P、Q,配合特定的加密/解密算法,可以把加密解密过程变成下面这样:

加密过程:密文 <= EncodeFunction(原文,P)
解密过程:原文 <= DecodeFunciton(密文,Q)

加密的时候,用到秘钥P,解密的时候,用到秘钥Q,这种加密和解密过程使用不同秘钥的算法,称之为:非对称加密算法

这有什么用呢?

大有用处。回到上面的例子,甲和乙在相互通信的时候,不需要事先约定好一个共同的秘钥了。他们只需要商定好一个非对称加密算法,然后甲生成自己的两个秘钥(P,Q),乙也生成自己的两个秘钥(P‘,Q')。然后,请注意看:

1、甲把自己的加密秘钥P发送给乙(可以是明文传送),跟乙说,把给我的信息用这个秘钥加密传送。

2、于是,当乙给甲发送消息时,先用秘钥P加密,然后发送给甲,甲用自己的解密秘钥Q,解密原文。

3、乙也把自己的加密秘钥P'发送给甲(可以明文传送),跟甲说,把给我的信息用这个秘钥加密传送。

4、于是,当甲给乙发送消息时,先用秘钥P'加密,然后发送给乙,乙用自己的解密秘钥Q',解密原文。

这种方法的绝妙之处就在于,全程规避了敏感信息的发送。就算有人截获了甲或者乙的加密秘钥以及密文,也没有用,因为这个秘钥只能用来加密,不能用来解密。

有关非对称加密算法的原理,相对复杂一点,需要用到一些数学知识,这里不展开了(这个话题可另开贴研究)。

但是,要注意,非对称加密算法有个非常重要的性质:加密秘钥和解密秘钥是可以互换的,换句话说,也可以用解密秘钥来加密,再用加密秘钥来解密,这个过程可以表示为:

加密过程:密文 <= EncodeFunction(原文,P)
解密过程:原文 <= DecodeFunciton(密文,Q)

|| (或者)

加密过程:密文‘ <= EncodeFunction(原文,Q)
解密过程:原文 <= DecodeFunciton(密文’,P)

而且,对于给定的一对秘钥P、Q,如果用其中的一个秘钥来加密,那么只能用另外一个秘钥才能解密,换句话说:如果用其中的一个秘钥可以解密,那么就可以认定密文是通过另外一个秘钥来加密的!

这性质有什么用呢?

太有用了,继续往下看。

身份认证

非对称加密算法,解决了秘钥下发的问题。每个通信节点都会生成自己的一对秘钥,把其中一个发送给其他人用来加密信息,这个秘钥称之为:公钥;另外一个自己保留好,用来解密其他人发来的密文,这个秘钥称之为:私钥

公钥是公开的,任何人都可能获得。这就衍生出一个问题:在甲乙通信中,甲乙双方的公钥都是公开的,私钥是保密的。假设存在一个非法节点丙,丙可以截取甲乙之间的密文,但因为没有对应的私钥所以无法理解其内容,但是,它却可以利用甲的公钥加密信息发送给甲,也可以利用乙的公钥加密信息并发送给乙。无论甲和乙,对于收到信息,只要能用自己的私钥进行解密,就会认为是合法的,却无法确认发送信息者的身份。

既然网络上任何一个节点都能通过公钥发送消息,那么真正合法的发送者如何跟接收者证明消息是由自己发出的呢?这可以利用前面提到的非对称加密算法的性质来解决。

假设甲有一对秘钥(P,Q),乙有一对秘钥(P',Q'),甲要发送信息给乙,并且要跟乙证明这段信息是来自于甲,那么他可以这样做:

1、甲在给乙发送信息的时候,把自己的签名用自己的私钥 Q 进行加密,得到一个加密签名

甲的加密签名 <= EncodeFunction(甲的签名,Q)

2、甲把这个加密后的签名,附着在正文之后,在对整个内容用乙的公钥P'进行加密

原文 <= 正文 + 甲的加密签名
密文 <= EncodeFunction(原文,P')

3、甲把加密后的内容发送给乙

4、乙对收到内容,用自己的私钥Q'进行解密,得到原文

原文 <= DecodeFunciton(密文,Q')
原文 => 正文 + 甲的加密签名

5、乙从原文中摘取除甲的加密签名,用甲的公钥P,进行解密

甲的签名 <= DecodeFunciton(甲的加密签名,P)

6、乙成功得到甲的签名,从而证明信息确实是甲发送过来的。

这是因为既然签名能用甲的公钥解密,说明一定是有人利用了甲的私钥来加密(就是上面提到的性质),而理论上拥有甲的私钥的人一定就是甲自己(前提是私钥不会泄露),故此得证。

上面这个过程有点复杂,简单的说,它解决的是“验证消息发送者确实是公钥的拥有者”这个问题。但是,仅仅这样还是不够的。

要知道,任何人都可以生成公钥!假设有个冒充银行的站点,通过DNS劫持把Client的访问指向自己。如果Client是第一次访问Server,那么假冒者可以把自己的公钥发送给它,之后就可以堂而皇之地与之进行通信。

这个问题的本质是无法验证公钥拥有者的合法身份,解决这个问题的常用办法是引入一个公共可信的认证中心

认证中心的特点是,你可以认为它的公钥是得到了公开认可的(比如,世人皆知119是火警电话,那么如果有人试图声称自己的电话号码是火警电话,是不可能有人相信的)。换句话说,没有人能伪造公钥并声称自己是认证中心。这样一来,就出现了一个绝对可靠的节点。

如果一个站点希望自己的身份得到认可,它可以向认证中心提出申请(提交材料)。认证中心通过一些方法(审核材料),确认了站点身份之后,把站点的身份信息、站点的公钥、连同自己(认证中心)的签名一起打包成一个数字证书颁发给申请者。此后,当有Client访问站点申请公钥时,站点将自己的数字证书直接返回给Client。

数字证书,可以被认为是一种将公钥与身份进行绑定之后的产物。

Client通过认证中心的公钥(可信的)可验证证书中的数字签名,从而可以验证证书的合法性,并由此可以确认Server的身份及其公钥。

SSL/PKI

非对称加密算法可以解决秘钥下发的问题,再通过认证中心,还可以解决身份认证的问题。看起来,对称加密算法完全没有存在的必要了啊?

非也,这个世界没有完美的东西,有相对优势,就有相对劣势。非对称算法比较复杂,因此时间开销很大(大约是对称算法的上千倍),在高频通信的场景下,效率很低。相比之下,对称加密算法的效率是很高的。

如果我们把前面介绍的知识点总结一下:

  • 对称加密算法,速度快,适合高频通信加密,但秘钥的下发和管理成本太高。
  • 非对称加密算法,速度慢,不适合高频通信;可确认信息来自公钥拥有者;但无法确认公钥拥有者的合法身份。
  • 认证中心/数字证书,可确认公钥拥有者的合法身份。

那么,如果我们把上述方法结合起来,就会得到一个更合理的解决方案:

1、Server端在认证中心注册,申请数字证书(内含公钥)。

2、Client在发起请求之前,先从Server端申获得数字证书(包括公钥)。(数字证书证明了Server的身份合法性)

3、Client与Server用非对称加密的方式建立通话,协商一个对称秘钥。

4、Client与Server使用对称加密算法开始真正的通话。

上述方法,综合使用了各种手段,分阶段解决安全性问题,这也是目前被业界广泛应用的方法。SSL协议/PKI体系,就是基于上述原理的一种具体实现。具体可参考