网上大多数关于小米手环的auth协议都停留在6,最新的7手环相对之前的认证较为麻烦使用到了除aes之外的ecdh加密?

这里感谢https://github.com/patyork/miband-7-monitor/提供的协议代码参考

首先我们来看authenticate.js文件this.ECC_PUB_KEY_SIZE和this.ECC_PRV_KEY_SIZE分别为48和24具体为什么不知道。大概是逆向小米手环app得到的

this.pub_buf = Module._malloc(this.ECC_PUB_KEY_SIZE);
this.prv_buf = Module._malloc(this.ECC_PRV_KEY_SIZE);

获取变量内存地址赋值

this.pub = Module.HEAPU8.subarray(
    this.pub_buf,
    this.pub_buf + this.ECC_PUB_KEY_SIZE
);
this.prv = Module.HEAPU8.subarray(
    this.prv_buf,
    this.prv_buf + this.ECC_PRV_KEY_SIZE
);

Module.HEAPU8.subarray引用了Module['HEAPU8'] = HEAPU8 = new Uint8Array(buf); 等同于创建了一个新的uint8array,并且从this.pub_buf取到this.pub_buf + this.ECC_PUB_KEY_SIZE,这样修改pub和prv会同样影响module中的值,this.prv同理

crypto.getRandomValues(this.prv);

设置this.prv为随机数

Module._ecdh_generate_keys(this.pub_buf, this.prv_buf);

生成this.pub的内容

await this.Band.writeChunkedValue(
    this.Band.Chars.CHUNKED_WRITE,
    CHUNK_ENDPOINTS.AUTH,
    this.Band.getNextHandle(),
    Uint8Array.from(auth)
)

开始发送消息

// 传递数据的大小
let remaining = data.length;
// 循环的次数
let count = 0;
// 固定头部的大小
let header_size = 11;
// 最大能发送的包长度
const mMTU = 23;
console.log("剩余包大小:%d,总数据大小",remaining,data)
while (remaining > 0) {
    const MAX_CHUNKLENGTH = mMTU - 3 - header_size;
    console.log("最大发送的包的大小",MAX_CHUNKLENGTH)
    const copybytes = Math.min(remaining, MAX_CHUNKLENGTH);
    console.log("获取剩余未发送数据%d和最大发送包%d大小之间最小的%d",remaining,MAX_CHUNKLENGTH,Math.min(remaining, MAX_CHUNKLENGTH))

    const chunk = new Uint8Array(copybytes + header_size);
    console.log("设置真实发送的包的大小,如果剩余数据小于最大发送包则用剩余数据长度加上固定头长度",chunk)

    let flags = base_flags;
    if (count == 0) {
        console.log("如果是第一次循环")
        // Endpoint 0x0a seems to take different flag, and that affects length?
        chunk[5] = data.length;
        chunk[6] = data.length >> 8; // 位移后为0
        chunk[7] = data.length >> 16; // 位移后为0
        chunk[8] = data.length >> 24; // 位移后为0
        chunk[9] = type;
        chunk[10] = type >> 8; // 位移后为0
        flags |= 0x01;
    }
    if (remaining <= MAX_CHUNKLENGTH) {
        // 如果发送的数据包小于最大能发送的,就设置flags
        flags |= 0x06; // last chunk?
        //# 0x07
    }
    console.log("flags",flags,typeof flags)
    chunk[0] = 0x03;
    chunk[1] = flags;
    chunk[2] = 0;
    chunk[3] = handle;
    chunk[4] = count;
    console.log("设置了固定头部后的包:",chunk)
    //#     [3, 7, 0, 8, 0, 1, 0, 0, 0, 40, 0, 0]

    chunk.set(
        data.slice(
            data.length - remaining,
            data.length - remaining + copybytes
        ),
        header_size // 这个似乎一直没有用到?
    );
    console.log("设置数据:",data.slice(
            data.length - remaining,
            data.length - remaining + copybytes
        ),
        header_size)
    //#     [3, 7, 0, 8, 0, 1, 0, 0, 0, 40, 0, 1
    console.log(chunk)
    console.log("Sending: " + toHexString(chunk));
    await char.writeValue(chunk);
    remaining -= copybytes;
    header_size = 5;
    count++;
    console.log("count",count,"remaining",remaining)
    console.log("==============================")
}

MAX_CHUNKLENGTH为能够传递的数据长度

copybytes如果剩余的数据小于最大能发送的数据,就把剩余的数据发送,而不是按最大发送数据将空的地方补0

chunk包大小

flags标志,告诉手环是第一次发送数据还是最后一次发送数据

chunk[0] = 0x03;
chunk[1] = flags;
chunk[2] = 0;
chunk[3] = handle;
chunk[4] = count;

这里是设置固定的头部,如果是第一次发送,那还需要占据更多的位置设置一些信息

chunk[5] = data.length;
chunk[6] = data.length >> 8; // 位移后为0
chunk[7] = data.length >> 16; // 位移后为0
chunk[8] = data.length >> 24; // 位移后为0
chunk[9] = type;
chunk[10] = type >> 8; // 位移后为0
flags |= 0x01; // 不可更改,尝试修改为1但无法登录
chunk.set(
    data.slice(
        data.length - remaining,
        data.length - remaining + copybytes
    ),
    header_size // 这个似乎一直没有用到?
);

设置剩余的空位置为数据,header_size似乎一直都没用到,但我没尝试删除它

当是最后一条数据包时就设置flags为7,我同样尝试修改flags = 7 但无法登录

if (remaining <= MAX_CHUNKLENGTH) {
    // 如果发送的数据包小于最大能发送的,就设置flags
    flags |= 0x06; // last chunk?
    //# 0x07
}

await char.writeValue(chunk); 写入数据包发送

remaining -= copybytes; 将总数据包长度减少本次发送的长度

header_size = 5; 第一次11字节长度的包发送完成,剩下包头部都是 5字节

count++; 计次加一 上方设置头部时有用到

综上所述,我尝试在windows平台下使用python语言的bleak库使用同样的数据包发送给手环,并且监听了 00000017-0000-3512-2118-0009af100700 characteristic,但是能够发送手环没有返回消息,我不知道是否是windows平台下的问题,因为我使用另一个蓝牙库时甚至搜索不到我的手环!如果您知道的话请在评论告诉我,不胜感激!

我仅仅研究了第一次发信,但这也非常接近答案了,因为我粗略了看了一下二次发信,仅仅是重新生成了rpub同时使用到了关键的authkey。

但我只是看他人代码而非逆向手环app,这样会存在许多困惑。同时我希望达成的效果可以通过用xposed框架实现,因此我也不会再继续研究了。如果您阅读了本文章并且愿意继续研究请欢迎在评论留下联系方式,我乐意与您交流。