pyezvizapi 1.0.4.1__py3-none-any.whl → 1.0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyezvizapi might be problematic. Click here for more details.

pyezvizapi/utils.py CHANGED
@@ -153,39 +153,102 @@ def decrypt_image(input_data: bytes, password: str) -> bytes:
153
153
  bytes: Decrypted image data
154
154
 
155
155
  """
156
- if len(input_data) < 48:
157
- raise PyEzvizError("Invalid image data")
158
-
159
- # check header
160
156
  header_len = len(HIK_ENCRYPTION_HEADER)
157
+ min_length = header_len + 32 # header + md5 hash
161
158
 
162
- if input_data[:header_len] != HIK_ENCRYPTION_HEADER:
159
+ if len(input_data) < min_length:
160
+ raise PyEzvizError("Invalid image data")
161
+
162
+ header_index = input_data.find(HIK_ENCRYPTION_HEADER)
163
+ if header_index == -1:
163
164
  _LOGGER.debug("Image header doesn't contain %s", HIK_ENCRYPTION_HEADER)
164
165
  return input_data
165
166
 
166
- file_hash = input_data[16:48]
167
+ if header_index:
168
+ _LOGGER.debug("Image header found at offset %s, trimming preamble", header_index)
169
+ input_data = input_data[header_index:]
170
+ if len(input_data) < min_length:
171
+ raise PyEzvizError("Invalid image data after trimming preamble")
172
+
173
+ hash_end = header_len + 32
174
+ blocks = _split_encrypted_blocks(input_data, header_len, min_length)
175
+ if not blocks:
176
+ raise PyEzvizError("Invalid image data")
177
+
178
+ decrypted_parts = [
179
+ _decrypt_single_block(block, password, header_len, hash_end) for block in blocks
180
+ ]
181
+ if len(decrypted_parts) > 1:
182
+ _LOGGER.debug("Decrypted %s concatenated image blocks", len(decrypted_parts))
183
+ return b"".join(decrypted_parts)
184
+
185
+
186
+ def _split_encrypted_blocks(
187
+ data: bytes, header_len: int, min_length: int
188
+ ) -> list[bytes]:
189
+ """Split concatenated hikencodepicture segments into individual blocks."""
190
+ blocks: list[bytes] = []
191
+ cursor = 0
192
+ data_len = len(data)
193
+
194
+ while cursor <= data_len - min_length:
195
+ if data[cursor : cursor + header_len] != HIK_ENCRYPTION_HEADER:
196
+ next_header = data.find(HIK_ENCRYPTION_HEADER, cursor + 1)
197
+ if next_header == -1:
198
+ break
199
+ cursor = next_header
200
+ continue
201
+
202
+ next_header = data.find(HIK_ENCRYPTION_HEADER, cursor + header_len)
203
+ block = data[cursor : next_header if next_header != -1 else data_len]
204
+ if len(block) < min_length:
205
+ break
206
+ blocks.append(block)
207
+ if next_header == -1:
208
+ break
209
+ cursor = next_header
210
+
211
+ return blocks
212
+
213
+
214
+ def _decrypt_single_block(
215
+ block: bytes, password: str, header_len: int, hash_end: int
216
+ ) -> bytes:
217
+ """Decrypt a single hikencodepicture block."""
218
+ file_hash = block[header_len:hash_end]
167
219
  passwd_hash = md5(str.encode(md5(str.encode(password)).hexdigest())).hexdigest()
168
220
  if file_hash != str.encode(passwd_hash):
169
221
  raise PyEzvizError("Invalid password")
170
222
 
223
+ ciphertext = block[hash_end:]
224
+ if not ciphertext:
225
+ raise PyEzvizError("Missing ciphertext payload")
226
+
227
+ remainder = len(ciphertext) % AES.block_size
228
+ if remainder:
229
+ _LOGGER.debug(
230
+ "Ciphertext not aligned to 16 bytes; trimming %s trailing bytes", remainder
231
+ )
232
+ ciphertext = ciphertext[:-remainder]
233
+ if not ciphertext:
234
+ raise PyEzvizError("Ciphertext too short after alignment adjustment")
235
+
171
236
  key = str.encode(password.ljust(16, "\u0000")[:16])
172
237
  iv_code = bytes([48, 49, 50, 51, 52, 53, 54, 55, 0, 0, 0, 0, 0, 0, 0, 0])
173
238
  cipher = AES.new(key, AES.MODE_CBC, iv_code)
174
239
 
175
- next_chunk = b""
176
- output_data = b""
177
- finished = False
178
- i = 48 # offset HIK header + hash
179
240
  chunk_size = 1024 * AES.block_size
180
- while not finished:
181
- chunk, next_chunk = next_chunk, cipher.decrypt(input_data[i : i + chunk_size])
182
- if len(next_chunk) == 0:
183
- padding_length = chunk[-1]
184
- chunk = chunk[:-padding_length]
185
- finished = True
186
- output_data += chunk
187
- i += chunk_size
188
- return output_data
241
+ output_data = bytearray()
242
+
243
+ for start in range(0, len(ciphertext), chunk_size):
244
+ block_chunk = cipher.decrypt(ciphertext[start : start + chunk_size])
245
+ if start + chunk_size >= len(ciphertext):
246
+ padding_length = block_chunk[-1]
247
+ block_chunk = block_chunk[:-padding_length]
248
+ output_data.extend(block_chunk)
249
+
250
+ return bytes(output_data)
251
+
189
252
 
190
253
 
191
254
  def return_password_hash(password: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.4.1
3
+ Version: 1.0.4.2
4
4
  Summary: EZVIZ API client for Home Assistant and CLI
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -12,10 +12,10 @@ pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
12
12
  pyezvizapi/mqtt.py,sha256=JGjO-uXdKtLidYN1wEZ_bxEKIlNLmnB82Ziiac6oxWs,22373
13
13
  pyezvizapi/test_cam_rtsp.py,sha256=pbuanoKs_Pryt2f5QctHIngzJG1nD6kv8nulQYh2yPc,5162
14
14
  pyezvizapi/test_mqtt.py,sha256=gBaurvo2bu-7sOe14AqNouepJHI-tPWzC3WTpDQbvHM,4155
15
- pyezvizapi/utils.py,sha256=WHGqI0bIy-G4yX6Vs7i_UDrr7dndx3yzbF_z7rDYGKE,13213
16
- pyezvizapi-1.0.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- pyezvizapi-1.0.4.1.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- pyezvizapi-1.0.4.1.dist-info/METADATA,sha256=aBfPakdjFSnVKiCtzCGzlA3RO515bTwdU5SDztAkSiw,7609
19
- pyezvizapi-1.0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- pyezvizapi-1.0.4.1.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
21
- pyezvizapi-1.0.4.1.dist-info/RECORD,,
15
+ pyezvizapi/utils.py,sha256=B-Lt4p14TTcJyrZvJ699GiUGj6qcd4vGmkKUf13bf_I,15455
16
+ pyezvizapi-1.0.4.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ pyezvizapi-1.0.4.2.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ pyezvizapi-1.0.4.2.dist-info/METADATA,sha256=_fdaKK2rdx9u4pHrjjkDHvBYZs5rTJqrh0OqkTioojg,7609
19
+ pyezvizapi-1.0.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ pyezvizapi-1.0.4.2.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
21
+ pyezvizapi-1.0.4.2.dist-info/RECORD,,