吖,终于放假了,睡了两天的充满电键者今天勤奋的爬起来码字了,马上春节,正文前先拜个早年,祝各位童鞋新年快乐,心想事成。
最近键者打算在项目中使用基于 的认证体系,来管理 之间的相互认证问题,于是开始研究怎么实现并管理自签名的证书,于是牵扯到了键者常年黑箱的数字证书,所以……今天我们来聊聊数字证书吧
作为常年的黑箱攻城狮,终究还是逃不过开箱的时候,也许就像薛定谔说的,不打开箱子,怎么知道箱子里的猫是活着还是死了呢……
虽然,可能打开了也不知道……
快照:数字签名
以前对接微信的开发者接口时,需要对请求的业务进行签名,微信会提供给键者一段字符串,键者每次起调微信的接口时,均需要将所有请求参数及参数值,按照参数名开头字母顺序排序,然后将这段字符串附加在排序后的请求之后,并用微信指定的哈希函数进行一次哈希计算,将哈希结果与其他请求参数一同发给微信的接口。
基于哈希函数不可逆的特性,微信只要将收到的请求参数用相同的规则排序,并使用同一段字符串再做一次哈希,并将哈希结果与键者提供的进行比对,就可以确保请求是否真的是键者发出的。
前文用到的那段 ,从职能上看,就是 ;而根据这段 和 生成哈希结果的过程,就是 ,而这个哈希结果,自然就是 。
其实这里还巧妙地实现了在如何避免在信道上传输用户的 的情况下,验证用户的身份。
用户在参数中表述自己的身份( ),及请求的详细内容( ),再用自己的 对所有请求参数进行签名,将签名结果提交给服务端;服务端根据用户身份拿出自己存储的那份 ,就可以计算并判定签名是否正确,然后再根据用户的权限设置,选择拒绝或者接受并执行相关的操作。
当然,工程上我们一般还会设置一个随机字串作为盐值,并加入当前的时间戳,以确保哪怕请求的内容一样,每次产生的 结果都不一样,防止重放攻击。
简单小结一下:一般来说, 的核心技术在于哈希函数的不可逆性及足够高的结果唯一性。
漫谈:数字证书
那么通过 ,可以确保通讯双方的安全么?很遗憾,并不能,显而易见的一个问题是, 可以确保请求方发出的请求没有被中间人篡改,通过恰当的设计还能一定程度上避免回放攻击,然而前文中的每个参数,仍然是公开的,也就是说,它并不能防止内容被窃听。
理论上,通讯安全需要防止三重威胁:窃听风险、篡改风险、冒充风险。
那么这个时候,很自然而然地,就会想到对内容进行加密咯?
相信各位童鞋肯定都对计算机中 和 两种加密体系的特点有一定了解,键者在本文就不啰嗦了。
我们先来考虑 的思路,服务端和客户端事前协商一把 ,客户端对请求内容先进行一次加密,服务端收到请求后,先用 对请求进行一次解密,然后再校验 ……
假设服务端仅使用一把 ,那么所有客户端都知道这把 ,倒也能完成通讯的需求。可是密钥这东西,知道的人越多,泄漏的可能性就越大,对于管理来说也不是一个可靠的方案,一个客户端的管理不善会波及所有的客户端,这个风险成本太大了,并不是一个可靠的方案。
那如果给每个客户端都分配一把 呢?也会有问题,每个客户端发来的消息都是加密过的。也就是说,服务端无法直接从客户端发来的消息中得知,这个客户端是哪个用户,也就无法选择合适的 对内容进行解密,除非用所有客户端的 “轮询”一遍……
所以根据电视剧的常规套路,这里先出现的 一般都是炮灰的节奏?
那么是时候引入传说中的 了,加入服务端自己生成好一对 ,然后将其中一把自己严加保管(即 ),另一个把公开给所有希望与自己通讯的客户端(即 ),则所有的客户端都可以通过 加密自己的请求,且该请求只有持有 的服务端才能解开,那么通讯的安全就算是有保证了……吗?
可惜后来就是一个先有鸡还是先有蛋的问题……
如果是完全私有的业务中,我们可以假定,客户端拿到的 总是可靠的,毕竟站在计算机的角度来讲,也许每个运维人员都是上帝,不信仰运维的服务器都会下火狱……
除非出 了,出 的时候服务器是大爷
可惜,我们并没有合适的渠道能确保我们获得的 总是可靠的。同样,随着新服务的增加,我们也难以及时地获取最新的 。
于是证书中心( )应劫而生,定位上, 是客户端和服务器都信任的第三方,由 对公钥提供担保。服务器只要持有 签发的证书,就可以向客户端证明自己的身份。客户端只要能验证服务器持有的证书确实是 签发的,就可以该服务端是可靠的。
现实中,公共业务的 证书一般都是根据操作系统或者浏览器直接发布到客户端设备的,一般来说,我们可以认为这个渠道已经是比较可靠的了。
具体实现上, 需要自己先生成一对密钥,保管好自己的私钥( ),公开自己公钥( )。
而客户端需要生成一份对自己身份的描述文件( ),并附带自己的公钥( ),其内容可能是这样的:
通过 认为可靠的渠道(例如,当面……)交给 , 再用 对这份文件进行加密,并将加密后结果返回给客户端。此时,这份加密后的结果就是传说中的 ( )了。
这个身份描述文件的官方称呼是 ,缩写为 。
那么这份数字证书怎么工作呢?首先, 是公开的,也就是说,任何人拿到这份 的时候,都可以通过 解密证书,获得身份描述文件。但是由于 是被严加保管的,也就是说,除了当事人,没有人能伪造一份 不同的证书。
则客户端生成请求的时候,除了附带 以外,同时应使用 将请求内容加密,将加密后的内容与 一起发给服务器。
注意,这里的 =Encrypt( HASH($req), $cli.key ),而不是前文的方式,但本质没有区别,只是不同的实现思路而已……
服务器收到了请求以后,首先通过 解密 ,则从结果中可以拿到可靠的 ,再通过 解密请求正文,就可以拿到请求的内容和 了,根据 可以验证请求的内容是否被篡改,并执行后续业务。
为什么说这里的 是可靠的?
这块涉及到的实体有点多,键者在这里简单小结一下:
事实上,认证的需求是相对的,前文描述的流程是客户端向服务端证明自己身份的思路,而替换一下身份,就可以变成服务端向客户端证明自己身份的场景。后者在实际应用中,就是 业务中最常见的场景,服务端向客户端证明自己真的是 ,而不是伪造的中间者。
同样,特定场景中还会有服务端客户端双方都需要认证对方身份的情况,其实也是键者实际业务中真正需要解决的问题,不过先在此按下不表。
抛砖之言
就键者个人的愚见,从工程的角度来说,无论是先有鸡,还是先有蛋,并不妨碍键者今晚吃鸡,啊不是,应该是后续状态的变化。就像我们不知道世界的起源,并不影响我们好好的活在当下。探究起源是有意义的,活在当下也是有意义的,两者并无分高下。
放在前文的场景,就是我们信任某个公钥也好,不信任某个公钥也好,我们终归总是要先信任一些东西。放在数学上,这叫公理。放在宣传中,这叫“我认为下面这些真理是不言而喻的……”。
所以在键者看来,引入 的最大价值并不是解决了先有鸡还是先有蛋的问题,而是更好的解决的多对多认证的问题。
当然定义好初始规则仍然是很重要的,就像盒子不管打不打开,首先,要有猫……
虽然前文描述的都是一对一的场景,但实际上,一个 可以签发任意数量的证书,一个客户端也可以生成任意数量的 ,一个 甚至也可以被任意数量的 进行签名,每次签名都会生成由该 认可的证书。同样,一个服务端也可以信任不止一个 ,只要持有该 的公钥即可。 一个 还可以授权给子 ,形成信任链。
所以, 的业务本质是仍然像许多其他引入三方的设计一样,将 的工作委托给第三方完成,无论是服务端,还是客户端,都可以更专注的完成自己的业务,同时避免关注到对方的存在。特别是面向 的场景中,自建 有非常大的应用价值,增加服务时候,不需要逐个向已有的服务添加新的服务的访问密钥,而是通过 签发证书的方式,即可实现动态扩容。
其实还涉及到证书过期的或者撤销的问题,但键者今天的血槽已经空Orz……
附录:关键字
数字签名:Digital Signature数字证书:Digital Certificate证书中心:Certificate Authority,CA签名请求:Certificate Signing Request,CSR
大悦天
兜兜转转攻城狮一枚
正在寻找自己的路
微信号
领取专属 10元无门槛券
私享最新 技术干货