Skip to content

Instantly share code, notes, and snippets.

@ancientGlider
Last active March 24, 2025 17:07
Show Gist options
  • Save ancientGlider/e72cdaa2daf0af5f8d80f53fea4666be to your computer and use it in GitHub Desktop.
Save ancientGlider/e72cdaa2daf0af5f8d80f53fea4666be to your computer and use it in GitHub Desktop.
Authentication for Keenetic routers for work with CLI via REST API (Python)
# -*- coding: utf-8 -*-
"""
Данные с адресом, логином, паролем хранятся в конфигурационном файле следующего вида:
[Router]
ip_addr = 192.168.1.1:8080
login = admin
passw = anyPassword
"""
CONFIG_FILE_NAME = "keenetic.conf" # имя конфигурационного файла
import configparser
import requests
import hashlib
cookies_current = None
session = requests.session() # заводим сессию глобально чтобы отрабатывались куки
def keen_auth(login, passw): # авторизация на роутере
response = keen_request(ip_addr, "auth")
if response.status_code == 401:
md5 = login + ":" + response.headers["X-NDM-Realm"] + ":" + passw
md5 = hashlib.md5(md5.encode('utf-8'))
sha = response.headers["X-NDM-Challenge"] + md5.hexdigest()
sha = hashlib.sha256(sha.encode('utf-8'))
response = keen_request(ip_addr, "auth", {"login": login, "password": sha.hexdigest()})
if response.status_code == 200:
return True
elif response.status_code == 200:
return True
else:
return False
def keen_request(ip_addr, query, post = None): # отправка запросов на роутер
global session
# конструируем url
url = "http://" + ip_addr + "/" + query
# если есть данные для запроса POST, делаем POST, иначе GET
if post:
return session.post(url, json=post)
else:
return session.get(url)
config = configparser.ConfigParser() # создаём объекта парсера
config.read(CONFIG_FILE_NAME) # читаем конфиг
ip_addr = config["Router"]["ip_addr"]
login = config["Router"]["login"]
passw = config["Router"]["passw"]
# тестируем
if keen_auth(login, passw):
response = keen_request(ip_addr, 'rci/show/interface/WifiMaster0');
print(response.text)
@notabene00
Copy link

Огромное спасибо!
Приобрел Keenetic Giga недавно, обнаружил, что для него нет device_tracker компонента для Home Assistant. До этого был Xiaomi MiR3G на padavan, использовал компонент asuswrt. Сейчас придется писать свое.
И пример аутентификации был очень кстати.
Снимок экрана 2021-02-25 в 15 23 38

@gotyefrid
Copy link

Переписал тоже самое на PHP

<?php

class KeeneticAPI
{
    private string $ip_addr;
    private string $login;
    private string $passw;
    private CurlHandle $session;
    private string $cookieJar;

    public function __construct(string $ip_addr, string $login, string $passw)
    {
        $this->ip_addr = $ip_addr;
        $this->login = $login;
        $this->passw = $passw;

        // Инициализация cURL сессии и файла для хранения cookie
        $this->cookieJar = tempnam(sys_get_temp_dir(), 'cookies_');
        $this->session = curl_init();
        $this->auth();
    }

    public function __destruct()
    {
        // Закрытие cURL сессии и удаление файла cookie
        curl_close($this->session);

        if (file_exists($this->cookieJar)) {
            unlink($this->cookieJar);
        }
    }

    private function auth()
    {
        $response = $this->request("auth");

        if ($response['status_code'] == 401) {
            // Извлечение заголовков
            $headers = [];
            
            foreach (explode("\r\n", $response['headers']) as $header_line) {
                $parts = explode(": ", $header_line, 2);
                if (count($parts) == 2) {
                    $headers[$parts[0]] = $parts[1];
                }
            }

            $ndm_realm = isset($headers['X-NDM-Realm']) ? $headers['X-NDM-Realm'] : '';
            $ndm_challenge = isset($headers['X-NDM-Challenge']) ? $headers['X-NDM-Challenge'] : '';

            // Вычисление хешей
            $md5_input = $this->login . ':' . $ndm_realm . ':' . $this->passw;
            $md5_hash = md5($md5_input);

            $sha_input = $ndm_challenge . $md5_hash;
            $sha_hash = hash('sha256', $sha_input);

            $postData = ["login" => $this->login, "password" => $sha_hash];

            $response = $this->request("auth", $postData);

            if ($response['status_code'] == 200) {
                return true;
            }
        } elseif ($response['status_code'] == 200) {
            return true;
        }

        return false;
    }

    public function request($query, $post = null)
    {
        $url = "http://" . $this->ip_addr . "/" . $query;

        curl_setopt($this->session, CURLOPT_URL, $url);
        curl_setopt($this->session, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->session, CURLOPT_HEADER, true);
        curl_setopt($this->session, CURLOPT_COOKIEFILE, $this->cookieJar);
        curl_setopt($this->session, CURLOPT_COOKIEJAR, $this->cookieJar);

        if ($post !== null) {
            $jsonData = json_encode($post);
            curl_setopt($this->session, CURLOPT_POST, true);
            curl_setopt($this->session, CURLOPT_POSTFIELDS, $jsonData);
            curl_setopt($this->session, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        } else {
            curl_setopt($this->session, CURLOPT_HTTPGET, true);
            curl_setopt($this->session, CURLOPT_POST, false);
            curl_setopt($this->session, CURLOPT_HTTPHEADER, []);
        }

        $response = curl_exec($this->session);

        $header_size = curl_getinfo($this->session, CURLINFO_HEADER_SIZE);
        $header = substr($response, 0, $header_size);
        $body = substr($response, $header_size);

        $status_code = curl_getinfo($this->session, CURLINFO_HTTP_CODE);

        return [
            'status_code' => $status_code,
            'headers' => $header,
            'text' => $body,
        ];
    }
}

Использую для того чтобы через Алису выключать\включать VPN для телевизора

<?php
require('KeeneticAPI.php');

$api = new KeeneticAPI('192.168.1.1', 'admin', '12345678');
//on
$api->request('rci/ip/hotspot/host', [
    'mac'=> "11:11:11:11:11:11",
    'permit'=> true,
    'policy'=> "Policy0",
]);

//off
$api->request('rci/ip/hotspot/host', [
    'mac'=> "11:11:11:11:11:11",
    'permit'=> true,
    'policy'=> [
        'no' => true
    ]
]);

Спасибо!

@alekssamos
Copy link

@gotyefrid
А я себе статические маршруты настроил, теперь у меня Youtube идёт в VPN, а остальное напрямую..
Почему бы вам также не сделать?
Маршрутизация>Загрузить из файла > Интерфейс выбираете где у вас VPN, а файлы бирёте эти: https://github.com/RockBlack-VPN/ip-address/tree/main/Global/Youtube
И всё отлично работает.

@Amfiaros
Copy link

Amfiaros commented Feb 5, 2025

@alekssamos

А я себе статические маршруты настроил, теперь у меня Youtube идёт в VPN, а остальное напрямую..

Увы, домены по типу rr2---sn-oan31-5a5e.googlevideo.com периодически меняются. Потому приходится собирать велосипеды из костылей.

pc. Хмм, надо помониторить, вдруг и правда пул ip просто переименовывают...

@AntonBelgorod
Copy link

Переписал тоже самое на PHP

<?php

class KeeneticAPI
{
    private string $ip_addr;
    private string $login;
    private string $passw;
    private CurlHandle $session;
    private string $cookieJar;

    public function __construct(string $ip_addr, string $login, string $passw)
    {
        $this->ip_addr = $ip_addr;
        $this->login = $login;
        $this->passw = $passw;

        // Инициализация cURL сессии и файла для хранения cookie
        $this->cookieJar = tempnam(sys_get_temp_dir(), 'cookies_');
        $this->session = curl_init();
        $this->aut

Привет! Скажи куда вписывать свои данные от роутера. Спасибо

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment