-
Notifications
You must be signed in to change notification settings - Fork 15
HMAC Request Signing
piekstra edited this page Feb 7, 2026
·
1 revision
All requests to the TP-Link V2 API require HMAC-SHA1 request signing. This was reverse-engineered from the Kasa Android app's Retrofit interceptor (C29915r).
These are hardcoded in the Kasa Android APK (sdkconfig.xml):
| Key | Value |
|---|---|
| AccessKey | e37525375f8845999bcc56d5e6faa76d |
| SecretKey | 314bc6700b3140ca80bc655e527cb062 |
These are the same for all users - they authenticate the app itself, not individual users.
import hashlib, base64
body_bytes = body_json.encode('utf-8')
content_md5 = base64.b64encode(hashlib.md5(body_bytes).digest()).decode()If the body is empty, use "{}" as the input:
content_md5 = base64.b64encode(hashlib.md5(b"{}").digest()).decode()import uuid
nonce = str(uuid.uuid4())timestamp = "9999999999" # Hardcoded in the app
url_path = "/api/v2/account/login" # Just the path, no query params
sig_string = f"{content_md5}\n{timestamp}\n{nonce}\n{url_path}"For device operations on the root path, url_path = "/".
Important: The URL path is the encodedPath() from the URL object - just the path portion, never including query parameters.
import hmac, hashlib
signature = hmac.new(
SECRET_KEY.encode(),
sig_string.encode(),
hashlib.sha1
).hexdigest()auth_header = f"Timestamp={timestamp}, Nonce={nonce}, AccessKey={ACCESS_KEY}, Signature={signature}"Add these headers to every request:
headers = {
"Content-Type": "application/json;charset=UTF-8",
"Content-MD5": content_md5,
"X-Authorization": auth_header,
}import json, hashlib, hmac, base64, uuid
ACCESS_KEY = "e37525375f8845999bcc56d5e6faa76d"
SECRET_KEY = "314bc6700b3140ca80bc655e527cb062"
def sign_request(body_json: str, url_path: str) -> dict:
"""Compute signing headers for a V2 API request."""
# Content-MD5
content_md5 = base64.b64encode(
hashlib.md5(body_json.encode()).digest()
).decode()
# Nonce & timestamp
nonce = str(uuid.uuid4())
timestamp = "9999999999"
# Signature
sig_string = f"{content_md5}\n{timestamp}\n{nonce}\n{url_path}"
signature = hmac.new(
SECRET_KEY.encode(), sig_string.encode(), hashlib.sha1
).hexdigest()
return {
"Content-MD5": content_md5,
"X-Authorization": (
f"Timestamp={timestamp}, Nonce={nonce}, "
f"AccessKey={ACCESS_KEY}, Signature={signature}"
),
}-
Interceptor:
sources/p401o7/C29915r.java- Retrofit/OkHttp interceptor that adds signing headers -
Helper:
sources/com/tplinkra/tplink/appserver/internal/AppServerHttpClientHelper.java- Signature computation for the SDK code path -
Config:
resources/assets/sdkconfig.xml- AccessKey and SecretKey values
- The timestamp
9999999999is hardcoded and never changes. The server does not validate it as a real timestamp. - The nonce is a random UUID generated per-request. The server likely uses it for replay protection.
- The AccessKey/SecretKey pair is specific to the Kasa Android app. Other TP-Link apps (e.g., Tapo) use different keys.