当前位置: 首页 > news >正文

不支持TLS的设备如何实现游客登录加密通信方案

文章目录

  • 背景
    • 设备情况
    • 原登录流程
    • 安全性考虑
    • 游客登录需求
  • 方案
    • 考虑点
    • 游客登录流程
    • did和loginID
    • 加密通信方案
    • 安全性考虑
      • 中间人攻击
      • 公钥泄漏
      • 私钥被暴力破解
      • 设备代码被破解
      • 设备LoginID被窃取
      • 服务器代码泄漏
      • 服务器被拖库
      • 服务端私钥泄漏
      • 暴力猜测loginID
      • 重放攻击
    • 总结

背景

一年多前的时候做了一个业务需求,简单来说就是需要在一类资源受限的嵌入式设备上实现能够游客登录我们的系统,经过刻苦的攻关(各种补知识),终于最终相对完美地实现了需求,应该是把所有的相关安全场景都考虑到了。闲来无事,来分享下整个技术方案。

下面假设读者有加密通信的基本知识,不深入细节。

本文会领着读者思考安全相关的问题。

设备情况

这个嵌入式设备上无法支持TLS,能够支持RSA加解密,但是要链接代码占用额外的空间。经过测试,RSA加密代码的大小和速度都比解密代码好很多(想来自然应该是这样)。另外也支持对称加密的相关算法。

原登录流程

原先只有手机验证码登录,在此设备上用户登录进行通信的流程如下:
在这里插入图片描述

  1. 明文请求短信接口,参数是要登录的手机号。
  2. 服务器通过运营商的短信通道给设备发送验证码
  3. 发送登录请求,包含明文的手机号,并用预埋的算法算出的Key1来加密验证码等敏感信息,这里其实用的是对称加密。服务器能使用同样的算法算出Key1,并解密,确认验证码正确;然后同样的加密算法加密后返回userID、token和后续通信用的对称加密Key和其他一些信息,这里的status_code是明文的,因为要确保设备要在登录失败时得知失败码。
  4. 后续请求中,会带上userID和token,来回的body中的数据部分都会用步骤3中返回的Key进行加密。

安全性考虑

  1. 中间人攻击:即在网络通信双方中间进行第三方监听的情况,第三方可以获取通信双方间所有的网络数据流,具体可以查阅相关资料。
    1. 在第一步中,中间人能知道哪个手机刚请求了验证码,但是没有意义。
    2. 运营商的短信通道和我们的互联网网络通道是隔离的,我们默认短信通道是安全的(不然没法搞了),即验证码可以安全地到达用户手上,此时只有用户和服务器知道刚刚的验证码是啥。
    3. 设备在发起登录请求时,由于验证码等信息已经是经过加密的了,虽然知道是哪个手机号在请求登录。中间人也没法知道具体的验证码是什么;在答复中同理,无法获取后续的加密key。
    4. 由于在第三步中没得到key,后面的请求也完全只能盯着加密后的数据干瞪眼。
  2. 加密安全性:
    1. 暴力破解:取决于选取的加密算法,我们假设只要合理选取对称加密算法,在可接受的时间内就不会被暴力破解成功。
    2. 密钥生成算法:由于login时的密钥生成算法是预先烧录的,而且只能通过一些公开信息(比如手机号)+有限位数的验证码(一般4位或6位)生成,因此登录密钥key生成算法如果泄漏,对此方案是毁灭性的,中间人只要穷举所有验证码生成的key对登录请求进行解密,就能获取到验证码以及后续的key。从服务器来说,要确保相关代码不泄漏,绝不能出现有213把代码发到全球最大规模同性交友平台这种情况。从设备来说,只能相信烧录的代码不会被逆向工程得到算法。
  3. 暴力登录:攻击方还可以通过直接用设备尝试各种验证码的方式来强行登录特定手机号,这个就是常规的,控制验证码验证次数那一套了,每次发验证码最多验证几次,验证错多少次后冻结账号之类的,真要是一下就猜中了验证码那确实没办法,可能就得继续往上搞多重认证,滑块验证之类的了。

总结:此方案的安全关键是登录时加密使用的密钥的生成算法不能泄漏。

留一个问题:

既然此方案在登录时就是用的对称加密,为什么要在登录的答复中再返回一个对称加密key,直接用登录时使用的那个不行吗?

游客登录需求

在登录部分的相关需求如下:

  1. 游客第一次登录时自动创建账号,如果(没有清存储的情况下)重新登录,还是同一个账号
  2. 实现不发短信的情况下能够直接登录
  3. 尽可能保证账号的安全
  4. 游客账号能使用正常登录账号的大部分功能

好了,现在问题抛给读者,如果让你来设计,你怎么实现对应方案?

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

– 思考的分界线 –

方案

考虑点

设计方案时,我主要进行了如下考虑:

  1. 改动小:为了能够快速实现,并且复用现有基础设施,尽可能使用现有能力。
  2. 安全:不能因为是游客就降低了账号的安全性,实际上最后的方案,安全性甚至超过了原短信登录方案
  3. 资源受限:尽量选用性能高,占用空间小的方案

游客登录流程

最终的游客登录流程如下:

  1. 发送登录请求,包含明文的设备ID(did),公开密钥编号(k.no),加密方法(e.method),由公开密钥加密过的登录ID(loginID)、答复密钥(Key2)、登录递增号(incr.no)。如果成功,服务器会返回用Key2对称加密过的userID、token和后续通信使用的对称加密Key。
  2. 后续请求中,会带上userID和token,来回的body中的数据部分都会用步骤1中返回的Key进行加密。

有没惊掉了下巴,怎么比原来的方案还简略了,一个请求就实现了登录?!

我们来给大家具体分析一下。

did和loginID

did:为了实现按设备标识游客,又保证正好的安全性。首先想到的就是,游客账号要和设备ID有关。至于设备ID的生成,我们这里不做讨论,只要算法选的好,就能保证每个设备的ID不同。
loginID:但是,我们并不能直接拿did来作为游客的账号

  1. did其实并不能保证不重复,比如一些测试设备或者由于算法缺陷导致撞did;
  2. did是明文传输的,所以谁都可以假冒。
  3. 一个设备被刷机后,下一个人不应该能登录上一个账号。因为比如手机被转卖了,那下一个人不是就能看到前一个人的聊天了,太夸张了。
    我们要想一个机制进一步标识游客账号,于是did+loginID唯一标识游客账号的方案就出来了。

设备在游客登录前,需要确保随机地生成一个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被窃取

loginID是各个设备在需要登录时随机生成的,不是用户自己输入的。因此想想有哪几种情况hacker可能直接拿到loginID?

  1. 远程黑入了设备,获取设备上存储的loginID。这个厉害了,就和用户存在本地的密码被盗了一样,这个作为设计协议方管不到,看设备上怎么弄能够更安全地存储这个值了。就算被盗也是一个用户一个用户的,还算可以接受。
  2. 设备被hacker拿到手。那他还费劲提取loginID干嘛,直接在app上点击游客登录不就行了。。

设备方泄漏loginID的好像就这样两种情况了。

服务器代码泄漏

无所谓,我就整个方案公开出来,算法告诉你,你能奈我何,不知道私钥你照样啥都干不了。

服务器被拖库

如果设备到用户账号的映射表泄漏了,只要正确对loginID进行了hash处理才存储,游客账号照样很安全,因为连服务器自己都不知道loginID到底是啥,只是在登录时比对了签名而已。

服务端私钥泄漏

这个问题确实大了,这是这个方案的生命线,只要私钥泄漏,所有使用对应k.no公钥的设备都有安全问题了,所以必须安全保管。

暴力猜测loginID

如果有人在暴力猜测loginID,就会短时间内发出大量同did的游客登录请求,只要和限制验证码登录次数一样限制同did的登录请求次数即可。

重放攻击

这种登录方式带来了另一种风险,中间人虽然无法随便登录别人的游客账号,但是可以通过把登录请求进行重放,导致之前登录成功的账号的token失效,从而实现将他踢蹬。因此,要求客户端每次发登录请求时,都要把incr.no++(记得固化);服务器会记录上次的incr.no值,如果小于等于,说明可能是重放的请求,可以直接拒绝登录。

总结

本文给出了一种资源受限设备进行游客登录的方案,不需要使用TSL就能实现相对安全的登录。并完整分析了安全风险。

此方案的安全性依赖于:

  1. 加密算法本身的可靠性,无法在有限的时间内暴力破解
  2. 服务端的私钥安全存储,不泄漏。

希望能给各位读者带来一定启发。

经验有限,可能还有没有考虑到的安全风险,敬请大佬指出!

相关文章:

  • 【Pandas 数据分析3-2】Pandas 数据读取与输出 - Excel
  • TiDB Dashboard 实例性能分析 - 持续分析页面
  • Spring Boot 集成 Redis 配置 MyBatis 二级缓存
  • 9 二叉树-添加
  • SSM进阶-搭建Dubbo
  • STM32F103 CAN通讯实操
  • JAVA-----注释、字面量、关键字、制表符
  • numpy数组的变形、级联操作、聚合操作、常用的数学函数以及矩阵相关
  • ActiveMQ(二)
  • 某大学ipv6和ipv4结合的校园网规划设计
  • 【程序员表白大师】html七夕脱单必看源码制作
  • 车载VPA形象发展史:谁是第一个吃螃蟹的人?
  • 22.9.30 喜迎暑假多校联赛第二场(欢乐AK找回自信)ABDEFH
  • C++----智能指针
  • SpringMVC处理Ajax请求及处理和响应json格式的数据
  • [PHP内核探索]PHP中的哈希表
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • chrome扩展demo1-小时钟
  • fetch 从初识到应用
  • JAVA_NIO系列——Channel和Buffer详解
  • JDK 6和JDK 7中的substring()方法
  • js算法-归并排序(merge_sort)
  • JS学习笔记——闭包
  • nginx 配置多 域名 + 多 https
  • Objective-C 中关联引用的概念
  • uni-app项目数字滚动
  • vue-router的history模式发布配置
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 配置 PM2 实现代码自动发布
  • 前嗅ForeSpider中数据浏览界面介绍
  • 浅谈Golang中select的用法
  • 使用权重正则化较少模型过拟合
  • 思维导图—你不知道的JavaScript中卷
  • 算法-插入排序
  • 微服务框架lagom
  • 微信开放平台全网发布【失败】的几点排查方法
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​Spring Boot 分片上传文件
  • (04)odoo视图操作
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (Note)C++中的继承方式
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (ZT)一个美国文科博士的YardLife
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)RocketMQ初步认识
  • (已解决)什么是vue导航守卫
  • (转)Oracle 9i 数据库设计指引全集(1)
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET 反射的使用
  • .net 后台导出excel ,word
  • .NET 将多个程序集合并成单一程序集的 4+3 种方法
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉