Skip to content

HMAC Request Signing

piekstra edited this page Feb 7, 2026 · 1 revision

HMAC Request Signing

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).

Keys

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.

Signature Computation

1. Compute Content-MD5

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()

2. Generate Nonce

import uuid
nonce = str(uuid.uuid4())

3. Build Signature String

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.

4. Compute HMAC-SHA1

import hmac, hashlib

signature = hmac.new(
    SECRET_KEY.encode(),
    sig_string.encode(),
    hashlib.sha1
).hexdigest()

5. Build Authorization Header

auth_header = f"Timestamp={timestamp}, Nonce={nonce}, AccessKey={ACCESS_KEY}, Signature={signature}"

Request Headers

Add these headers to every request:

headers = {
    "Content-Type": "application/json;charset=UTF-8",
    "Content-MD5": content_md5,
    "X-Authorization": auth_header,
}

Complete Example

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}"
        ),
    }

Source in Decompiled APK

  • 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

Notes

  • The timestamp 9999999999 is 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.

Clone this wiki locally