From dcd9322cfe177dff1bfc342c09cbac4b39931a0b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Jan 2024 22:23:09 -1000 Subject: [PATCH 1/4] Reduce the number of times we recreate the cipher in klap --- kasa/klaptransport.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/kasa/klaptransport.py b/kasa/klaptransport.py index cd0e3de6b..14f478688 100644 --- a/kasa/klaptransport.py +++ b/kasa/klaptransport.py @@ -46,6 +46,7 @@ import hashlib import logging import secrets +import struct import time from pprint import pformat as pf from typing import Any, Dict, Optional, Tuple, cast @@ -66,6 +67,8 @@ ONE_DAY_SECONDS = 86400 SESSION_EXPIRE_BUFFER_SECONDS = 60 * 20 +PACK_SIGNED_LONG = struct.Struct(">l").pack + def _sha256(payload: bytes) -> bytes: digest = hashes.Hash(hashes.SHA256()) # noqa: S303 @@ -432,6 +435,8 @@ def __init__(self, local_seed, remote_seed, user_hash): self.user_hash = user_hash self._key = self._key_derive(local_seed, remote_seed, user_hash) (self._iv, self._seq) = self._iv_derive(local_seed, remote_seed, user_hash) + self._aes = algorithms.AES(self._key) + self._generate_cipher() self._sig = self._sig_derive(local_seed, remote_seed, user_hash) def _key_derive(self, local_seed, remote_seed, user_hash): @@ -451,19 +456,20 @@ def _sig_derive(self, local_seed, remote_seed, user_hash): payload = b"ldk" + local_seed + remote_seed + user_hash return hashlib.sha256(payload).digest()[:28] - def _iv_seq(self): - seq = self._seq.to_bytes(4, "big", signed=True) - iv = self._iv + seq - return iv + def _generate_cipher(self): + iv_seq = self._iv + PACK_SIGNED_LONG(self._seq) + cbc = modes.CBC(iv_seq) + self._cipher = Cipher(self._aes, cbc) def encrypt(self, msg): """Encrypt the data and increment the sequence number.""" - self._seq = self._seq + 1 + self._seq += 1 + self._generate_cipher() + if isinstance(msg, str): msg = msg.encode("utf-8") - cipher = Cipher(algorithms.AES(self._key), modes.CBC(self._iv_seq())) - encryptor = cipher.encryptor() + encryptor = self._cipher.encryptor() padder = padding.PKCS7(128).padder() padded_data = padder.update(msg) + padder.finalize() ciphertext = encryptor.update(padded_data) + encryptor.finalize() @@ -478,8 +484,7 @@ def encrypt(self, msg): def decrypt(self, msg): """Decrypt the data.""" - cipher = Cipher(algorithms.AES(self._key), modes.CBC(self._iv_seq())) - decryptor = cipher.decryptor() + decryptor = self._cipher.decryptor() dp = decryptor.update(msg[32:]) + decryptor.finalize() unpadder = padding.PKCS7(128).unpadder() plaintextbytes = unpadder.update(dp) + unpadder.finalize() From 26d1fcf208496db87c9127f2d16b1ff6f476b6db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Jan 2024 06:24:58 -1000 Subject: [PATCH 2/4] no need to do in init --- kasa/klaptransport.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kasa/klaptransport.py b/kasa/klaptransport.py index 6df45948e..e6d5b188a 100644 --- a/kasa/klaptransport.py +++ b/kasa/klaptransport.py @@ -436,7 +436,6 @@ def __init__(self, local_seed, remote_seed, user_hash): self._key = self._key_derive(local_seed, remote_seed, user_hash) (self._iv, self._seq) = self._iv_derive(local_seed, remote_seed, user_hash) self._aes = algorithms.AES(self._key) - self._generate_cipher() self._sig = self._sig_derive(local_seed, remote_seed, user_hash) def _key_derive(self, local_seed, remote_seed, user_hash): @@ -474,7 +473,7 @@ def encrypt(self, msg): padded_data = padder.update(msg) + padder.finalize() ciphertext = encryptor.update(padded_data) + encryptor.finalize() signature = hashlib.sha256( - self._sig + self._seq.to_bytes(4, "big", signed=True) + ciphertext + self._sig + PACK_SIGNED_LONG(self._seq) + ciphertext ).digest() return (signature + ciphertext, self._seq) From db1e3b21459f0a6ab883a5bb8dc15456e6532f1f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Jan 2024 06:32:10 -1000 Subject: [PATCH 3/4] no need to do in init --- kasa/klaptransport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kasa/klaptransport.py b/kasa/klaptransport.py index e6d5b188a..2885c3277 100644 --- a/kasa/klaptransport.py +++ b/kasa/klaptransport.py @@ -429,6 +429,8 @@ class KlapEncryptionSession: i.e. sequence number which the device expects to increment. """ + _cipher: Cipher + def __init__(self, local_seed, remote_seed, user_hash): self.local_seed = local_seed self.remote_seed = remote_seed From 0291edca2b0edeb1f0bc3f37641262a25fd0e5c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Jan 2024 06:38:04 -1000 Subject: [PATCH 4/4] Use hashlib in place of hashes.Hash - Its faster and less code --- kasa/klaptransport.py | 11 +++-------- kasa/protocol.py | 10 +++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/kasa/klaptransport.py b/kasa/klaptransport.py index 66052f590..ac9243d1d 100644 --- a/kasa/klaptransport.py +++ b/kasa/klaptransport.py @@ -50,7 +50,7 @@ from pprint import pformat as pf from typing import Any, Dict, Optional, Tuple, cast -from cryptography.hazmat.primitives import hashes, padding +from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from .credentials import Credentials @@ -68,16 +68,11 @@ def _sha256(payload: bytes) -> bytes: - digest = hashes.Hash(hashes.SHA256()) # noqa: S303 - digest.update(payload) - hash = digest.finalize() - return hash + return hashlib.sha256(payload).digest() # noqa: S324 def _sha1(payload: bytes) -> bytes: - digest = hashes.Hash(hashes.SHA1()) # noqa: S303 - digest.update(payload) - return digest.finalize() + return hashlib.sha1(payload).digest() # noqa: S324 class KlapTransport(BaseTransport): diff --git a/kasa/protocol.py b/kasa/protocol.py index 60b3d7ca6..aa9e3cbea 100755 --- a/kasa/protocol.py +++ b/kasa/protocol.py @@ -11,6 +11,7 @@ """ import base64 import errno +import hashlib import logging import struct from abc import ABC, abstractmethod @@ -18,8 +19,6 @@ # When support for cpython older than 3.11 is dropped # async_timeout can be replaced with asyncio.timeout -from cryptography.hazmat.primitives import hashes - from .credentials import Credentials from .deviceconfig import DeviceConfig @@ -29,11 +28,8 @@ def md5(payload: bytes) -> bytes: - """Return an md5 hash of the payload.""" - digest = hashes.Hash(hashes.MD5()) # noqa: S303 - digest.update(payload) - hash = digest.finalize() - return hash + """Return the MD5 hash of the payload.""" + return hashlib.md5(payload).digest() # noqa: S324 class BaseTransport(ABC):