Python + openpyxlで、ブックやシートの保護・解除を試してみた

Excelには、ブックやシートを保護するための機能があります。

  • 読み取りパスワード
  • 書き込みパスワード
  • ブックの保護
  • シートの保護

 
それらをopnepyxlでやるにはどうしたら良いかを試した時のメモです。

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

また、公式ドキュメントだとこのあたりのことです。
Protection — openpyxl 2.5.12 documentation

 
目次

 

環境

 

読み取りパスワード・書き込みパスワードの設定は不可

openpyxlでは読み取りパスワードと書き込みパスワードは設定できないようです。

issueによると

because it is proprietary MS code not covered by the OOXML specification.

https://bitbucket.org/openpyxl/openpyxl/issues/193/can-i-open-password-protected-excel-files

とのことです。

 

ブックの保護

パスワード無しでブックの保護を行う

Workbookオブジェクトには security 属性があり、それに対して保護の設定を行うことで実現できます。

ブックの保護なしのファイルを読み込むと、 security 属性は None です。

そのため、 openpyxl.workbook.protection.WorkbookProtection オブジェクトを設定した上で、 lockStructure = True とします。

# ブックを読み込み
wb = _load(NO_PROTECTION_FILE)

# ブックを保護
wb.security = WorkbookProtection()
wb.security.lockStructure = True

# 保存
_save(wb, f'No_1_PROTECT_book_using_{NO_PROTECTION_FILE}')


# _load()と_save()は以下の関数(以降のソースコードも同様)
# ディレクトリ名を付けると一行が長くなって見づらかったので、関数化した
def _load(file_name):
    return openpyxl.load_workbook(BASE_FILE_DIR.joinpath(file_name))

def _save(workbook, file_name):
    workbook.save(RESULT_FILE_DIR.joinpath(file_name))

 
結果です。

f:id:thinkAmi:20181215100903p:plain:w300

 

パスワードありでブックを保護する

パスワード無しに加え、 wb.security.workbook_password = PASSWORD_FOR_BOOK を追加します。

wb = _load(NO_PROTECTION_FILE)

# ブックを保護
wb.security = WorkbookProtection()
wb.security.lockStructure = True
wb.security.workbook_password = PASSWORD_FOR_BOOK

# 保存
_save(wb, f'No_2_PROTECT_book_using_{NO_PROTECTION_FILE}')

 
結果です。ブックの保護を解除しようとすると、パスワード入力が求められます。

f:id:thinkAmi:20181215101129p:plain:w300

 

シートの保護

パスワード無しでシートを保護する

シートオブジェクトの protection 属性にある enable() メソッドを使います。

なお、公式ドキュメントではブックオブジェクトに対して設定するよう記載されていましたが、シートオブジェクトが正しそうです。
https://openpyxl.readthedocs.io/en/stable/protection.html#worksheet-protection

wb = _load(NO_PROTECTION_FILE)

# 対象のワークシートオブジェクトを取得する
ws = wb['Sheet1']

# パスワード無しで保護
ws.protection.enable()

# 保存
_save(wb, f'No_3_PROTECT_sheet_without_password_{NO_PROTECTION_FILE}')

 
結果です。

f:id:thinkAmi:20181215101422p:plain:w300

 

パスワードありでシートを保護する

パスワードなしに加え、 ws.protection.password = PASSWORD_FOR_SHEET を使います。

# 対象のワークシートオブジェクトを取得する
ws = wb['Sheet1']

# パスワードをセット
ws.protection.password = PASSWORD_FOR_SHEET

# シートを保護
ws.protection.enable()

 

シートの保護時に「このシートのすべてのユーザーに許可する操作」を設定する

Excelでは、シートの保護時に このシートのすべてのユーザーに許可する操作 というチェックボックスがいくつかあります。

openpyxlでそれらを有効化する場合は以下となります。

# 対象のワークシートオブジェクトを取得する
ws = wb['Sheet1']

# このシートのすべてのユーザーに許可する操作
ws.protection.objects = True                # オブジェクトの編集
ws.protection.scenarios = True              # シナリオの編集
ws.protection.formatCells = True            # セルの書式設定
ws.protection.formatColumns = True          # 列の書式設定
ws.protection.formatRows = True             # 行の書式設定
ws.protection.insertColumns = True          # 列の挿入
ws.protection.insertRows = True             # 行の挿入
ws.protection.insertHyperlinks = True       # ハイパーリンクの挿入
ws.protection.deleteColumns = True          # 列の削除
ws.protection.deleteRows = True             # 行の削除
ws.protection.selectLockedCells = True      # ロックされたセルの選択
ws.protection.selectUnlockedCells = True    # ロックされていないセルの選択
ws.protection.sort = True                   # 並べ替え
ws.protection.autoFilter = True             # フィルター
ws.protection.pivotTables = True            # ピボットテーブルレポート

# パスワード無しで保護
ws.protection.enable()

 

セルをロックしないで、シートを保護する

Excelのデフォルトでは、シートを保護すると全セルにロックがかかり、セルへの入力ができなくなります。

一部のセルのみ入力可能にしてシートを保護したい場合、事前にセルのロックを解除します。

openpyxlでは、以下のようにします。

from openpyxl.styles import Protection

wb = _load(NO_PROTECTION_FILE)
ws = wb['Sheet1']

# ロックを外したい(保護されない)セルを選ぶ
unlock_cells = ws['A1:B3']

# 取得したデータや型を見ると、行ごとにタプルでセルが入っている
print(f'type: ({type(unlock_cells)}), values: {unlock_cells}')
# => type: (<class 'tuple'>), values: ((<Cell 'Sheet1'.A1>, <Cell 'Sheet1'.B1>),
#                                      (<Cell 'Sheet1'.A2>, <Cell 'Sheet1'.B2>),
#                                      (<Cell 'Sheet1'.A3>, <Cell 'Sheet1'.B3>))

# chain.from_iterable()でネストタプルを平坦にしてから処理 (使ってみたかっただけ)
# 普通は for の2重ループで良いのかな
for cell in chain.from_iterable(unlock_cells):

    # 念のための確認
    print(f'type: ({type(cell)}), values: {cell}')
    # => type: (<class 'openpyxl.cell.cell.Cell'>), values: <Cell 'Sheet1'.A1>

    # ロックを解除
    cell.protection = Protection(locked=False)

# シートを保護
ws.protection.enable()

 
結果です。

ロックされたセルの場合、入力しようとすると、以下のようにメッセージが表示されます。

f:id:thinkAmi:20181215102139p:plain:w300

一方、ロックされていないセルの場合、入力が可能です。

f:id:thinkAmi:20181215102154p:plain:w300

 

ブックの保護を解除

ブックの保護時とは逆で、 wb.security.lockStructure = False とします。

また、パスワード付きブックの保護の場合は、 wb.security.workbook_password = PASSWORD_FOR_BOOK を設定します。

wb = _load(BOOK_PROTECTION_FILE)

# 保護したときのパスワードをセット
wb.security.workbook_password = PASSWORD_FOR_BOOK

# ブックの保護を解除
# wb.security.lock_structureでも良い:Aliasが設定されている
wb.security.lockStructure = False

_save(wb, f'No_6_UNPROTECT_{BOOK_PROTECTION_FILE}')

 
結果です。ブックの保護が解除されています。

f:id:thinkAmi:20181215102630p:plain:w300

 

シートの保護を解除

シートの保護とは別のメソッド ws.protection.disable() を使います。

また、パスワードで保護している場合は、 ws.protection.password = PASSWORD_FOR_SHEET で設定する必要があります。

wb = _load(SHEET_PROTECTION_WITH_PASSWORD_FILE)
ws = wb['Sheet1']

# シートを保護したときのパスワードをセット
ws.protection.password = PASSWORD_FOR_SHEET

# シートの保護を解除
ws.protection.disable()

_save(wb, f'No_8_UNPROTECT_{SHEET_PROTECTION_WITH_PASSWORD_FILE}')

 
結果です。指定したシート Sheet1 のみシートの保護が解除されています。

f:id:thinkAmi:20181215103043p:plain:w300

 

ソースコード

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