通过小米路由器api实现登录和获取信息

📒 笔记 · 2024-02-15

通过小米路由器api实现小米路由器的登录和实时信息的获取。文章中代码以Golang为例。


使用到的api:

  1. http://<ip>/cgi-bin/luci/api/xqsystem/init_info

该api不需要登录,可以查看:

  • 路由器的名称和型号
  • 连接设备的数量
  • 加密方式等信息
  1. http://<ip>/cgi-bin/luci/api/xqsystem/login

登录接口的api,登录成功后可以得到token

  1. http://<ip>/cgi-bin/luci/;stok=<token>/api/misystem/status

查看路由器运行状态,可以得到连接的设备的信息,路由器的实时网速,流量情况,cpu的情况和温度,运行时间,内存使用情况

登录

登录参数

小米路由器登陆的api是:http://192.168.31.1/cgi-bin/luci/api/xqsystem/login

登录发送post请求,通过浏览器抓包可以看到携带4个登陆的参数;

  • username:小米路由器默认admin
  • password:经过加密规则加密后的密码
  • logtype:默认为2
  • nonce:携带着设备信息,时间,随机数的字符串

image

登录得到token

  1. 获取加密规则

小米路由器不同的型号有两种不同的加密规则:

  • 第一种是旧的加密方式:sha1加密
  • 第二种是新加密方式:sha256加密

通过访问apihttp://<ip>/cgi-bin/luci/api/xqsystem/init_info获得加密规则。

func GetName(ip string) (bool, string, string) {
    url := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/init_info", ip)
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("http.Get", err)
    }
    pageBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("io.ReadAll", err)
    }
    resultMap := make(map[string]interface{})
    err = json.Unmarshal(pageBytes, &resultMap)
    if err != nil {
        fmt.Println("json.Unmarshal", err)
    }
    // 路由器名称
    routerName := resultMap["routername"].(string)
    // 路由器型号
    hardwareName := resultMap["hardware"].(string)
    // 获取加密方式
    encryptMode, ok := resultMap["newEncryptMode"].(float64)
    if !ok {
        fmt.Println("使用旧加密模式")
        return false, routerName, hardwareName
    }
    if encryptMode != 0 {
        fmt.Println("使用新加密模式")
        return true, routerName, hardwareName
    }
    return false, routerName, hardwareName
}
  1. 获得参数nonce

nonce参数的值主要由四部分组成:类型的值、设备的id、时间戳、随机数

其中设备id可以省略,也可以查看登录页面源代码中的 deviceId = 获得:

image

生成nonce的值的golang代码为:

func createNonce() string {
    typeVar := 0
    deviceID := ""
    timeVar := int(time.Now().Unix())
    randomVar := rand.Intn(10000)
    return fmt.Sprintf("%d_%s_%d_%d", typeVar, deviceID, timeVar, randomVar)
}
  1. 获得加密后的密码:

查看小米路由器登录页面的源代码,可以看到两种加密方式的规则:

image

将两种加密方式转换为golang代码:

  • 旧加密方式:nonce为前面生成的值,key的值是登陆页面源代码中Encrypt中的key的值。
func hashPassword(pwd string, nonce string, key string) string {
    pwdKey := pwd + key
    pwdKeyHash := sha1.New()
    pwdKeyHash.Write([]byte(pwdKey))
    pwdKeyHashStr := fmt.Sprintf("%x", pwdKeyHash.Sum(nil))

    noncePwdKey := nonce + pwdKeyHashStr
    noncePwdKeyHash := sha1.New()
    noncePwdKeyHash.Write([]byte(noncePwdKey))
    noncePwdKeyHashStr := fmt.Sprintf("%x", noncePwdKeyHash.Sum(nil))
    return noncePwdKeyHashStr
}
  • 新加密方式
func newhashPassword(pwd string, nonce string, key string) string {
    pwdKey := pwd + key
    pwdKeyHash := sha256.Sum256([]byte(pwdKey))
    pwdKeyHashStr := hex.EncodeToString(pwdKeyHash[:])

    noncePwdKey := nonce + pwdKeyHashStr
    noncePwdKeyHash := sha256.Sum256([]byte(noncePwdKey))
    noncePwdKeyHashStr := hex.EncodeToString(noncePwdKeyHash[:])

    return noncePwdKeyHashStr
}
  1. 登录小米路由器,获得token
func GetToken(ip string, password string, key string) string {
    ourl := fmt.Sprintf("http://%s/cgi-bin/luci/api/xqsystem/login", ip)

    nonce := createNonce()
    // 我的路由器使用旧的加密方式
    hashedPassword := hashPassword(password, nonce, key)

    params := url.Values{}
    params.Set("username", "admin")
    params.Set("password", hashedPassword)
    params.Set("logtype", "2")
    params.Set("nonce", nonce)

    resp, err := http.PostForm(ourl, params)
    if err != nil {
        fmt.Println("http.PostForm", err)
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("io.ReadAll", err)
    }
    resultMap := make(map[string]interface{})
    err = json.Unmarshal(body, &resultMap)

    if err != nil {
        fmt.Println("json.Unmarshal", err)
    }
    var token string
    if resultMap["code"].(float64) == 0 {
        token = resultMap["token"].(string)
    } else {
        fmt.Println("登陆失败")
    }
    return token
}
  1. 携带token发送get请求,获得路由器实时状态
下述代码中只获取了部分状态信息。
func GetStatus(ip string, token string) map[string]interface{} {
    url := fmt.Sprintf("http://%s/cgi-bin/luci/;stok=%s/api/misystem/status", ip, token)
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("http.Get", err)
    }
    pageBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("io.ReadAll", err)
    }
    defer resp.Body.Close()
    var resultMap map[string]interface{}
    err = json.Unmarshal(pageBytes, &resultMap)
    if err != nil {
        fmt.Println("json.Unmarshal", err)
    }
    status := make(map[string]interface{})
    // 总上传量
    upload, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["upload"].(string), 64)
    // 总下载量
    download, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["download"].(string), 64)
    // 下载速度
    downSpeed, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["downspeed"].(string), 64)
    // 上传速度
    upSpeed, _ := strconv.ParseFloat(resultMap["wan"].(map[string]interface{})["upspeed"].(string), 64)
    // cpu占用率
    cpuload := resultMap["cpu"].(map[string]interface{})["load"].(float64)
    // cpu温度
    cputp := int(resultMap["temperature"].(float64))
    // 内存占用
    memusage := resultMap["mem"].(map[string]interface{})["usage"].(float64)
    upTime, _ := strconv.ParseFloat(resultMap["upTime"].(string), 64)

    // fmt.Println(utils.FormatSpeed(upload), download, downSpeed, upSpeed, cpuload, cputp, memusage, upTime)
    status["upload"] = utils.FormatSpeed(upload)
    status["download"] = utils.FormatSpeed(download)
    status["downSpeed"] = utils.FormatSpeed(downSpeed)
    status["upSpeed"] = utils.FormatSpeed(upSpeed)
    status["cpuload"] = math.Round(cpuload*10) / 10
    status["cputp"] = cputp
    status["memusage"] = memusage
    status["upTime"] = fmt.Sprint(math.Round(upTime/3600/24*10)/10, "天")
    return status
}

FormatSpeed函数:

func FormatSpeed(speed float64) string {
    switch {
    case speed >= 1024*1024*1024:
        return fmt.Sprintf("%.2fG", speed/(1024*1024*1024))
    case speed >= 1024*1024:
        return fmt.Sprintf("%.2fM", speed/(1024*1024))
    case speed >= 1024:
        return fmt.Sprintf("%.2fKB", speed/1024)
    default:
        return fmt.Sprintf("%.2fB", speed)
    }
}
Go
Theme Jasmine by Kent Liao