Skip to content

IOT devices with KLAP encryption and login_version=2 use wrong transport (KlapTransport instead of KlapTransportV2) #1648

@lasdolphin

Description

@lasdolphin

IOT devices with KLAP encryption and login_version=2 use wrong transport (KlapTransport instead of KlapTransportV2)

Bug Description

HS300 (and possibly other IOT devices) with firmware that requires KLAP v2 authentication fail to connect with AuthenticationError: Device response did not match our challenge.

The root cause is that device_factory.py always uses KlapTransport for IOT devices with KLAP encryption, ignoring the login_version parameter from discovery. Devices with login_version=2 require KlapTransportV2 which uses a different hash algorithm.

Environment

  • python-kasa version: 0.10.2
  • Python version: 3.12
  • OS: Linux (Ubuntu)
  • Device: HS300 Power Strip (hw_ver 2.0)

Discovery Response

The device correctly reports login_version: 2 in discovery:

{
    'device_model': 'HS300(US)',
    'device_type': 'IOT.SMARTPLUGSWITCH',
    'mgt_encrypt_schm': {
        'encrypt_type': 'KLAP',
        'http_port': 80,
        'lv': 2,  # <-- login_version = 2
    },
    ...
}

Root Cause

In device_factory.py line 231:

supported_device_protocols: dict[str, tuple[type[BaseProtocol], type[BaseTransport]]] = {
    "IOT.XOR": (IotProtocol, XorTransport),
    "IOT.KLAP": (IotProtocol, KlapTransport),  # <-- Always uses KlapTransport (v1)
    "SMART.AES": (SmartProtocol, AesTransport),
    "SMART.KLAP": (SmartProtocol, KlapTransportV2),  # <-- SMART devices use v2
    ...
}

The protocol selection key is built from device_family + encryption_type only:

protocol_transport_key = protocol_name + "." + ctype.encryption_type.value

The login_version is not considered, so all IOT KLAP devices get KlapTransport regardless of their actual requirements.

Hash Algorithm Difference

  • KlapTransport (v1): MD5(MD5(username) + MD5(password))
  • KlapTransportV2 (v2): SHA256(SHA1(username) + SHA1(password))

When the wrong transport is used, the handshake hash comparison fails:

Expected auth_hash (KlapTransportV2): 876584dd4d497651c678a8c3d6cb619c...
Actual _local_auth_hash (KlapTransport): 53a1c1366c1e8e04719da24462371caa

Steps to Reproduce

import asyncio
from kasa import Discover, Credentials

async def main():
    creds = Credentials("user@example.com", "password")
    devices = await Discover.discover(
        target="10.0.0.255",
        username=creds.username,
        password=creds.password,
    )
    
    # HS300 with KLAP v2
    if "10.0.0.110" in devices:
        dev = devices["10.0.0.110"]
        print(f"login_version: {dev.config.connection_type.login_version}")
        print(f"Transport: {type(dev.protocol._transport).__name__}")
        
        try:
            await dev.update()
        except Exception as e:
            print(f"Error: {e}")

asyncio.run(main())

Output:

login_version: 2
Transport: KlapTransport  # <-- Should be KlapTransportV2!
Error: Device response did not match our challenge on ip 10.0.0.110...

Workaround

Manually create IotProtocol with KlapTransportV2:

from kasa.transports.klaptransport import KlapTransportV2
from kasa.protocols import IotProtocol
from kasa.iot import IotStrip

# Create protocol with correct transport
protocol = IotProtocol(transport=KlapTransportV2(config=device_config))
device = IotStrip(host=ip, protocol=protocol)
await device.update()  # Works!

Suggested Fix

Modify get_protocol() in device_factory.py to check login_version for IOT KLAP devices:

def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol | None:
    ctype = config.connection_type
    
    # ... existing code ...
    
    # For IOT devices with KLAP and login_version >= 2, use KlapTransportV2
    if (ctype.device_family.value.startswith("IOT.") and 
        ctype.encryption_type == DeviceEncryptionType.Klap and
        ctype.login_version is not None and 
        ctype.login_version >= 2):
        return IotProtocol(transport=KlapTransportV2(config=config))
    
    # ... rest of existing code ...

Or add a new protocol transport key:

supported_device_protocols = {
    "IOT.XOR": (IotProtocol, XorTransport),
    "IOT.KLAP": (IotProtocol, KlapTransport),
    "IOT.KLAP.V2": (IotProtocol, KlapTransportV2),  # <-- New
    ...
}

Additional Context

This issue appeared after a power outage caused the HS300 devices to reboot. The devices had been updated to newer firmware that uses KLAP v2 authentication. Before the reboot, existing sessions may have been cached, masking the issue.

The same credentials work correctly in the official Kasa app, confirming the credentials are valid.


Related: This may affect other IOT devices (not just HS300) that have been updated to firmware requiring KLAP v2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions