Sitelet https://github.com/python-kasa/python-kasa/pull/1731
Skip to content

fix: use KLAP v2 transport for IOT devices with login version 2 - #1731

Open
bm1549 wants to merge 1 commit into
python-kasa:masterfrom
bm1549:fix/iot-klap-login-v2
Open

fix: use KLAP v2 transport for IOT devices with login version 2#1731
bm1549 wants to merge 1 commit into
python-kasa:masterfrom
bm1549:fix/iot-klap-login-v2

Conversation

@bm1549

@bm1549 bm1549 commented Jul 26, 2026

Copy link
Copy Markdown

Summary

Some IOT-family devices keep the legacy IOT command set after a firmware
update but upgrade their KLAP authentication to login version 2 (sha256
hashing). get_protocol maps every IOT.KLAP device to the v1
KlapTransport (md5 hashing), so the handshake response never matches and
authentication fails with:

Device response did not match our challenge on ip <x>, check that your
e-mail and password (both case-sensitive) are correct.

This affects at least the EP10 plug and the HS300 power strip once their
firmware advertises login_version: 2.

Fix

Select KlapTransportV2 for IOT.KLAP devices that advertise
login_version >= 2, matching the transport already used for SMART.KLAP.
Devices with login version 1 (or unset) are unchanged and continue to use the
v1 KlapTransport.

Testing

Verified against a real EP10(US) smart plug running firmware
1.1.1 Build 250908, which advertises IOT.SMARTPLUGSWITCH / KLAP /
login_version: 2 in its discovery result:

  • Before: IotProtocol + KlapTransport (v1/md5) → handshake fails with
    the "did not match our challenge" error.
  • With KlapTransportV2 (v2/sha256): the same credentials authenticate
    and a full update() returns valid get_sysinfo state.
  • After the fix: get_protocol() selects KlapTransportV2 for this
    device and update() succeeds end to end.

Added a test_get_protocol parametrization (iot-klap-v2) asserting that
IOT.KLAP with login_version=2 resolves to IotProtocol +
KlapTransportV2, while the existing login-version-1 case still resolves to
KlapTransport. Full test_get_protocol suite passes; ruff check and
ruff format are clean.

Fixes home-assistant/core#177207

@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.30%. Comparing base (a29d061) to head (ff11447).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1731      +/-   ##
==========================================
+ Coverage   93.29%   93.30%   +0.01%     
==========================================
  Files         157      157              
  Lines        9932     9934       +2     
  Branches     1022     1023       +1     
==========================================
+ Hits         9266     9269       +3     
  Misses        471      471              
+ Partials      195      194       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bm1549
bm1549 force-pushed the fix/iot-klap-login-v2 branch from e5fd5d7 to 168336b Compare July 26, 2026 04:12
Some IOT-family devices (e.g. the EP10 plug and HS300 strip) keep the
legacy IOT command set after a firmware update but upgrade their KLAP
authentication to login version 2 (sha256 hashing). The device factory
mapped every IOT.KLAP device to the v1 KlapTransport (md5 hashing), so
the handshake response never matched and authentication failed with
"Device response did not match our challenge".

Select KlapTransportV2 for IOT.KLAP devices that advertise login
version >= 2, matching the transport already used for SMART.KLAP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bm1549
bm1549 force-pushed the fix/iot-klap-login-v2 branch from 168336b to ff11447 Compare July 26, 2026 04:13
@emmjaibi

emmjaibi commented Aug 2, 2026

Copy link
Copy Markdown

Thanks for tracking this down and helping to figure out what is going on. I can confirm the underlying "IOT.KLAP hardcoded to v1" bug is real and matches what I'm seeing on two HS103(US) units, HW Ver 5.0.

However, I don't think login_version alone is sufficient to pick the right transport, and this patch as written would regress previously-working devices. Here's the evidence:

Device A — HS103(US), HW 5.0, freshly factory-reset and re-paired today (post Android Kasa app update):

json
"mgt_encrypt_schm": {
  "encrypt_type": "KLAP",
  "new_klap": 1,
  "ANS": false,
  "http_port": 80,
  "lv": 2
}

Fails against stock 0.10.2 (KlapTransport/v1/md5) with the "did not match our challenge" error. Succeeds once patched to force KlapTransportV2 — matches your PR's finding exactly.

Device B — same model/hardware/firmware generation (HS103(US), HW 5.0), same TP-Link account, paired months ago, working correctly against stock 0.10.2 (KlapTransport/v1/md5) until today:

json
"mgt_encrypt_schm": {
  "encrypt_type": "KLAP",
  "new_klap": 1,
  "ANS": true,
  "http_port": 80,
  "lv": 2
}

Identical new_klap/lv values to Device A. After applying this patch (forcing KlapTransportV2 for any IOT.KLAP + lv >= 2), Device B now fails authentication with the same error — it needs the old v1/md5 transport, not v2.

So both devices advertise the same discovery-time capability flags, but apparently have different actual stored credential hashes on-device (presumably determined by whichever app/firmware version was used at the time each device was originally paired, not by current firmware capability). Discovery doesn't expose anything that distinguishes them (Device B has a real owner hash, Device A didn't until re-pairing), but I don't know if that's the case always or just coincidental to my specific case.

Given that, a static login_version-based rule can't safely choose the transport for both cases because it always breaks one class of device or the other. What about trying the try_connect_all() brute-force approach mentioned in the original analysis (trying both v1 and v2 and using whichever succeeds), rather than a single discovery-time decision?

I'm now annoyingly stuck with one new device happily working with the patched version, or the original one working with the stock code. Both bought and shipped in same 2-pack box! :) So silly.

@emmjaibi

emmjaibi commented Aug 2, 2026

Copy link
Copy Markdown

BTW - I removed the Device B (originally paired months ago) which had failed with patch in place using KASA App. I then re-added the device via Kasa App and the patched version now works correctly. So now both Device A and B are working with your patched version. Seems the time the device is initialized plays a role in compatibility with the protocol/authentication changes.

@iamteedoh

Copy link
Copy Markdown

Confirmed this fixes the handshake on two HS300(US) hw2.0 units (fw 1.1.2 Build 241220,
IOT.SMARTPLUGSWITCH / KLAP / login_version: 2). Thanks for tracking it down.

Heads up that multi outlet strips need a second fix on top of this one: with the V2 transport
selected they authenticate fine, but get_device_class_from_family() returns IotPlug for
IOT.SMARTPLUGSWITCH, so an HS300 is driven as a single plug and every outlet dies on
KeyError: 'relay_state'. The strip aware get_device_class_from_sys_info() path is gated on
XorTransport in _connect(), so KLAP devices never reach it.

Details and a verified local fix here: #1604 (comment)

Not asking you to widen this PR. I'm just flagging so it doesn't look unfixed for strip owners
once this lands.

@ophirr

ophirr commented Aug 13, 2026

Copy link
Copy Markdown

Confirming this fixes HS103(US) hw 5.0 on firmware 1.1.3 Build 250908 Rel.112508.

Before (stock python-kasa, via Home Assistant 2026.8.1)

Both plugs stopped working immediately after a firmware update offered by the Kasa
app, and continued to work in the Kasa app throughout. Home Assistant’s config flow
failed with:

Device response did not match our challenge on ip 192.168.4.52,
check that your e-mail and password (both case-sensitive) are correct.

The credentials were correct. Deleting and re-adding the integration,
re-provisioning the plugs in the Kasa app, and disabling account 2FA all made no
difference, which is consistent with the transport rather than the secret being
wrong.

The coordinator error before the entries were removed is a useful signature, since
it distinguishes this from a WiFi problem. It is ECONNREFUSED, not a timeout:

Unable to connect to the device: 192.168.4.52:9999:
[Errno 111] Connect call failed ('192.168.4.52', 9999)
Discovery fingerprint

Unauthenticated discovery already reports the deciding field, so this needs no
credentials to reproduce:

192.168.4.52 IotSmartPlugSwitch Klap login_version = 2
192.168.4.33 IotSmartPlugSwitch Klap login_version = 2
192.168.4.222 IotSmartPlugSwitch Xor login_version = None <- control

After (this PR’s head)

Installed with:

pip install "git+https://github.com/python-kasa/python-kasa.git@refs/pull/1731/head"
Both plugs authenticate and land on the v2 transport:

XXX1 HS103 hw5.0 sw 1.1.3 Build 250908 Klap lv=2 KlapTransportV2 OK
XXX2 HS103 hw5.0 sw 1.1.3 Build 250908 Klap lv=2 KlapTransportV2 OK
XXX3 HS103 hw3.0 sw 1.0.4 Build 210414 Xor lv=n/a XorTransport OK

The third line is a control on the same model at an older hardware revision and
legacy firmware. It still selects XorTransport and still works, so the change
does not disturb pre-KLAP devices on the same network.

Scope of this report

Two single-outlet plugs only. This does not exercise the multi-outlet strip
behaviour raised earlier in the thread, and says nothing about the
credential-initialisation-timing question, since I only have devices that are
consistent at discovery time.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TP-Link HS300 fails authentication with "Device response did not match our challenge" after 2026.7.4 update — resolved by rolling back to 2026.7.3

4 participants