本文档记录了对海康威视私有 WebSocket 协议栈的逆向分析过程,以及基于分析结果实现的 Python 客户端。
海康威视的 Web 视频流使用两层 WebSocket 连接:
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ │ Browser │ ──WS──▶ │ Proxy Server │ ──TCP──▶ │ Camera │ │ (h5player) │ ◀──WS─── │ (cloud relay) │ ◀─RTSP─ │ (device) │ └──────────────┘ └─────────────────┘ └──────────────┘
代理 URL 格式:
wss://<proxy_host>:<proxy_port>/proxy/<device_ip>:<device_port>/openUrl/<auth>
示例:
wss://example.com:6014/proxy/[1111::2222]:559/openUrl/auth_token
媒体端点 URL:
wss://<proxy_host>:<proxy_port>/media?version=0.1&cipherSuites=0&sessionID=&proxy=<device>
客户端发送标准 HTTP Upgrade 请求:
GET /media?version=0.1&cipherSuites=0&sessionID=&proxy=[ipv6]:559 HTTP/1.1 Host: example.com:6014 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: <base64_key> Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: v1.0.0
服务器响应:
HTTP/1.1 101 Switching Protocols Sec-WebSocket-Protocol: v1.0.0
发送 JSON 格式的认证信息:
{
"username": "admin",
"password": "",
"clientType": "web3.0",
"keyVersion": "v1",
"random": "uuid-without-dashes"
}
服务器响应认证后,下发密钥参数:
{
"PKD": "服务器下发的公钥数据",
"rand": "随机数",
"type": "keyExchange"
}
基于 h5player.min.js 和 Decoder.js 逆向分析:
1. password_md5 = MD5(password) 2. salt_data = SHA256(password_md5 + "rtsp" + username) 3. auth_data = SHA256(PKD + rand) 4. combined = SHA256(salt_data + auth_data + salt_data) 5. secret_key = hex(combined)
Python 实现:
def generate_secret_key(PKD: str, rand: str, username: str, password: str) -> str:
password_md5 = hashlib.md5(password.encode()).digest()
salt_input = password_md5 + b"rtsp" + username.encode()
salt_data = hashlib.sha256(salt_input).digest()
auth_input = PKD.encode() + rand.encode()
auth_data = hashlib.sha256(auth_input).digest()
combined_input = salt_data + auth_data + salt_data
combined = hashlib.sha256(combined_input).digest()
return combined.hex()
服务器发送加密的视频帧:
┌─────────────────────────────────────────────────────┐ │ 0x24 0x34 │ 0x00 0x00 │ [4B length] │ ... │ │ 固定头 │ 保留 │ 数据长度 │ 负载数据 │ └─────────────────────────────────────────────────────┘
0x01:关键帧 (I-Frame)0x02:P-Frame| 类型 | 名称 | 描述 |
|---|---|---|
| 0x01 | MSG_TYPE_HELLO | 握手 |
| 0x02 | MSG_TYPE_AUTH_REQUEST | 认证请求 |
| 0x03 | MSG_TYPE_AUTH_RESPONSE | 认证响应 |
| 0x04 | MSG_TYPE_KEY_EXCHANGE | 密钥交换 |
| 0x05 | MSG_TYPE_SESSION_ERROR | 会话错误 |
| 0x06 | MSG_TYPE_KEEPALIVE | 心跳 |
| 0x20 | 开始预览 | 开始媒体流预览 |
| 0x40 | MSG_TYPE_VIDEO_DATA | 视频数据 |
| 0x41 | MSG_TYPE_AUDIO_DATA | 音频数据 |
所有消息使用以下格式:
┌──────────┬────────────┬─────────────────────────────┐ │ 1 Byte │ 4 Bytes │ N Bytes │ │ Type │ Length │ Payload │ └──────────┴────────────┴─────────────────────────────┘
客户端发送到服务器的帧必须使用掩码:
服务器发送到客户端的帧:
# 进入项目目录
cd hik_ws_client
# 运行演示程序
python3 demo.py "wss://example.com:6014/proxy/[1111::2222]:559/openUrl/auth_token"
python3 demo.py "wss://example.com:6014/proxy/[1111::3333]:559/openUrl/auth_token_b"
# 指定用户名密码
python3 demo.py "wss://..." --username admin --password yourpassword
# 启用详细输出
python3 demo.py "wss://..." -v
import asyncio
from hik_ws_client import HikMediaClient, HikConfig, parse_proxy_url
async def main():
# 方式1: 从 URL 解析配置
config = parse_proxy_url("wss://host:port/proxy/ip:port/openUrl/auth")
# 方式2: 直接创建配置
config = HikConfig(
proxy_host="host.com",
proxy_port=6014,
proxy_path="/proxy/ip:port",
device_ip="192.168.1.64",
device_port=8000,
username="admin",
password=""
)
# 创建客户端
client = HikMediaClient(config)
# 设置回调
def on_video(data):
print(f"Video: {len(data)} bytes")
client.on_video_data = on_video
# 运行
await client.run()
asyncio.run(main())
# 使用 --save-frames 选项保存原始帧数据
python3 demo.py "wss://..." --save-frames --output-dir ./frames
hik_ws_client/ ├── hik_ws_client.py # 核心协议实现 ├── demo.py # 演示程序 ├── README.md # 本文档 └── output/ # 视频帧输出目录
# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate
# 安装依赖
pip install pycryptodome
| 包名 | 用途 | 版本要求 |
|---|---|---|
| pycryptodome | AES/RSA 加密 | >= 3.23.0 |
| (标准库) asyncio | 异步IO | Python 3.8+ |
| (标准库) json | JSON解析 | Python 3.8+ |
| (标准库) hashlib | MD5/SHA计算 | Python 3.8+ |
| (标准库) hmac | HMAC计算 | Python 3.8+ |
| (标准库) base64 | Base64编码 | Python 3.8+ |
| (标准库) struct | 二进制打包 | Python 3.8+ |
| (标准库) socket | 网络通信 | Python 3.8+ |
# 方法1: 使用虚拟环境
cd /path/to/hik_ws_client
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
当前版本实现了视频流的接收和原始数据保存,但未包含视频解码。获取的原始数据是 RTP 打包的 G.711 音频和视频编码数据(如 H.264/H.265)。
# 保存原始帧数据到文件
python3 demo.py "wss://..." --save-frames --output-dir ./frames
保存的原始帧可用于后续解码分析。
import asyncio
from hik_ws_client import HikMediaClient, HikConfig, parse_proxy_url
async def main():
url = "wss://..."
config = parse_proxy_url(url)
client = HikMediaClient(config)
# 设置视频帧回调
def on_video_frame(data: bytes):
# data 是原始视频帧数据
print(f"Received video frame: {len(data)} bytes")
# 在这里可以实现解码和显示
client.on_video_data = on_video_frame
# 运行
await client.run()
asyncio.run(main())
完整的视频显示需要:
示例框架:
import cv2
import numpy as np
def display_frame(encoded_data: bytes):
"""
需要实现:
1. RTP 解包
2. H.264/H.265 解码 (可使用 ffmpeg-python 或 decord)
3. OpenCV 显示
"""
# 这是一个框架,需要根据实际编码格式实现
pass
h5player.min.js:海康威视 Web 播放器核心代码Decoder.js:WebAssembly 解码器 JavaScript 接口DecodeWorker.js:解码器 Web Workerhik.txt:协议流程分析记录