Skip to content

Commit 9512615

Browse files
authored
fix: ensure null bytes round trip (#3)
1 parent 4d5a8e8 commit 9512615

3 files changed

Lines changed: 58 additions & 22 deletions

File tree

src/kasa_crypt/_crypt_impl.pyx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,35 @@ from libc.string cimport strlen
55

66

77
cdef extern from "crypt_wrapper.h":
8-
void _encrypt_into(const char * unencrypted, char * encrypted)
9-
void _decrypt_into(const char * encrypted, char * unencrypted)
8+
void _encrypt_into(const char * unencrypted, char * encrypted, unsigned long length)
9+
void _decrypt_into(const char * encrypted, char * unencrypted, unsigned long length)
1010

11-
cdef void _decrypt(const char *encrypted, char **unencrypted, Py_ssize_t *length):
12-
cdef Py_ssize_t n = strlen(encrypted)
13-
unencrypted[0] = <char *> malloc((n + 1) * sizeof(char))
11+
cdef void _decrypt(const char *encrypted, char **unencrypted, Py_ssize_t length):
12+
unencrypted[0] = <char *> malloc((length + 1) * sizeof(char))
1413
if not unencrypted[0]:
1514
return # malloc failed
16-
_decrypt_into(encrypted, unencrypted[0] )
17-
length[0] = n
15+
_decrypt_into(encrypted, unencrypted[0], length)
1816

19-
cdef void _encrypt(const char *unencrypted, char** encrypted, Py_ssize_t *length):
20-
cdef Py_ssize_t n = strlen(unencrypted)
21-
encrypted[0] = <char *> malloc((n + 1) * sizeof(char))
17+
cdef void _encrypt(const char *unencrypted, char** encrypted, Py_ssize_t length):
18+
encrypted[0] = <char *> malloc((length + 1) * sizeof(char))
2219
if not encrypted[0]:
2320
return # malloc failed
24-
_encrypt_into(unencrypted, encrypted[0])
25-
length[0] = n
21+
_encrypt_into(unencrypted, encrypted[0], length)
2622

2723
def encrypt(string: str) -> bytes:
2824
cdef char* encrypted = NULL
29-
cdef Py_ssize_t length = 0
30-
_encrypt(string.encode('utf-8'), &encrypted, &length)
25+
py_byte_string = string.encode('utf-8')
26+
cdef Py_ssize_t length = len(py_byte_string)
27+
_encrypt(py_byte_string, &encrypted, length)
3128
try:
3229
return encrypted[:length]
3330
finally:
3431
free(encrypted)
3532

3633
def decrypt(string: bytes) -> str:
3734
cdef char* unencrypted = NULL
38-
cdef Py_ssize_t length = 0
39-
_decrypt(string, &unencrypted, &length)
35+
cdef Py_ssize_t length = len(string)
36+
_decrypt(string, &unencrypted, length)
4037
try:
4138
return unencrypted[:length].decode('utf-8')
4239
finally:

src/kasa_crypt/crypt_wrapper.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@
55
#include <string.h>
66

77

8-
void _encrypt_into(const char * unencrypted, char * encrypted) {
8+
void _encrypt_into(const char * unencrypted, char * encrypted, unsigned long length) {
99
uint8_t unencrypted_byte;
1010
uint8_t key = 171;
11-
unsigned long len = strlen(unencrypted);
12-
for(unsigned i = 0; i < len; i++) {
11+
for(unsigned i = 0; i < length; i++) {
1312
unencrypted_byte = unencrypted[i];
1413
key = key ^ unencrypted_byte;
1514
encrypted[i] = key;
1615
}
1716
}
18-
void _decrypt_into(const char * encrypted, char * unencrypted) {
17+
void _decrypt_into(const char * encrypted, char * unencrypted, unsigned long length) {
1918
uint8_t unencrypted_byte;
2019
uint8_t encrypted_byte;
2120
uint8_t key = 171;
22-
unsigned long len = strlen(encrypted);
23-
for(unsigned i = 0; i < len; i++) {
21+
for(unsigned i = 0; i < length; i++) {
2422
encrypted_byte = encrypted[i];
2523
unencrypted_byte = key ^ encrypted_byte;
2624
key = encrypted_byte;

tests/test_init.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@
55
# from
66
# https://github.com/python-kasa/python-kasa/blob/master/kasa/tests/test_protocol.py
77

8+
PLAIN_TEXT_STRING = (
9+
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n"
10+
"\x0b"
11+
"\x0c"
12+
"\r"
13+
"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c"
14+
"\x1d"
15+
"\x1e"
16+
"\x1f "
17+
"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85"
18+
"\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
19+
"¡¢£¤¥¦§¨©ª«¬"
20+
)
21+
ENCRYPTED_BYTE_WITH_NULLS = (
22+
b"\x00\x00\x00\xad\xab\xaa\xa8\xab\xaf\xaa\xac\xab\xa3\xaa\xa0\xab"
23+
b"\xa7\xaa\xa4\xab\xbb\xaa\xb8\xab\xbf\xaa\xbc\xab\xb3\xaa\xb0\xab"
24+
b"\xb7\xaa\xb4\xab\x8b\xaa\x88\xab\x8f\xaa\x8c\xab\x83\xaa\x80\xab"
25+
b"\x87\xaa\x84\xab\x9b\xaa\x98\xab\x9f\xaa\x9c\xab\x93\xaa\x90\xab"
26+
b"\x97\xaa\x94\xab\xeb\xaa\xe8\xab\xef\xaa\xec\xab\xe3\xaa\xe0\xab"
27+
b"\xe7\xaa\xe4\xab\xfb\xaa\xf8\xab\xff\xaa\xfc\xab\xf3\xaa\xf0\xab"
28+
b"\xf7\xaa\xf4\xab\xcb\xaa\xc8\xab\xcf\xaa\xcc\xab\xc3\xaa\xc0\xab"
29+
b"\xc7\xaa\xc4\xab\xdb\xaa\xd8\xab\xdf\xaa\xdc\xab\xd3\xaa\xd0\xab"
30+
b"\xd7\xaa\xd4\xabi\xe9+\xaah\xea(\xabi\xed/\xaah\xee,\xabi\xe1#\xaah\xe2 \xab"
31+
b"i\xe5'\xaah\xe6$\xabi\xf9;\xaah\xfa8\xabi\xfd?\xaah\xfe<\xabi\xf13\xaa"
32+
b"h\xf20\xabi\xf57\xaah\xf64\xabi\xc9\x0b\xaah\xca\x08\xabi\xcd\x0f\xaa"
33+
b"h\xce\x0c\xabi\xc1\x03\xaah\xc2\x00\xabi\xc5"
34+
)
35+
836

937
def test_encrypt():
1038
d = json.dumps({"foo": 1, "bar": 2})
@@ -135,3 +163,16 @@ def test_decrypt_real_device():
135163
' Rel.165938","hw_ver":"1.0","model":"EP40(US)","deviceId":"8006231E1499BAC4D4BC7EFCD4B075181E6393F2","oemId":"2F9215F1DCBF7DC17F80E2B0CACD47FC","hwId":"B3B7B05B758C3EDA8F9C69FECDBA2111","rssi":-40,"latitude_i":297852,"longitude_i":-954073,"alias":"TP-LINK_Smart'
136164
' Plug_004F","status":"new","mic_type":"IOT.SMARTPLUGSWITCH","feature":"TIM","mac":"E8:48:B8:1E:00:4F","updating":0,"led_off":0,"children":[{"id":"00","state":1,"alias":"Zombie","on_time":470,"next_action":{"type":-1}},{"id":"01","state":1,"alias":"Magic","on_time":174,"next_action":{"type":-1}}],"child_num":2,"ntc_state":0,"err_code":0}}}'
137165
)
166+
167+
168+
def test_roundtrip_with_nulls():
169+
assert decrypt(ENCRYPTED_BYTE_WITH_NULLS[4:]) == PLAIN_TEXT_STRING
170+
171+
172+
def test_encrypt_with_nulls():
173+
string_with_nulls = b"this\x00has\x00nulls".decode()
174+
assert (
175+
encrypt(string_with_nulls)
176+
== b"\x00\x00\x00\x0e\xdf\xb7\xde\xad\xad\xc5\xa4\xd7\xd7\xb9\xcc\xa0\xcc\xbf"
177+
)
178+
assert decrypt(encrypt(string_with_nulls)[4:]) == string_with_nulls

0 commit comments

Comments
 (0)