首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
当SWOOLE遇上PHP
当SWOOLE遇上SERVER
当SWOOLE遇上TCP
当SWOOLE遇上PROTOCOL
Swoole的进程模型
Swoole的进程与热重载
守护进程二三事与Supervisor
番外:数字签名与数字证书
番外:OpenSSL与数字证书
当前位置:
首页>>
技术小册>>
Swoole入门教程
小册名称:Swoole入门教程
## 快照:数字签名 以前对接微信的开发者接口时,需要对请求的业务进行签名,微信会提供给键者一段字符串,键者每次起调微信的接口时,均需要将所有请求参数及参数值,按照参数名开头字母顺序排序,然后将这段字符串附加在排序后的请求之后,并用微信指定的哈希函数进行一次哈希计算,将哈希结果与其他请求参数一同发给微信的接口。 ``` go // 充满真诚的伪代码 req := "更新" user := "大悦天" secret := "HelloWorld" // 准备用于签名的字符串 strToSign := fmt.Sprintf("req=%s&user=%s&secret=%s", req, user, secret) signedStr := Sha256(strToSign) // 发送给服务端的内容 strToSend := fmt.Sprintf("req=%s&user=%s&sign=%s", req, user, signedStr) ``` 基于哈希函数不可逆的特性,微信只要将收到的请求参数用相同的规则排序,并使用同一段字符串再做一次哈希,并将哈希结果与键者提供的进行比对,就可以确保请求是否真的是键者发出的。 前文用到的那段`字符串`,从职能上看,就是`密钥`;而根据这段`请求`和`密钥`生成哈希结果的过程,就是`签名`,而这个哈希结果,自然就是`数字签名`。 > 其实这里还巧妙地实现了在如何避免在信道上传输用户的`密钥`的情况下,验证用户的身份。 用户在参数中表述自己的身份(`user`),及请求的详细内容(`req`),再用自己的`密钥`对所有请求参数进行签名,将签名结果提交给服务端;服务端根据用户身份拿出自己存储的那份`密钥`,就可以计算并判定签名是否正确,然后再根据用户的权限设置,选择拒绝或者接受并执行相关的操作。 > 当然,工程上我们一般还会设置一个随机字串作为盐值,并加入当前的时间戳,以确保哪怕请求的内容一样,每次产生的`数字签名`结果都不一样,防止重放攻击。 简单小结一下:一般来说,`数字签名`的核心技术在于哈希函数的不可逆性及足够高的结果唯一性。 ## 漫谈:数字证书 那么通过`数字签名`,可以确保通讯双方的安全么?很遗憾,并不能,显而易见的一个问题是,`数字签名`可以确保请求方发出的请求没有被中间人篡改,通过恰当的设计还能一定程度上避免回放攻击,然而前文中的每个参数,仍然是公开的,也就是说,它并不能防止内容被窃听。 > 理论上,通讯安全需要防止三重威胁:窃听风险、篡改风险、冒充风险。 那么这个时候,很自然而然地,就会想到对内容进行加密咯? > 相信各位童鞋肯定都对计算机中`对称加密`和`不对称加密`两种加密体系的特点有一定了解,键者在本文就不啰嗦了。 我们先来考虑`对称加密`的思路,服务端和客户端事前协商一把`对称密钥`,客户端对请求内容先进行一次加密,服务端收到请求后,先用`对称密钥`对请求进行一次解密,然后再校验`数字签名`…… 假设服务端仅使用一把`对称密钥`,那么所有客户端都知道这把`对称密钥`,倒也能完成通讯的需求。可是密钥这东西,知道的人越多,泄漏的可能性就越大,对于管理来说也不是一个可靠的方案,一个客户端的管理不善会波及所有的客户端,这个风险成本太大了,并不是一个可靠的方案。 那如果给每个客户端都分配一把`对称密钥`呢?也会有问题,每个客户端发来的消息都是加密过的。也就是说,服务端无法直接从客户端发来的消息中得知,这个客户端是哪个用户,也就无法选择合适的`对称密钥`对内容进行解密,除非用所有客户端的`对称密钥`“轮询”一遍…… > 所以根据电视剧的常规套路,这里先出现的`对称加密`一般都是炮灰的节奏? 那么是时候引入传说中的`不对称加密`了,加入服务端自己生成好一对`不对称密钥`,然后将其中一把自己严加保管(即`私钥`),另一个把公开给所有希望与自己通讯的客户端(即`公钥`),则所有的客户端都可以通过`公钥`加密自己的请求,且该请求只有持有`私钥`的服务端才能解开,那么通讯的安全就算是有保证了……吗? > 可惜后来就是一个先有鸡还是先有蛋的问题…… 如果是完全私有的业务中,我们可以假定,客户端拿到的`公钥`总是可靠的,毕竟站在计算机的角度来讲,也许每个运维人员都是上帝,不信仰运维的服务器都会下火狱…… > 除非出`Bug`了,出`Bug`的时候服务器是大爷 可惜,我们并没有合适的渠道能确保我们获得的`公钥`总是可靠的。同样,随着新服务的增加,我们也难以及时地获取最新的`公钥`。 于是证书中心(`CA`)应劫而生,定位上,`CA`是客户端和服务器都信任的第三方,由`CA`对公钥提供担保。服务器只要持有`CA`签发的证书,就可以向客户端证明自己的身份。客户端只要能验证服务器持有的证书确实是`CA`签发的,就可以该服务端是可靠的。 > 现实中,公共业务的`CA`证书一般都是根据操作系统或者浏览器直接发布到客户端设备的,一般来说,我们可以认为这个渠道已经是比较可靠的了。 具体实现上,`CA`需要自己先生成一对密钥,保管好自己的私钥(`ca.key`),公开自己公钥(`ca.pub`)。 而客户端需要生成一份对自己身份的描述文件(`cli.csr`),并附带自己的公钥(`cli.pub`),其内容可能是这样的: ``` yaml 账户: '大悦天' 公钥: '大悦天的公钥' ``` 通过`CA`认为可靠的渠道(例如,当面……)交给`CA`,`CA`再用`ca.key`对这份文件进行加密,并将加密后结果返回给客户端。此时,这份加密后的结果就是传说中的`数字证书`(`cli.crt`)了。 > 这个身份描述文件的官方称呼是`证书签名请求`,缩写为`CSR`。 那么这份数字证书怎么工作呢?首先,`ca.pub`是公开的,也就是说,任何人拿到这份`cli.crt`的时候,都可以通过`ca.pub`解密证书,获得身份描述文件。但是由于`ca.key`是被严加保管的,也就是说,除了当事人,没有人能伪造一份`公钥`不同的证书。 则客户端生成请求的时候,除了附带`数字签名`以外,同时应使用`cli.key`将请求内容加密,将加密后的内容与`cli.crt`一起发给服务器。 > 注意,这里的`数字签名`=Encrypt( HASH($req), $cli.key ),而不是前文的方式,但本质没有区别,只是不同的实现思路而已…… 服务器收到了请求以后,首先通过`ca.pub`解密`cli.crt`,则从结果中可以拿到可靠的`cli.pub`,再通过`cli.pub`解密请求正文,就可以拿到请求的内容和`数字签名`了,根据`数字签名`可以验证请求的内容是否被篡改,并执行后续业务。 > 为什么说这里的`cli.pub`是可靠的? 这块涉及到的实体有点多,键者在这里简单小结一下: ``` // 又是充满真诚的伪代码 var ca.key, ca.pub var cli.key, cli.pub var cli.csr = `ID: 大悦天` var cli.crt = ($cli.csr + $cli.pub) + $ca.key // 使用私钥对csr和pub分别加密 // 以下是客户端请求过程 var cli.req = ` ID: '大悦天' Action: '更新', ` // tips: sha1已被证明不安全,工程上请选用sha256或以上级别的哈希算法。 var cli.hash.req = HASH( $cli.req ) var cli.encrypt.req = $cli.req + $cli.key // 加密 var cli.encrypt.hash.req = $cli.hash.req + $cli.key // 加密 cli.Send( $cli.encrypt.req, $cli.encrypt.hash.req, $cli.crt) // 以下是服务端的验证过程 var cli.csr, cli.pub = $cli.crt - $ca.pub // 描述的是解密过程 var cli.hash.req = $cli.encrypt.hash.req - $cli.pub // 解密 var cli.req = $cli.encrypt.req - $cli.pub // 解密 // 服务端根据req重新算一个哈希值 var cli.srvHash.req = HASH( $cli.req ) // 将csr中的身份与req中的身份进行比对 if cli.csr.ID == cli.req.ID { // 名副其实 }else{ // 名不副实 } // 将收到的哈希值与自己根据请求重算的哈希值进行比较 if $cli.hash.req == $cli.srvHash.req { // 认证通过 println("虽然认识你,但就是不更新=。=") }else{ // 认证失败 println("不认识你,不更新……") } ``` 事实上,认证的需求是相对的,前文描述的流程是客户端向服务端证明自己身份的思路,而替换一下身份,就可以变成服务端向客户端证明自己身份的场景。后者在实际应用中,就是`HTTPS`业务中最常见的场景,服务端向客户端证明自己真的是`服务端`,而不是伪造的中间者。 > 同样,特定场景中还会有服务端客户端双方都需要认证对方身份的情况,其实也是键者实际业务中真正需要解决的问题,不过先在此按下不表。 ## 抛砖之言 就键者个人的愚见,从工程的角度来说,无论是先有鸡,还是先有蛋,并不妨碍键者今晚吃鸡,啊不是,应该是后续状态的变化。就像我们不知道世界的起源,并不影响我们好好的活在当下。探究起源是有意义的,活在当下也是有意义的,两者并无分高下。 > 放在前文的场景,就是我们信任某个公钥也好,不信任某个公钥也好,我们终归总是要先信任一些东西。放在数学上,这叫公理。放在宣传中,这叫“我认为下面这些真理是不言而喻的……”。 ``` txt 而密码只要存在,就有被破解或者泄露的危险。 ——鲁迅《我没说过》 ``` 所以在键者看来,引入`CA`的最大价值并不是解决了先有鸡还是先有蛋的问题,而是更好的解决的多对多认证的问题。 > 当然定义好初始规则仍然是很重要的,就像盒子不管打不打开,首先,要有猫…… 虽然前文描述的都是一对一的场景,但实际上,一个`CA`可以签发任意数量的证书,一个客户端也可以生成任意数量的`CSR`,一个`CSR`甚至也可以被任意数量的`CA`进行签名,每次签名都会生成由该`CA`认可的证书。同样,一个服务端也可以信任不止一个`CA`,只要持有该`CA`的公钥即可。 一个`CA`还可以授权给子`CA`,形成信任链。 所以,`数字证书`的业务本质是仍然像许多其他引入三方的设计一样,将`认证`的工作委托给第三方完成,无论是服务端,还是客户端,都可以更专注的完成自己的业务,同时避免关注到对方的存在。特别是面向`微服务`的场景中,自建`证书中心`有非常大的应用价值,增加服务时候,不需要逐个向已有的服务添加新的服务的访问密钥,而是通过`证书中心`签发证书的方式,即可实现动态扩容。
上一篇:
守护进程二三事与Supervisor
下一篇:
番外:OpenSSL与数字证书
该分类下的相关小册推荐:
PHP程序员面试笔试真题与解析
全面构建Magento2电商系统
ThinkPHP项目开发实战
Laravel(10.x)从入门到精通(四)
Workerman高性能Web框架-Webman
Swoole高性能框架-SwooleWorker
Laravel(10.x)从入门到精通(六)
Laravel(10.x)从入门到精通(九)
PHP8入门与项目实战(3)
Swoole高性能框架-Hyperf
PHP8入门与项目实战(4)
Magento零基础到架构师(库存管理)