Commit 826bee0bedbf0aa9428f22773e11372ba72fc343

Authored by Christian Herdtweck
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,6 +133,8 @@ def is_encrypted(olefile):
133 return False 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 WRITE_PROTECT_ENCRYPTION_PASSWORD = 'VelvetSweatshop' 138 WRITE_PROTECT_ENCRYPTION_PASSWORD = 'VelvetSweatshop'
137 139
138 140
@@ -142,22 +144,19 @@ def _check_msoffcrypto(): @@ -142,22 +144,19 @@ def _check_msoffcrypto():
142 raise ImportError('msoffcrypto-tools could not be imported') 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 :param str filename: path to an ole file on disc 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 :param temp_file_args: arguments for :py:func:`tempfile.mkstemp` e.g., 160 :param temp_file_args: arguments for :py:func:`tempfile.mkstemp` e.g.,
162 `dirname` or `prefix`. `suffix` will default to 161 `dirname` or `prefix`. `suffix` will default to
163 suffix of input `filename`; `text` will be ignored 162 suffix of input `filename`; `text` will be ignored
@@ -167,42 +166,51 @@ def try_decrypt_write_protection(filename, **temp_file_args): @@ -167,42 +166,51 @@ def try_decrypt_write_protection(filename, **temp_file_args):
167 """ 166 """
168 _check_msoffcrypto() 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 with open(filename, 'rb') as reader: 175 with open(filename, 'rb') as reader:
172 crypto_file = msoffcrypto.OfficeFile(reader) 176 crypto_file = msoffcrypto.OfficeFile(reader)
173 if not crypto_file.is_encrypted(): 177 if not crypto_file.is_encrypted():
174 raise ValueError('Given input file {} is not encrypted!' 178 raise ValueError('Given input file {} is not encrypted!'
175 .format(filename)) 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 write_descriptor = None 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 write_handle.close() 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