我住的小区使用了一个叫守望领域的智能门禁系统,可以通过手机App开小区门禁和单元门,但是用App开门需要经过四五步:打开App→进入开门界面→找到需要开的门→点击开门。
加上戴口罩时候解锁手机需要输入密码,导致整个流程非常耗时,经常需要站在小区门口和单元门口操作半天,有一段时间我甚至养成了携带实体门禁卡的习惯,实体门禁卡开门要快很多。
最近又开始忘带门禁卡,苦恼之余发现iOS在锁屏界面右划可以免解锁直接进入spotlight界面,这个界面可以添加捷径,如果能写一个捷径去调用守望领域App的API开门,就可以实现手机免解锁一键开门。
查找 API 首先需要通过Charles之类的软件查找App调用的API,配置Charles查看App请求的方式不再赘述,Google一下可以看到很多教程。直接看结果Charles的结果,可以看到api.lookdoor.cn是这个软件所请求的API域名。
打开软件发的请求非常多,经过操作和请求的对比可以看到,发送开门指令调用的API是:/func/hjapp/house/v1/pushOpenDoorBySn.json?equipmentId=xxxxxx 这个路径。
详细查看这个请求可以发现,equipmentId指的就是小区门的Id,接口使用cookie做认证,只要将cookie带上就可以模拟开门指令。
第一次尝试 打开iOS捷径App,创建一个新捷径,App调用API使用了POST请求,搜索Get contents of这个动作来实现发送POST请求。
通过Charles找到要开的门的URL填入,Method选择POST,Headers里填入Cookie进行认证,内容直接从Charles复制就可以,尝试运行,it works!
接下来把这个捷径添加到Spotlight界面,锁屏界面右划点一下,就可以实现一键开小区门禁,和打开App的四五步操作相比,确实省时省力。拿着新配好的捷径去上班,下班回到小区想试一把一键开门,结果又被困到门口了,上午还正常的捷径竟然失效了,打开一看API报登录超时,有可能是Cookie里的SESSION_ID过期了。
分析登录过程 再次用Charles抓包,分析登录相关的API,会发现主要是这两个:
/func/hjapp/user/v2/getPasswordAndKey.json:获取AES Key的API
/func/hjapp/user/v2/login.json?password=xxxxxx:登录API
通过分析,用时序图来表示这部分的交互逻辑:
登录过程清楚了,但是其中使用AES_KEY对密码进行加密的配置还是不清楚的,使用一个工具来尝试通过密文和AES_KEY来解密:http://tool.chacuo.net/cryptaes
输入密钥和密文,使用各种配置进行解密,当能够解出内容的时候,证明我们找到了加密的配置,可以看到BlockSize=128,padder使用的是pkcs7padding,加密模式是ECB。解密出来的字符并不是我们的密码,看着像是md5过的,用 echo -n xxxxxx | md5sum 把密码md5一下,对上了。看来服务端校验的是单次md5后的密码。
到这里登录逻辑已经搞清了,但是iOS捷径无法实现AES加密,单纯依托捷径来实现开门已经不可行了,需要搭建一个后端服务来计算密文。既然躲不过麻烦要搭建服务,不如把登录、开门整个流程都放在服务上,这样iOS捷径只需要一个请求就可以完成开门动作了。
考虑到登录开门的逻辑很简单,也就是3个HTTP请求+AES加密,直接在裸服务器上从0搭建步骤多成本高,要自己申请虚机、部署HTTP Server、Web App,还需要申请SSL证书,不仅初次搭建要搞个一两天,后续对机器和证书的维护也需要大量时间,成本极高。
最好是有服务能直接托管一段Python代码,第一时间想到的是Leancloud,一个Serverless服务提供商,但是实操过程中发现,由于政策要求Leancloud已经不提供域名了,绑定自己的域名也需要进行备案。这意味着只能选择一家海外Serverless服务商,看来看去AWS Lambda应该可以满足要求,试一下。
使用 AWS Lambda 搭建服务 AWS Lambda是一个Serverless服务,可以直接托管一段函数,省去配置服务和基础设施的麻烦。搭建一个Python的Serverless服务需要准备这么几件事:
新建函数,编写代码
添加API Gateway Trigger,确保函数可以通过HTTP请求调用
配置函数的运行环境,增加一个层(Layer),这个层里打包进AES加密需要的cryptography和HTTP请求需要的requests
1. 函数代码 首先上代码,需要填写自己的手机号、md5后的密码、设备ID(可以用Charles获取)等字段,粘贴到Lambda的在线编辑器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import jsonimport requestsimport base64import urllib.parsefrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesfrom cryptography.hazmat.primitives import paddingPHONE = '' PASSWORD_MD5 = '' DEVICE_ID = '' def encrypt (key, msg ): cipher = Cipher(algorithms.AES(str .encode(key)), modes.ECB()) encryptor = cipher.encryptor() padder = padding.PKCS7(128 ).padder() msg = padder.update(str .encode(msg)) + padder.finalize() ct = encryptor.update(msg) + encryptor.finalize() return base64.b64encode(ct) def lambda_handler (event, context ): resp = requests.post('https://api.lookdoor.cn:443/func/hjapp/user/v2/getPasswordAesKey.json?' ) cookie = resp.headers['set-cookie' ] aes_key = resp.json()['data' ]['aesKey' ] password_encypted = urllib.parse.quote_plus(encrypt(aes_key, PASSWORD_MD5)) url = f'https://api.lookdoor.cn:443/func/hjapp/user/v2/login.json?password={password_encypted} &deviceId={DEVICE_ID} &loginNumber={PHONE} &equipmentFlag=1' requests.post(url, headers={'cookie' : cookie}) equipment_id = event['queryStringParameters' ]['equipment_id' ] url = f'https://api.lookdoor.cn:443/func/hjapp/house/v1/pushOpenDoorBySn.json?equipmentId={equipment_id} ' resp = requests.post(url, headers={'cookie' : cookie}) return resp.json()
代码首先通过API获取AES_KEY和SESSION_ID,然后使用AES_KEY对密码进行加密,接下来调用登录接口将获取的SESSION_ID绑定到当前账户,接下来根据请求传入的设备ID(门的ID)来发送开门指令。
点击Deploy部署,然后运行测试,会出现超时的报错,这是因为Lambda函数默认的执行器内存大小是128MB,超时时间是3s,在配置页面把内存改大一些,超时时间设置为10s就可以了。
2. 添加 API Gateway Trigger 一个Lambda函数可以被多种形式触发执行,因为要使用捷径通过HTTP请求调用,所以加一个API Gateway Trigger,添加后会自动为函数生成一个URL,通过这个URL就可以直接调用函数。
3. 添加包含依赖的 Layer 代码中使用了 requests 和 cryptography 这两个第三方库,Lambda不支持使用pip直接安装这些依赖,而是需要我们在把依赖打成zip包上传成为容器的一层Layer,添加到函数镜像中。需要注意的是,Lambda函数执行的环境是Linux,对于cryptography这个库需要打包Linux版的才可以正常使用。
由于日常使用的是Mac,所以在AWS上申请一台Ubuntu 20的EC2实例,登录实例后使用如下命令安装依赖,并打包成zip文件:
1 2 3 4 mkdir python pip install -t python cryptography pip install -t python requests zip -r python/*
在AWS上创建一个新的Layer,并将生成的python.zip上传到Layer上。尝试通过URL访问写好的Lambda函数,可以看到开门指令已经成功下发。
配置iOS捷径 打开iOS捷径App,创建一个新捷径,搜索Get contents of这个动作,填入Lambda函数的URL和门的ID。由于API Gateway并没有配置认证,所以其他参数默认即可。如果有安全方面的顾虑,可以自己实现一个简单的Token认证或添加Lambda提供的JWT认证。点击执行,接口返回成功,证明整个流程已经跑通,以后就可以用这个捷径给自己和外卖小哥开门了。
总结 一开始本想用自定义一个iOS捷径的方式来实现一键开门禁,但为了实现SESSION_ID自动更新,不得不基于AWS Lambda搭了一个后端服务来模拟App的行为,所幸AWS Lambda提供了低成本的构建方案,包括搭建服务和配置SSL证书都可以几乎0成本的完成,免费套餐政策也能让这个服务长期跑着而不产生任何实际花费。