我住的小區使用了一個叫守望領域的智能門禁系統,可以通過手機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的在線編輯器中。
import json
import requests
import base64
import urllib.parse
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
PHONE = ''
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文件:
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成本的完成,免費套餐政策也能讓這個服務長期跑着而不產生任何實際花費。