-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Brute force encryption key automatically (#52)
* Brute force encryption key automatically * Fix config flow error handling * Add config flow tests * Cleanup
- Loading branch information
Showing
10 changed files
with
407 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import itertools | ||
import json | ||
import logging | ||
import math | ||
import string | ||
from enum import Enum | ||
|
||
from typing import Optional, Iterable | ||
|
||
# Adapted from https://www.online-python.com/pm93n5Sqg4 | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
KEY_LEN = 16 | ||
KEY_CHARSET_CODEPOINTS: list[int] = [ord(c) for c in string.ascii_letters + string.digits] | ||
PLAINTEXT_CHARSET_CODEPOINTS: list[int] = [ord(c) for c in string.printable] | ||
|
||
|
||
class Encryption(Enum): | ||
NO_ENCRYPTION = 1 # Use `encrypted=0` in request, response is plaintext JSON | ||
ENCRYPTION = 2 # Use `encrypted=1` in request, response is encrypted bytes in hex encoding | ||
ENCRYPTION_WITHOUT_KEY = 3 # Use `encrypted=1` in request, response is unencrypted hex bytes (https://github.com/ofalvai/home-assistant-candy/issues/35#issuecomment-965557116) | ||
|
||
|
||
def find_key(encrypted_response: bytes) -> Optional[str]: | ||
candidate_key_codepoints: list[list[int]] = [ | ||
list(_find_candidate_key_codepoints(encrypted_response, i)) for i in range(16) | ||
] | ||
|
||
number_of_keys = math.prod(len(l) for l in candidate_key_codepoints) | ||
_LOGGER.info(f"{number_of_keys} keys to test") | ||
|
||
for key in itertools.product(*candidate_key_codepoints): | ||
decrypted = decrypt(key, encrypted_response) | ||
if _is_valid_json(decrypted): | ||
key_str = "".join(chr(point) for point in key) | ||
_LOGGER.info(f"Potential key found: {key_str}") | ||
return key_str | ||
|
||
return None | ||
|
||
|
||
def decrypt(key: bytes, encrypted_response: bytes) -> bytes: | ||
key_len = len(key) | ||
decrypted: list[int] = [] | ||
for (i, byte) in enumerate(encrypted_response): | ||
decrypted.append(byte ^ key[i % key_len]) | ||
return bytes(decrypted) | ||
|
||
|
||
def _find_candidate_key_codepoints(encrypted_response: bytes, key_offset: int) -> Iterable[int]: | ||
bytes_to_check: bytes = encrypted_response[key_offset::KEY_LEN] | ||
for point in KEY_CHARSET_CODEPOINTS: | ||
if all(point ^ byte in PLAINTEXT_CHARSET_CODEPOINTS for byte in bytes_to_check): | ||
yield point | ||
|
||
|
||
def _is_valid_json(decrypted: bytes) -> bool: | ||
try: | ||
json.loads(decrypted) | ||
except json.JSONDecodeError: | ||
return False | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.