近两年,物联网技术发展迅猛,各样的智能设备渐渐地走进了我们的家居生活。在众多的智能设备厂商中,小米是较早的布局智能家居生态的厂商,购买智能家居设备的用户几乎都会有一到两个小米设备。那么是否可以控制这些小米设备呢,其中过程是否会有安全风险呢?本文接下来会主要介绍这些内容。
具体地,除了米家app控制小米设备外,小米还提供一种局域网控制的方式,但前提是要获得用于设备认证一串字符串(即token),所以接下来主要介绍如何获取设备token,以及如何实现局域网控制设备。
一、总体流程介绍
在同一局域网下,小米设备可以使用专有的加密UDP网络协议miio协议通信控制。在网络可达的前提下,向小米设备发送一串hello bytes即可获得含有token的结构体数据。之后,构造相应的结构体,并以同样的方式发送给设备即可实现控制。具体流程如下图所示:
二、小米设备token获取
小米设备的token获取有三种途径,如下所述:
2.1 miio获取token
miio有基于Python实现的库,其Github项目地址为:https://github.com/rytilahti/python-miio。该项目支持所有兼容miio协议的设备,并将设备发现、识别和控制的方法进行了分类。
2.1.1 环境安装
python-miio需要Python3.5以上版本上才能运行,所以首先搭建Python环境。下面,我们在操作系统为Ubuntu的电脑或者树莓派中安装Python3.5:
- 安装5依赖(本机存在的会忽略)
sudoapt-getinstallbuild-essentiallibsqlite3-devsqlite3bzip2libbz2-devlibssl-devopenssllibgdbm-devliblzma-devlibreadline-devlibncursesw5-dev
- 编译安装5
wgethttps://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz tarzxvfPython-3.5.2.tgz cd./Python-3.5.2 ./configure--prefix=/usr/bin/python3.5 sudomake sudomakeinstall
- 编译后运行一下5,结果如下证明安装成功
sean@ubuntu:~/Desktop/week/ProcessAndDeadline$ python3.5 Python3.5.2(default,Nov232017,16:37:01) [GCC5.4.020160609]onlinux Type"help","copyright","credits" or"license" formoreinformation. >>>
- 安装miio库,下载库代码到本地并安装
gitclonehttps://github.com/rytilahti/python-miio cd python-miio/ python3.5 setup.py install
2.1.2 通过脚本获取token
下面就以小米智能插座为例,说明如何获取该设备的token。
- 脚本编写
首先要保证获取token的客户端要与插座网络可达。为了显示直观,我们将主要实现代码从库中提取出来(如下)。将文件放在python-miio/miio目录下(该脚本主要就是使用socket向设备ip的54321端口发送固定字符串,返回值即为设备token):
#-*-coding:utf8-*- importcodecs importsocket fromprotocolimportMessage helobytes=bytes.fromhex('21310020ffffffffffffffffffffffffffffffffffffffffffffffffffffffff') s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.sendto(helobytes,('192.168.42.17',54321))#插座ip,端口54321 data,addr=s.recvfrom(1024) m=Message.parse(data) tok=codecs.encode(m.checksum,'hex') print(m) print(tok)
- 运行:5 miio_test.py
返回如下消息结构,其中checksum即为设备的token,解码之后为:’1d0616858062b8f836ebcacc98e62dd2’。
root@raspberrypi:~/python-miio/miio#python3.5miio_test.py Container: data=Container: offset2=32 offset1=32 length=0 value= (total0) data= (total0) header=Container: offset2=16 offset1=0 length=16 value=Container: length=32 unknown=0 device_id=\x03\xa9\x84\xb6(total4) ts=1970-01-2222:10:05 data=!1\x00\x00\x00\x00\x00\x03\xa9\x84\xb6\x00\x1c\xe7=(total16) checksum=\x1d\x06\x16\x85\x80b\xb8\xf86\xeb\xca\xcc\x98\xe6-\xd2(total16) b'1d0616858062b8f836ebcacc98e62dd2'
支持这种方式拿token的还有小米的空气净化器、净水器、扫地机器人、智能插座插线板等。具体列表见https://github.com/rytilahti/python-miio。
2.2 从米家app获取token
如果能用上述的探测方法获取token还是比较便捷的,但目前只有部分小米设备支持。接下来还有一种方法可以直接从app获取token。以小米绿米网关为例:首先下载米家app,将绿米网关配置入网后,点击网关设备。接下来步骤如下组图,最后的密码即为网关的token。
目前绿米的这种设计模式是用户友好的,而且设备的所有者还可以选择是否开放局域网控制以及刷新控制token的有效性,比较安全。个人还是很希望小米的其他设备同样开放app侧获取设备token,因为毕竟获取需要搭建复杂的环境以及调试代码,大部分使用者应该不能接受的。
2.3 从数据库获取token
该方法是读取手机中米家的app中的数据记录来获取设备的token,具体步骤如下:
- 准备一部获取root权限的安卓手机
- 安装米家app并登录账号
- 进入/data/data/com.xiaomi.smarthome/databases/
- 拷贝db,下载到电脑
- 前往网站(http://miio2.yinhh.com/),上传db,点击提交,即可获得token。
因为没有root的安卓手机,笔者没有具体测试这种方式获取token的有效性,具体可以参考这篇文章(https://homekit.loli.ren/docs/show/12)
三、控制小米WiFi插座
如果获得了token,就能对小米的设备进行操作,接下来介绍使用miio协议控制小米插座的主要步骤。
3.1 控制脚本编写
基于1.1.2获取到的回传的token信息,构造如下数据结构,用来控制设备。
ts=m.header.value.ts+datetime.timedelta(seconds=1) cmd={'id':1,'method':'set_power','params':['on']} header={'length':0,'unknown':0x00000000, 'device_id':device_id,'ts':ts} msg={'data':{'value':cmd}, 'header':{'value':header}, 'checksum':0}
其中:
token为获取到的设备token;
device_id为获取token返回结构中的device_id字段;
ts是一个时间结构,控制传的ts的需要在获取到ts基础上加1秒;
cmd中的method包括:set_power(控制开关)、get_prop(获取状态),控制的params是[‘on’]/ [‘off’],获取状态的params是[‘power’, ‘temperature’]
下面的代码是实现打开插座的控制,其中插座的IP为192.168.42.17。
m0=Message.build(msg,token=m.checksum) s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.sendto(m0,('192.168.42.17',54321)) data,addr=s.recvfrom(1024) m1=Message.parse(data,token=tok) print(m1)
3.2 执行控制脚本
执行上面编写的控制代码,得到返回状态,如果返回状态中value变量中的result字段为[ ‘ok’],即为控制成功。当cmd使用get_prop方法时,如果返回的value变量值为{‘id’: 1, ‘result’: [‘on’, 59]}时,表示插座状态是开的,温度为59℃。因为篇幅关系具体就不贴了。
root@raspberrypi:~/python-miio/miio#python3.5miio_test.py Container: data=Container: value={'id':1,'result':['ok']} data=\x03\x88T\x86\x1a\xbd\xb5\xb24.\xdcm\xdb\xc3\xb4\xdb\x0e7\x80JR\x0e\xda\xa987\x91Q\xd0\xee\x9bV(total32) offset2=64 offset1=32 length=32 header=Container: value=Container: length=64 unknown=0 device_id=\x03\xa9\x84\xb6(total4) ts=1970-01-2220:40:38 data=!1\x00@\x00\x00\x00\x00\x03\xa9\x84\xb6\x00\x1c\xd2F(total16) offset2=16 offset1=0 length=16 checksum=\xfc\xe2\r\x8b\xd9\xb6\x1d\xfda\xd5\x11\x04\xe1b\xbe\xfd(total16)
四、总结
从目前的智能家居市场来看,用户不会只使用单个智能设备厂商的设备,所以对于厂商来说,通过开放接口给用户一些局域网的控制“自由”,实现不同厂商设备的联动是一个不错的选择。
从另外一个角度,本文中体现的安全问题我们也不容忽视。如果如2.1所示在局域网中不经过认证就能获取物联网设备的访问凭证,并进而进行控制,无形中给入侵者留了一扇门。例如,攻击者可经过扫描互联网发现家庭路由器,并利用弱口令或设备漏洞获得路由器的shell权限,接下来就可按照文中步骤就可以获得设备token进而控制。
在接下来的文章中,我们会给大家介绍一些智能家居的平台,以及家庭环境中智能设备的一些安全防护方法,让智能与安全同行。