Python + msoffcrypto-toolで、Excelの読み取りパスワードを解除する

前回、Python + openpyxlで、ブックやシートの保護・解除を試しました。
Python + openpyxlで、ブックやシートの保護・解除を試してみた - メモ的な思考的な

 
その際、openpyxlではExcelの読み取りパスワードを解除できませんでした。

ただ、読み取りパスワード設定済のExcelファイルを読み込みたいことがあったため、別のライブラリがないかを調べてみました。

なお、今回もパスワードは把握している前提です。パスワードのクラックではありません。

 
目次

 

環境

 

調査したライブラリ

xlwings

 
Web上に、xlwings を使って読み取りパスワードを解除する事例がありました。
Load password protected Excel files into Pandas DataFrame - David Hamann

 
ただ、 wb = xw.Book(PATH) にてExcelファイルを開く際に、読み取りパスワード入力ダイアログが表示されました。

f:id:thinkAmi:20181215113543p:plain:w300

 
手動でパスワード入力するのが手間なため、他のライブラリを探すことにしました。

 

pywin32

 
読み取りパスワードを解除するよう

excel = win32com.client.Dispatch('Excel.Application')
book = excel.Workbooks.Open('対象のファイル', False, False, None, '読み取りパスワード')
book.SaveAs('解除後のファイル', None, '')
book.Close()

と実装したところ、読み取りパスワード入力ダイアログが表示されずに解除できました。

しかし、 pywin32Windows上でしか動作せず、Macでは利用できないため、他のライブラリを探すことにしました。

 
ちなみに、最近の pywin32 は、 pypiwin32 としてPyPIからインストールできるようです。
https://pypi.org/project/pypiwin32/

名前などが怪しいですが、メンテナーが pywin32 とほぼ同じなので、大丈夫な気がします。
https://pypi.org/project/pywin32/

 

msoffcrypto-tool

 
GitHubのREADMEに従い、Macのターミナルから実行したところ、読み取りパスワード入力ダイアログが表示されずにパスワードが解除されました。

 
README上ではライブラリとして使う方法も記載されていたため、 msoffcrypto-tool を使うことにしました。

 

msoffcrypto-toolの実装

実装の流れは以下です。

# 対象のExcelを開く
with file.open(mode='rb') as locked:

    # OfficeFileオブジェクトにする
    office_file = msoffcrypto.OfficeFile(locked)

    # パスワードを設定する
    office_file.load_key(password=PASSWORD)

    # 解除する(ファイルは上書き保存される)
    office_file.decrypt(unlocked)

これにより、Excelファイル(xlsx, xlsの両方)とも、読み取りパスワードを解除できました。

 
ただ、読み取りパスワードが設定されていないExcelファイルに対して実行すると、例外が発生しました。

xlsxxls では、例外が発生する箇所が異なりました。

  • xlsx
    • office_file = msoffcrypto.OfficeFile(locked) のタイミング
  • xls
    • office_file.load_key(password=PASSWORD) のタイミング

 
なお、msoffcrypto.OfficeFileには、読み取りパスワードが設定されているかをチェックするメソッド is_encrypted() があります。

ただし、 xlsx 形式では常に True が返ってくる実装になっていることに注意が必要です。
https://github.com/nolze/msoffcrypto-tool/blob/v4.6.3/msoffcrypto/format/ooxml.py#L143

 
以上より、読み取りパスワードが設定されていないファイルでも動作するように修正してみました。

import msoffcrypto
import pathlib


BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
PASSWORD = '12345'
UNLOCKED_FILE = BASE_DIR.joinpath('unlocked.xlsx')


def unlock():
    for file in BASE_DIR.iterdir():
        # Excelファイルだけ対象
        if not file.is_file() or file.suffix not in ['.xlsx', '.xls']:
            continue

        with file.open(mode='rb') as locked:
            # xlsxファイルの場合、読み取りパスワード無しのファイルは例外が発生する
            # is_encrypted()には以下の記載がある
            #
            # https://github.com/nolze/msoffcrypto-tool/blob/v4.6.3/msoffcrypto/format/ooxml.py#L143
            # def is_encrypted(self):
            #     # olefile cannot process non password protected ooxml files.
            #     # Hence if it has reached here it must be password protected.
            #     return True
            try:
                office_file = msoffcrypto.OfficeFile(locked)
            except OSError:
                if file.suffix == '.xlsx':
                    continue
                raise

            # 読み取りパスワードが設定されているかをチェック(xlsxはチェックできないので、xls向け)
            if not office_file.is_encrypted():
                continue

            # パスワードをセット
            # xlsでパスワードが設定されていない場合、load_key()時にエラーが出るため、事前にチェックが必要
            #   File "python3.6/site-packages/msoffcrypto/format/xls97.py", line 479, in load_key
            #     # Skip to FilePass; TODO: Raise exception if not encrypted
            #     num, size = workbook.skip_to(recordNameNum['FilePass'])
            #   File "python3.6/site-packages/msoffcrypto/format/xls97.py", line 428, in skip_to
            #     raise Exception("Record not found")
            # Exception: Record not found
            office_file.load_key(password=PASSWORD)

            # 読み取りパスワード解除後のファイルは、拡張子の前に '_unlocked' を付けて保存する
            unlocked_file = BASE_DIR.joinpath(f'{file.stem}_unlocked{file.suffix}')
            with unlocked_file.open(mode='wb') as unlocked:
                # パスワードを解除
                office_file.decrypt(unlocked)


if __name__ == '__main__':
    unlock()

 

ソースコード

GitHubに上げました。 msoffcrypto_tool ディレクトリの中が今回のファイルです。
https://github.com/thinkAmi-sandbox/python_excel_libraries-sample