一年多前的时候做了一个业务需求,简单来说就是需要在一类资源受限的嵌入式设备上实现能够游客登录我们的系统,经过刻苦的攻关(各种补知识),终于最终相对完美地实现了需求,应该是把所有的相关安全场景都考虑到了。闲来无事,来分享下整个技术方案。
下面假设读者有加密通信的基本知识,不深入细节。
本文会领着读者思考安全相关的问题。
这个嵌入式设备上无法支持TLS,能够支持RSA加解密,但是要链接代码占用额外的空间。经过测试,RSA加密代码的大小和速度都比解密代码好很多(想来自然应该是这样)。另外也支持对称加密的相关算法。
原先只有手机验证码登录,在此设备上用户登录进行通信的流程如下:

总结:此方案的安全关键是登录时加密使用的密钥的生成算法不能泄漏。
留一个问题:
既然此方案在登录时就是用的对称加密,为什么要在登录的答复中再返回一个对称加密key,直接用登录时使用的那个不行吗?
在登录部分的相关需求如下:
好了,现在问题抛给读者,如果让你来设计,你怎么实现对应方案?
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
– 思考的分界线 –
设计方案时,我主要进行了如下考虑:
最终的游客登录流程如下:

有没惊掉了下巴,怎么比原来的方案还简略了,一个请求就实现了登录?!
我们来给大家具体分析一下。
did:为了实现按设备标识游客,又保证正好的安全性。首先想到的就是,游客账号要和设备ID有关。至于设备ID的生成,我们这里不做讨论,只要算法选的好,就能保证每个设备的ID不同。
loginID:但是,我们并不能直接拿did来作为游客的账号
设备在游客登录前,需要确保随机地生成一个loginID字符串,然后固化存储下来用做之后每一次登录时传递的loginID。
仅拿当前秒数、unix s、以及他们作为随机数生成器的种子 来生成loginID的方案是有安全问题的,思考下为什么?
在服务器处,根据did+loginID的组合确定游客账号是否登录过,没有登录过就相当于第一次登录,直接给创建新账号,否则就是登录老账号;这里loginID有点类似于账号密码,为了防止拖库导致账号泄漏,服务端还要考虑对loginID做加盐+hash存储,这里不讨论细节。
首先,e.method是为了未来扩展其他加密方法使用,忽略不谈。
在设备发起的游客登录请求中,重要信息是用非对称加密的公钥进行加密的,公钥是提前烧录在芯片中的,我们这里直接使用RSA加密,前面说过,RSA的加密代码体积又小速度又快(相比解密部分)。而服务端收到请求后,会使用私钥进行解密,获取出loginID、Key2和incr.no,Key2会用来对称加密服务器答复中的重要部分。服务器并不在乎Key2是怎么生成的,设备给的是啥就用啥加密,设备应该给出一个随机的Key2。设备收到答复后,用自己知道的Key2对答复进行对称解密,获得后续通信用的Key,然后就和原来一致了。
我们可以看到,相比原来的验证码登录,我们主要只增加了RSA加密的代码和一些零碎的代码,其他都是可以复用的。Key2从原来通过算法由验证码+手机号等算出,改成了由设备直接提供。
而k.no的作用是服务器可以预先生成许多个不同的密钥对,然后编号后把公钥给客户端,每批设备烧录不同的公钥。通过宏技术,可以使得只会占用一个公钥的内存。
XXXX.h
#define KEY_NUM 10
extern const uint8_t key_pub[];
XXXX.c
#include "XXXX.h"
……
#if (KEY_NUM==10)
const uint8_t key_pub[]={0xAB, ……};
#endif
#if (KEY_NUM==11)
const uint8_t key_pub[]={0xBB, ……};
#endif
……
假设有人在中间监听我们的网络,他只能知道did是什么,其他部分都被公钥加密了,由于他没有私钥,只能干瞪眼。
假如公钥泄漏了,中间人可以通过公钥加密来登录某个did的游客账号,这时候,由于不知道这设备对应游客账号的loginID,他登录的账号一定是空账号,登录了也没用。
之所以叫公钥,就是本来就可以公开。
首先,RSA的密钥的暴力破解难度是很大的,要是怕不够大,增加密钥位数就好。假设此人费劲千幸万苦破解了其中一个私钥,那会导致使用这一批公私钥的设备都不安全了。这时候k.no的作用就体现了,只要k.no换的勤快,起码使用其他k.no的用户的登录请求不会被解密。
对于已经在登录状态的游客账号,只要其未在中间人监听期间进行登录操作,其账号还是安全的,因为中间人还是不知道其loginID
没有任何问题,设备上存的是一个公钥,本身就是默认可以公开的,加密算法也可以公开,不知道密钥都是干瞪眼,只要设备生成loginID和Key2真的足够随机,找不到bug,代码公开都没问题。
loginID是各个设备在需要登录时随机生成的,不是用户自己输入的。因此想想有哪几种情况hacker可能直接拿到loginID?
设备方泄漏loginID的好像就这样两种情况了。
无所谓,我就整个方案公开出来,算法告诉你,你能奈我何,不知道私钥你照样啥都干不了。
如果设备到用户账号的映射表泄漏了,只要正确对loginID进行了hash处理才存储,游客账号照样很安全,因为连服务器自己都不知道loginID到底是啥,只是在登录时比对了签名而已。
这个问题确实大了,这是这个方案的生命线,只要私钥泄漏,所有使用对应k.no公钥的设备都有安全问题了,所以必须安全保管。
如果有人在暴力猜测loginID,就会短时间内发出大量同did的游客登录请求,只要和限制验证码登录次数一样限制同did的登录请求次数即可。
这种登录方式带来了另一种风险,中间人虽然无法随便登录别人的游客账号,但是可以通过把登录请求进行重放,导致之前登录成功的账号的token失效,从而实现将他踢蹬。因此,要求客户端每次发登录请求时,都要把incr.no++(记得固化);服务器会记录上次的incr.no值,如果小于等于,说明可能是重放的请求,可以直接拒绝登录。
本文给出了一种资源受限设备进行游客登录的方案,不需要使用TSL就能实现相对安全的登录。并完整分析了安全风险。
此方案的安全性依赖于:
希望能给各位读者带来一定启发。
经验有限,可能还有没有考虑到的安全风险,敬请大佬指出!