aspose-cells-foss 25.12.1__py3-none-any.whl → 26.2.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.
Files changed (93) hide show
  1. aspose_cells/__init__.py +88 -0
  2. aspose_cells/auto_filter.py +527 -0
  3. aspose_cells/cell.py +483 -0
  4. aspose_cells/cell_value_handler.py +319 -0
  5. aspose_cells/cells.py +779 -0
  6. aspose_cells/cfb_handler.py +445 -0
  7. aspose_cells/cfb_writer.py +659 -0
  8. aspose_cells/cfb_writer_minimal.py +337 -0
  9. aspose_cells/comment_xml.py +475 -0
  10. aspose_cells/conditional_format.py +1185 -0
  11. aspose_cells/csv_handler.py +690 -0
  12. aspose_cells/data_validation.py +911 -0
  13. aspose_cells/document_properties.py +356 -0
  14. aspose_cells/encryption_crypto.py +247 -0
  15. aspose_cells/encryption_params.py +138 -0
  16. aspose_cells/hyperlink.py +372 -0
  17. aspose_cells/json_handler.py +185 -0
  18. aspose_cells/markdown_handler.py +583 -0
  19. aspose_cells/shared_strings.py +101 -0
  20. aspose_cells/style.py +841 -0
  21. aspose_cells/workbook.py +499 -0
  22. aspose_cells/workbook_hash_password.py +68 -0
  23. aspose_cells/workbook_properties.py +712 -0
  24. aspose_cells/worksheet.py +570 -0
  25. aspose_cells/worksheet_properties.py +1239 -0
  26. aspose_cells/xlsx_encryptor.py +403 -0
  27. aspose_cells/xml_autofilter_loader.py +195 -0
  28. aspose_cells/xml_autofilter_saver.py +173 -0
  29. aspose_cells/xml_conditional_format_loader.py +215 -0
  30. aspose_cells/xml_conditional_format_saver.py +351 -0
  31. aspose_cells/xml_datavalidation_loader.py +239 -0
  32. aspose_cells/xml_datavalidation_saver.py +245 -0
  33. aspose_cells/xml_hyperlink_handler.py +323 -0
  34. aspose_cells/xml_loader.py +986 -0
  35. aspose_cells/xml_properties_loader.py +512 -0
  36. aspose_cells/xml_properties_saver.py +607 -0
  37. aspose_cells/xml_saver.py +1306 -0
  38. aspose_cells_foss-26.2.2.dist-info/METADATA +190 -0
  39. aspose_cells_foss-26.2.2.dist-info/RECORD +41 -0
  40. {aspose_cells_foss-25.12.1.dist-info → aspose_cells_foss-26.2.2.dist-info}/WHEEL +1 -1
  41. aspose_cells_foss-26.2.2.dist-info/top_level.txt +1 -0
  42. aspose/__init__.py +0 -14
  43. aspose/cells/__init__.py +0 -31
  44. aspose/cells/cell.py +0 -350
  45. aspose/cells/constants.py +0 -44
  46. aspose/cells/converters/__init__.py +0 -13
  47. aspose/cells/converters/csv_converter.py +0 -55
  48. aspose/cells/converters/json_converter.py +0 -46
  49. aspose/cells/converters/markdown_converter.py +0 -453
  50. aspose/cells/drawing/__init__.py +0 -17
  51. aspose/cells/drawing/anchor.py +0 -172
  52. aspose/cells/drawing/collection.py +0 -233
  53. aspose/cells/drawing/image.py +0 -338
  54. aspose/cells/formats.py +0 -80
  55. aspose/cells/formula/__init__.py +0 -10
  56. aspose/cells/formula/evaluator.py +0 -360
  57. aspose/cells/formula/functions.py +0 -433
  58. aspose/cells/formula/tokenizer.py +0 -340
  59. aspose/cells/io/__init__.py +0 -27
  60. aspose/cells/io/csv/__init__.py +0 -8
  61. aspose/cells/io/csv/reader.py +0 -88
  62. aspose/cells/io/csv/writer.py +0 -98
  63. aspose/cells/io/factory.py +0 -138
  64. aspose/cells/io/interfaces.py +0 -48
  65. aspose/cells/io/json/__init__.py +0 -8
  66. aspose/cells/io/json/reader.py +0 -126
  67. aspose/cells/io/json/writer.py +0 -119
  68. aspose/cells/io/md/__init__.py +0 -8
  69. aspose/cells/io/md/reader.py +0 -161
  70. aspose/cells/io/md/writer.py +0 -334
  71. aspose/cells/io/models.py +0 -64
  72. aspose/cells/io/xlsx/__init__.py +0 -9
  73. aspose/cells/io/xlsx/constants.py +0 -312
  74. aspose/cells/io/xlsx/image_writer.py +0 -311
  75. aspose/cells/io/xlsx/reader.py +0 -284
  76. aspose/cells/io/xlsx/writer.py +0 -931
  77. aspose/cells/plugins/__init__.py +0 -6
  78. aspose/cells/plugins/docling_backend/__init__.py +0 -7
  79. aspose/cells/plugins/docling_backend/backend.py +0 -535
  80. aspose/cells/plugins/markitdown_plugin/__init__.py +0 -15
  81. aspose/cells/plugins/markitdown_plugin/plugin.py +0 -128
  82. aspose/cells/range.py +0 -210
  83. aspose/cells/style.py +0 -287
  84. aspose/cells/utils/__init__.py +0 -54
  85. aspose/cells/utils/coordinates.py +0 -68
  86. aspose/cells/utils/exceptions.py +0 -43
  87. aspose/cells/utils/validation.py +0 -102
  88. aspose/cells/workbook.py +0 -352
  89. aspose/cells/worksheet.py +0 -670
  90. aspose_cells_foss-25.12.1.dist-info/METADATA +0 -189
  91. aspose_cells_foss-25.12.1.dist-info/RECORD +0 -53
  92. aspose_cells_foss-25.12.1.dist-info/entry_points.txt +0 -2
  93. aspose_cells_foss-25.12.1.dist-info/top_level.txt +0 -1
@@ -0,0 +1,356 @@
1
+ """
2
+ Aspose.Cells for Python - Document Properties Module
3
+
4
+ This module provides classes for document-level properties according to ECMA-376 specification.
5
+ These properties are stored in docProps/core.xml and docProps/app.xml files.
6
+
7
+ ECMA-376 Part 2: Open Packaging Conventions - Core Properties
8
+ ECMA-376 Part 1: Extended Properties (docProps/app.xml)
9
+ """
10
+
11
+ from datetime import datetime
12
+
13
+
14
+ class CoreProperties:
15
+ """
16
+ Represents core document properties stored in docProps/core.xml.
17
+
18
+ Uses Dublin Core metadata elements and OPC core properties.
19
+
20
+ ECMA-376 Part 2, Section 11 - Core Properties
21
+
22
+ Examples:
23
+ >>> wb.document_properties.core.title = "Sales Report"
24
+ >>> wb.document_properties.core.creator = "John Doe"
25
+ >>> wb.document_properties.core.subject = "Q4 2024 Sales"
26
+ """
27
+
28
+ def __init__(self):
29
+ self._title = None
30
+ self._subject = None
31
+ self._creator = None
32
+ self._keywords = None
33
+ self._description = None
34
+ self._last_modified_by = None
35
+ self._revision = None
36
+ self._created = None
37
+ self._modified = None
38
+ self._category = None
39
+ self._content_status = None
40
+ self._content_type = None
41
+ self._identifier = None
42
+ self._language = None
43
+ self._version = None
44
+
45
+ @property
46
+ def title(self):
47
+ """Document title (dc:title)."""
48
+ return self._title
49
+
50
+ @title.setter
51
+ def title(self, value):
52
+ self._title = value
53
+
54
+ @property
55
+ def subject(self):
56
+ """Document subject (dc:subject)."""
57
+ return self._subject
58
+
59
+ @subject.setter
60
+ def subject(self, value):
61
+ self._subject = value
62
+
63
+ @property
64
+ def creator(self):
65
+ """Document creator/author (dc:creator)."""
66
+ return self._creator
67
+
68
+ @creator.setter
69
+ def creator(self, value):
70
+ self._creator = value
71
+
72
+ @property
73
+ def keywords(self):
74
+ """Keywords associated with the document (cp:keywords)."""
75
+ return self._keywords
76
+
77
+ @keywords.setter
78
+ def keywords(self, value):
79
+ self._keywords = value
80
+
81
+ @property
82
+ def description(self):
83
+ """Document description/comments (dc:description)."""
84
+ return self._description
85
+
86
+ @description.setter
87
+ def description(self, value):
88
+ self._description = value
89
+
90
+ @property
91
+ def last_modified_by(self):
92
+ """Name of person who last modified the document (cp:lastModifiedBy)."""
93
+ return self._last_modified_by
94
+
95
+ @last_modified_by.setter
96
+ def last_modified_by(self, value):
97
+ self._last_modified_by = value
98
+
99
+ @property
100
+ def revision(self):
101
+ """Revision number (cp:revision)."""
102
+ return self._revision
103
+
104
+ @revision.setter
105
+ def revision(self, value):
106
+ self._revision = value
107
+
108
+ @property
109
+ def created(self):
110
+ """Document creation date (dcterms:created)."""
111
+ return self._created
112
+
113
+ @created.setter
114
+ def created(self, value):
115
+ self._created = value
116
+
117
+ @property
118
+ def modified(self):
119
+ """Document last modification date (dcterms:modified)."""
120
+ return self._modified
121
+
122
+ @modified.setter
123
+ def modified(self, value):
124
+ self._modified = value
125
+
126
+ @property
127
+ def category(self):
128
+ """Document category (cp:category)."""
129
+ return self._category
130
+
131
+ @category.setter
132
+ def category(self, value):
133
+ self._category = value
134
+
135
+ @property
136
+ def content_status(self):
137
+ """Content status such as Draft, Final (cp:contentStatus)."""
138
+ return self._content_status
139
+
140
+ @content_status.setter
141
+ def content_status(self, value):
142
+ self._content_status = value
143
+
144
+
145
+ class ExtendedProperties:
146
+ """
147
+ Represents extended/application properties stored in docProps/app.xml.
148
+
149
+ ECMA-376 Part 1, Section 22.2 - Extended Properties
150
+
151
+ Examples:
152
+ >>> wb.document_properties.extended.application = "Microsoft Excel"
153
+ >>> wb.document_properties.extended.company = "Acme Corp"
154
+ """
155
+
156
+ def __init__(self):
157
+ self._application = "Microsoft Excel"
158
+ self._app_version = None
159
+ self._company = None
160
+ self._manager = None
161
+ self._doc_security = 0
162
+ self._hyperlink_base = None
163
+ self._scale_crop = False
164
+ self._links_up_to_date = False
165
+ self._shared_doc = False
166
+
167
+ @property
168
+ def application(self):
169
+ """Name of the application that created the document."""
170
+ return self._application
171
+
172
+ @application.setter
173
+ def application(self, value):
174
+ self._application = value
175
+
176
+ @property
177
+ def app_version(self):
178
+ """Version of the application that created the document."""
179
+ return self._app_version
180
+
181
+ @app_version.setter
182
+ def app_version(self, value):
183
+ self._app_version = value
184
+
185
+ @property
186
+ def company(self):
187
+ """Company or organization name."""
188
+ return self._company
189
+
190
+ @company.setter
191
+ def company(self, value):
192
+ self._company = value
193
+
194
+ @property
195
+ def manager(self):
196
+ """Manager associated with the document."""
197
+ return self._manager
198
+
199
+ @manager.setter
200
+ def manager(self, value):
201
+ self._manager = value
202
+
203
+ @property
204
+ def doc_security(self):
205
+ """Document security level (0=none, 1=password protected, etc.)."""
206
+ return self._doc_security
207
+
208
+ @doc_security.setter
209
+ def doc_security(self, value):
210
+ self._doc_security = value
211
+
212
+ @property
213
+ def hyperlink_base(self):
214
+ """Base URL for relative hyperlinks."""
215
+ return self._hyperlink_base
216
+
217
+ @hyperlink_base.setter
218
+ def hyperlink_base(self, value):
219
+ self._hyperlink_base = value
220
+
221
+ @property
222
+ def scale_crop(self):
223
+ """Whether to scale or crop document thumbnail."""
224
+ return self._scale_crop
225
+
226
+ @scale_crop.setter
227
+ def scale_crop(self, value):
228
+ self._scale_crop = value
229
+
230
+ @property
231
+ def links_up_to_date(self):
232
+ """Whether hyperlinks are up to date."""
233
+ return self._links_up_to_date
234
+
235
+ @links_up_to_date.setter
236
+ def links_up_to_date(self, value):
237
+ self._links_up_to_date = value
238
+
239
+ @property
240
+ def shared_doc(self):
241
+ """Whether the document is shared."""
242
+ return self._shared_doc
243
+
244
+ @shared_doc.setter
245
+ def shared_doc(self, value):
246
+ self._shared_doc = value
247
+
248
+
249
+ class DocumentProperties:
250
+ """
251
+ Container for all document-level properties.
252
+
253
+ This includes both core properties (docProps/core.xml) and
254
+ extended properties (docProps/app.xml).
255
+
256
+ Examples:
257
+ >>> wb.document_properties.core.title = "Sales Report"
258
+ >>> wb.document_properties.core.creator = "John Doe"
259
+ >>> wb.document_properties.extended.company = "Acme Corp"
260
+ """
261
+
262
+ def __init__(self):
263
+ self._core = CoreProperties()
264
+ self._extended = ExtendedProperties()
265
+
266
+ @property
267
+ def core(self):
268
+ """Gets core document properties (stored in docProps/core.xml)."""
269
+ return self._core
270
+
271
+ @property
272
+ def extended(self):
273
+ """Gets extended/application properties (stored in docProps/app.xml)."""
274
+ return self._extended
275
+
276
+ # Convenience properties that map to core properties
277
+ @property
278
+ def title(self):
279
+ """Document title."""
280
+ return self._core.title
281
+
282
+ @title.setter
283
+ def title(self, value):
284
+ self._core.title = value
285
+
286
+ @property
287
+ def subject(self):
288
+ """Document subject."""
289
+ return self._core.subject
290
+
291
+ @subject.setter
292
+ def subject(self, value):
293
+ self._core.subject = value
294
+
295
+ @property
296
+ def author(self):
297
+ """Document author (alias for creator)."""
298
+ return self._core.creator
299
+
300
+ @author.setter
301
+ def author(self, value):
302
+ self._core.creator = value
303
+
304
+ @property
305
+ def creator(self):
306
+ """Document creator."""
307
+ return self._core.creator
308
+
309
+ @creator.setter
310
+ def creator(self, value):
311
+ self._core.creator = value
312
+
313
+ @property
314
+ def keywords(self):
315
+ """Document keywords."""
316
+ return self._core.keywords
317
+
318
+ @keywords.setter
319
+ def keywords(self, value):
320
+ self._core.keywords = value
321
+
322
+ @property
323
+ def comments(self):
324
+ """Document comments (alias for description)."""
325
+ return self._core.description
326
+
327
+ @comments.setter
328
+ def comments(self, value):
329
+ self._core.description = value
330
+
331
+ @property
332
+ def category(self):
333
+ """Document category."""
334
+ return self._core.category
335
+
336
+ @category.setter
337
+ def category(self, value):
338
+ self._core.category = value
339
+
340
+ @property
341
+ def company(self):
342
+ """Company name."""
343
+ return self._extended.company
344
+
345
+ @company.setter
346
+ def company(self, value):
347
+ self._extended.company = value
348
+
349
+ @property
350
+ def manager(self):
351
+ """Manager name."""
352
+ return self._extended.manager
353
+
354
+ @manager.setter
355
+ def manager(self, value):
356
+ self._extended.manager = value
@@ -0,0 +1,247 @@
1
+ """
2
+ XLSX Encryption Cryptographic Operations
3
+
4
+ This module implements cryptographic operations for XLSX encryption/decryption
5
+ according to MS-OFFCRYPTO (Agile Encryption).
6
+ """
7
+
8
+ import hashlib
9
+ import os
10
+ import struct
11
+ from Crypto.Cipher import AES
12
+
13
+
14
+ class PasswordDerivation:
15
+ """Password derivation helpers for Agile encryption."""
16
+
17
+ @staticmethod
18
+ def derive_hash_agile(password, salt, hash_algorithm, spin_count):
19
+ """
20
+ Derive H_n for Agile encryption (MS-OFFCRYPTO 2.3.4.11).
21
+
22
+ H_0 = Hash(salt + password)
23
+ H_i = Hash(i + H_{i-1}) for i in [0, spin_count)
24
+ """
25
+ password_bytes = password.encode('utf-16le')
26
+ hash_func_name = hash_algorithm.algorithm_name.lower()
27
+
28
+ h = hashlib.new(hash_func_name)
29
+ h.update(salt)
30
+ h.update(password_bytes)
31
+ current = h.digest()
32
+
33
+ for i in range(spin_count):
34
+ h = hashlib.new(hash_func_name)
35
+ h.update(struct.pack('<I', i))
36
+ h.update(current)
37
+ current = h.digest()
38
+
39
+ return current
40
+
41
+ @staticmethod
42
+ def derive_key_with_block_key(h_base, block_key, hash_algorithm, key_bits):
43
+ """
44
+ Derive key from H_n and block key (MS-OFFCRYPTO 2.3.4.11).
45
+ """
46
+ hash_func_name = hash_algorithm.algorithm_name.lower()
47
+ h = hashlib.new(hash_func_name)
48
+ h.update(h_base)
49
+ h.update(block_key)
50
+ derived = h.digest()
51
+
52
+ key_bytes = key_bits // 8
53
+ if len(derived) >= key_bytes:
54
+ return derived[:key_bytes]
55
+ return derived + (b'\x36' * (key_bytes - len(derived)))
56
+
57
+ @staticmethod
58
+ def derive_iv_agile(salt, block_key, hash_algorithm, block_size):
59
+ """
60
+ Derive IV for Agile encryption (MS-OFFCRYPTO 2.3.4.12).
61
+
62
+ If block_key is None, IV = salt (padded/truncated to block_size).
63
+ Otherwise IV = Hash(salt + block_key), padded/truncated to block_size.
64
+ """
65
+ if block_key is None:
66
+ iv = salt
67
+ else:
68
+ hash_func_name = hash_algorithm.algorithm_name.lower()
69
+ h = hashlib.new(hash_func_name)
70
+ h.update(salt)
71
+ h.update(block_key)
72
+ iv = h.digest()
73
+
74
+ if len(iv) >= block_size:
75
+ return iv[:block_size]
76
+ return iv + (b'\x36' * (block_size - len(iv)))
77
+
78
+
79
+ class EncryptionVerifier:
80
+ """Encryption verifier generation and validation."""
81
+
82
+ BLOCK_KEY_VERIFIER = bytes.fromhex('fea7d2763b4b9e79')
83
+ BLOCK_KEY_VERIFIER_HASH = bytes.fromhex('d7aa0f6d3061344e')
84
+ BLOCK_KEY_KEYVALUE = bytes.fromhex('146e0be7abacd0d6')
85
+ BLOCK_KEY_DATA_INTEGRITY_KEY = bytes.fromhex('5fb2ad010cb9e1f6')
86
+ BLOCK_KEY_DATA_INTEGRITY_VALUE = bytes.fromhex('a0677f02b22c8433')
87
+
88
+ @staticmethod
89
+ def _pad_zero(data, block_size):
90
+ if len(data) % block_size == 0:
91
+ return data
92
+ pad_len = block_size - (len(data) % block_size)
93
+ return data + (b'\x00' * pad_len)
94
+
95
+ @staticmethod
96
+ def generate_verifier_agile(password, salt, hash_algorithm, cipher_algorithm, spin_count):
97
+ """
98
+ Generate PasswordKeyEncryptor fields (MS-OFFCRYPTO 2.3.4.13).
99
+ """
100
+ verifier_input = os.urandom(16)
101
+ hash_func_name = hash_algorithm.algorithm_name.lower()
102
+ verifier_hash = hashlib.new(hash_func_name, verifier_input).digest()
103
+
104
+ h_base = PasswordDerivation.derive_hash_agile(
105
+ password, salt, hash_algorithm, spin_count
106
+ )
107
+
108
+ verifier_key = PasswordDerivation.derive_key_with_block_key(
109
+ h_base, EncryptionVerifier.BLOCK_KEY_VERIFIER,
110
+ hash_algorithm, cipher_algorithm.key_bits
111
+ )
112
+ iv = PasswordDerivation.derive_iv_agile(
113
+ salt, None, hash_algorithm, cipher_algorithm.block_size
114
+ )
115
+ encrypted_verifier = AES.new(verifier_key, AES.MODE_CBC, iv).encrypt(verifier_input)
116
+
117
+ hash_key = PasswordDerivation.derive_key_with_block_key(
118
+ h_base, EncryptionVerifier.BLOCK_KEY_VERIFIER_HASH,
119
+ hash_algorithm, cipher_algorithm.key_bits
120
+ )
121
+ verifier_hash_padded = EncryptionVerifier._pad_zero(
122
+ verifier_hash, cipher_algorithm.block_size
123
+ )
124
+ encrypted_verifier_hash = AES.new(hash_key, AES.MODE_CBC, iv).encrypt(verifier_hash_padded)
125
+
126
+ key_value = os.urandom(cipher_algorithm.key_bits // 8)
127
+ key_value_key = PasswordDerivation.derive_key_with_block_key(
128
+ h_base, EncryptionVerifier.BLOCK_KEY_KEYVALUE,
129
+ hash_algorithm, cipher_algorithm.key_bits
130
+ )
131
+ encrypted_key_value = AES.new(key_value_key, AES.MODE_CBC, iv).encrypt(key_value)
132
+
133
+ return {
134
+ 'verifier_salt': salt,
135
+ 'encrypted_verifier': encrypted_verifier,
136
+ 'encrypted_verifier_hash': encrypted_verifier_hash,
137
+ 'encrypted_key_value': encrypted_key_value,
138
+ 'key_value': key_value,
139
+ 'password_hash': h_base
140
+ }
141
+
142
+ @staticmethod
143
+ def verify_password_agile(password, salt, encrypted_verifier, encrypted_verifier_hash,
144
+ hash_algorithm, cipher_algorithm, spin_count):
145
+ """
146
+ Verify password for Agile encryption (MS-OFFCRYPTO 2.3.4.13).
147
+
148
+ Returns:
149
+ H_n if password is correct, None otherwise.
150
+ """
151
+ try:
152
+ h_base = PasswordDerivation.derive_hash_agile(
153
+ password, salt, hash_algorithm, spin_count
154
+ )
155
+
156
+ verifier_key = PasswordDerivation.derive_key_with_block_key(
157
+ h_base, EncryptionVerifier.BLOCK_KEY_VERIFIER,
158
+ hash_algorithm, cipher_algorithm.key_bits
159
+ )
160
+ iv = PasswordDerivation.derive_iv_agile(
161
+ salt, None, hash_algorithm, cipher_algorithm.block_size
162
+ )
163
+ decrypted_verifier = AES.new(verifier_key, AES.MODE_CBC, iv).decrypt(encrypted_verifier)
164
+
165
+ hash_func_name = hash_algorithm.algorithm_name.lower()
166
+ computed_hash = hashlib.new(hash_func_name, decrypted_verifier).digest()
167
+
168
+ hash_key = PasswordDerivation.derive_key_with_block_key(
169
+ h_base, EncryptionVerifier.BLOCK_KEY_VERIFIER_HASH,
170
+ hash_algorithm, cipher_algorithm.key_bits
171
+ )
172
+ decrypted_hash = AES.new(hash_key, AES.MODE_CBC, iv).decrypt(encrypted_verifier_hash)
173
+
174
+ if computed_hash != decrypted_hash[:len(computed_hash)]:
175
+ return None
176
+
177
+ return h_base
178
+ except Exception:
179
+ return None
180
+
181
+ @staticmethod
182
+ def decrypt_key_value(h_base, key_salt, encrypted_key_value, hash_algorithm, cipher_algorithm):
183
+ """Decrypt intermediate key value (MS-OFFCRYPTO 2.3.4.13)."""
184
+ key_value_key = PasswordDerivation.derive_key_with_block_key(
185
+ h_base, EncryptionVerifier.BLOCK_KEY_KEYVALUE,
186
+ hash_algorithm, cipher_algorithm.key_bits
187
+ )
188
+ iv = PasswordDerivation.derive_iv_agile(
189
+ key_salt, None, hash_algorithm, cipher_algorithm.block_size
190
+ )
191
+ return AES.new(key_value_key, AES.MODE_CBC, iv).decrypt(encrypted_key_value)
192
+
193
+ @staticmethod
194
+ def decrypt_data_integrity(secret_key, package_salt, encrypted_hmac_key, encrypted_hmac_value,
195
+ hash_algorithm, block_size, hash_size):
196
+ """Decrypt HMAC key and value (MS-OFFCRYPTO 2.3.4.14)."""
197
+ iv_key = PasswordDerivation.derive_iv_agile(
198
+ package_salt, EncryptionVerifier.BLOCK_KEY_DATA_INTEGRITY_KEY,
199
+ hash_algorithm, block_size
200
+ )
201
+ iv_val = PasswordDerivation.derive_iv_agile(
202
+ package_salt, EncryptionVerifier.BLOCK_KEY_DATA_INTEGRITY_VALUE,
203
+ hash_algorithm, block_size
204
+ )
205
+ hmac_key = AES.new(secret_key, AES.MODE_CBC, iv_key).decrypt(encrypted_hmac_key)
206
+ hmac_value = AES.new(secret_key, AES.MODE_CBC, iv_val).decrypt(encrypted_hmac_value)
207
+ return hmac_key[:hash_size], hmac_value[:hash_size]
208
+
209
+
210
+ class PackageEncryption:
211
+ """Package data encryption and decryption."""
212
+
213
+ @staticmethod
214
+ def encrypt_package_agile(data, key, package_salt, hash_algorithm, block_size):
215
+ """Encrypt package data using Agile encryption (MS-OFFCRYPTO 2.3.4.15)."""
216
+ encrypted = bytearray()
217
+ segment_size = 4096
218
+
219
+ for segment_index in range(0, len(data), segment_size):
220
+ chunk = data[segment_index:segment_index + segment_size]
221
+ block_key = struct.pack('<I', segment_index // segment_size)
222
+ iv = PasswordDerivation.derive_iv_agile(
223
+ package_salt, block_key, hash_algorithm, block_size
224
+ )
225
+ if len(chunk) % block_size != 0:
226
+ chunk = chunk + (b'\x00' * (block_size - (len(chunk) % block_size)))
227
+ cipher = AES.new(key, AES.MODE_CBC, iv)
228
+ encrypted.extend(cipher.encrypt(chunk))
229
+
230
+ return bytes(encrypted)
231
+
232
+ @staticmethod
233
+ def decrypt_package_agile(encrypted_data, key, package_salt, hash_algorithm, block_size):
234
+ """Decrypt package data using Agile encryption (MS-OFFCRYPTO 2.3.4.15)."""
235
+ decrypted = bytearray()
236
+ segment_size = 4096
237
+
238
+ for segment_index in range(0, len(encrypted_data), segment_size):
239
+ chunk = encrypted_data[segment_index:segment_index + segment_size]
240
+ block_key = struct.pack('<I', segment_index // segment_size)
241
+ iv = PasswordDerivation.derive_iv_agile(
242
+ package_salt, block_key, hash_algorithm, block_size
243
+ )
244
+ cipher = AES.new(key, AES.MODE_CBC, iv)
245
+ decrypted.extend(cipher.decrypt(chunk))
246
+
247
+ return bytes(decrypted)