Commit 826bee0bedbf0aa9428f22773e11372ba72fc343
1 parent
756fd308
crypto: generalize decrypt function
Showing
1 changed file
with
49 additions
and
41 deletions
oletools/crypto.py
| ... | ... | @@ -133,6 +133,8 @@ def is_encrypted(olefile): |
| 133 | 133 | return False |
| 134 | 134 | |
| 135 | 135 | |
| 136 | +#: one way to achieve "write protection" in office files is to encrypt the file | |
| 137 | +#: using this password | |
| 136 | 138 | WRITE_PROTECT_ENCRYPTION_PASSWORD = 'VelvetSweatshop' |
| 137 | 139 | |
| 138 | 140 | |
| ... | ... | @@ -142,22 +144,19 @@ def _check_msoffcrypto(): |
| 142 | 144 | raise ImportError('msoffcrypto-tools could not be imported') |
| 143 | 145 | |
| 144 | 146 | |
| 145 | -def try_decrypt_write_protection(filename, **temp_file_args): | |
| 147 | +def decrypt(filename, passwords=None, **temp_file_args): | |
| 146 | 148 | """ |
| 147 | - Try to decrypt a file that was encrypted just to achieve write protection. | |
| 149 | + Try to decrypt an encrypted file | |
| 148 | 150 | |
| 149 | - One way to achieve write protection / read only access to an office | |
| 150 | - document is to embed the file encrypted into an OLE file using a fixed | |
| 151 | - password. This is of course no real protection, just a way to tell GUIs to | |
| 152 | - discourage modifications of the file. | |
| 153 | - | |
| 154 | - This function tries to decrypt the given file using that standard password | |
| 155 | - and returns an ole file accessing the decrypted data. If the decryption | |
| 156 | - fails the encryption is probably a real one and None is returned. | |
| 157 | - | |
| 158 | - Decryption output will be sent to a temporary file, whose name is returned. | |
| 151 | + This function tries to decrypt the given file using a given set of | |
| 152 | + passwords. If no password is given, tries the standard password for write | |
| 153 | + protection. Creates a file with decrypted data whose file name is returned. | |
| 154 | + If the decryption fails, None is returned. | |
| 159 | 155 | |
| 160 | 156 | :param str filename: path to an ole file on disc |
| 157 | + :param passwords: list/set/tuple/... of passwords or a single password or | |
| 158 | + None | |
| 159 | + :type passwords: iterable or str or None | |
| 161 | 160 | :param temp_file_args: arguments for :py:func:`tempfile.mkstemp` e.g., |
| 162 | 161 | `dirname` or `prefix`. `suffix` will default to |
| 163 | 162 | suffix of input `filename`; `text` will be ignored |
| ... | ... | @@ -167,42 +166,51 @@ def try_decrypt_write_protection(filename, **temp_file_args): |
| 167 | 166 | """ |
| 168 | 167 | _check_msoffcrypto() |
| 169 | 168 | |
| 170 | - result = None | |
| 169 | + if passwords is None: | |
| 170 | + passwords = (WRITE_PROTECT_ENCRYPTION_PASSWORD, ) | |
| 171 | + elif isinstance(passwords, str): | |
| 172 | + passwords = (passwords, ) | |
| 173 | + | |
| 174 | + decrypt_file = None | |
| 171 | 175 | with open(filename, 'rb') as reader: |
| 172 | 176 | crypto_file = msoffcrypto.OfficeFile(reader) |
| 173 | 177 | if not crypto_file.is_encrypted(): |
| 174 | 178 | raise ValueError('Given input file {} is not encrypted!' |
| 175 | 179 | .format(filename)) |
| 176 | - try: | |
| 177 | - crypto_file.load_key(password=WRITE_PROTECT_ENCRYPTION_PASSWORD) | |
| 178 | - except Exception: | |
| 179 | - return None # password verification failed | |
| 180 | 180 | |
| 181 | - # create temp file | |
| 182 | - if 'suffix' not in temp_file_args: | |
| 183 | - temp_file_args['suffix'] = splitext(filename)[1] | |
| 184 | - temp_file_args['text'] = False | |
| 181 | + for password in passwords: | |
| 182 | + try: | |
| 183 | + crypto_file.load_key(password=password) | |
| 184 | + except Exception: | |
| 185 | + continue # password verification failed, try next | |
| 185 | 186 | |
| 186 | - write_descriptor = None | |
| 187 | - write_handle = None | |
| 188 | - try: | |
| 189 | - write_descriptor, result = mkstemp(**temp_file_args) | |
| 190 | - write_handle = os.fdopen(write_descriptor, 'wb') | |
| 191 | - crypto_file.decrypt(write_handle) | |
| 187 | + # create temp file | |
| 188 | + if 'suffix' not in temp_file_args: | |
| 189 | + temp_file_args['suffix'] = splitext(filename)[1] | |
| 190 | + temp_file_args['text'] = False | |
| 192 | 191 | |
| 193 | - # non-error cleanup: close file descriptor and handle | |
| 194 | - write_handle.close() | |
| 195 | - write_handle = None | |
| 196 | - # os.close(write_descriptor) already done by write_handle.close | |
| 197 | 192 | write_descriptor = None |
| 198 | - except Exception: | |
| 199 | - # error: clean up: close everything and del file ignoring errors; | |
| 200 | - # then re-raise original exception | |
| 201 | - if write_handle: | |
| 193 | + write_handle = None | |
| 194 | + try: | |
| 195 | + write_descriptor, decrypt_file = mkstemp(**temp_file_args) | |
| 196 | + write_handle = os.fdopen(write_descriptor, 'wb') | |
| 197 | + write_descriptor = None # is now handled via write_handle | |
| 198 | + crypto_file.decrypt(write_handle) | |
| 199 | + | |
| 200 | + # decryption was successfull; clean up and return | |
| 201 | + write_handle.close() | |
| 202 | + write_handle = None | |
| 203 | + break | |
| 204 | + except Exception: | |
| 205 | + # error: clean up: close everything and del file ignoring errors; | |
| 206 | + # then re-raise original exception | |
| 207 | + if write_handle: | |
| 202 | 208 | write_handle.close() |
| 203 | - elif write_descriptor: | |
| 204 | - os.close(write_descriptor) | |
| 205 | - if result and isfile(result): | |
| 206 | - os.unlink(result) | |
| 207 | - raise | |
| 208 | - return result | |
| 209 | + elif write_descriptor: | |
| 210 | + os.close(write_descriptor) | |
| 211 | + if decrypt_file and isfile(decrypt_file): | |
| 212 | + os.unlink(decrypt_file) | |
| 213 | + decrypt_file = None | |
| 214 | + raise | |
| 215 | + # if we reach this, all passwords were tried without success | |
| 216 | + return decrypt_file | ... | ... |