Python(Django)でパスワード付きのExcelファイルを生成する

Django

はじめに

この記事ではPythonでパスワード付きのExcelファイルを生成する方法を説明しています。
実際にはDjangoで利用しています。

openpyxlライブラリはExceファイルの読み書きができますが、パスワードはWorkbookやWorksheetにしか設定できません。
そのため、Excelファイルにパスワードを設定するために「secure-spreadsheet」を使います。
「secure-spreadsheet」はnpmでインストールするJavaScript製のコマンドラインプログラムでPython、PHP、Rubyで使えるようです。

「secure-spreadsheet」の利点はOSに依存しない、Microsoft Offceをインストールしなくても使えることです。

環境

pipenvの仮想環境を使っています。

 - Debian bullseye 64bit
 - Python 3.9.8
 - Django 4.0.0

Djangoのディレクトリ構造は以下の通りです。

/home/www/wsgi/proj
   ┣ -- .venv
   ┣ -- proj
   ┣ -- app
   ┣ -- npm
   ┣ -- templates
   ┣ -- static
   ┣ -- db.sqlite3
   (以下、省略)


secure-spreadsheetのインストール

Djangoのプロジェクトディレクトリにnpmというディレクトリを作成してからインストールします。
ローカルにインストールするので「-g」オプションは付けません。
OSがDebianなのでユーザとグループはwww-dataです。

GitHub - ankane/secure-spreadsheet: Encrypt and password protect sensitive CSV and XLSX files
Encrypt and password protect sensitive CSV and XLSX files - ankane/secure-spreadsheet
sudo apt update
sudo apt install npm

cd /home/www/wsgi/proj
sudo mkdir npm
cd npm
sudo npm install secure-spreadsheet

cd ..
sudo chown -R www-data.www-data /home/www/wsgi/proj/npm


適当なExcelファイル(test.xlsx)を準備して、パスワードを「sss」として動作テストを行います。
生成した「output.xlsx」をパスワードを入力して開ければ成功です。


cd /home/www/wsgi/proj
cat test.xlsx | ./npm/node_modules/.bin/secure-spreadsheet --password sss --input-format xlsx > output.xlsx

secure-spreadsheetをアップデートするには以下のコマンドを実行します。

cd /home/www/wsgi/proj
sudo npm update secure-spreadsheet

Excelのパスワードに使える文字列や記号

Excelのパスワードに使える最大の文字数は15文字です。
また、使える文字種は以下のようです。
・文字: A~Z、a~z
・数字: 0~9
・記号: ! @ # $ % ^ & * – _ + = [ ] { } | \ : ‘ , . ? / ` ~ ” ( ) ; < >

Protect a Word document with a password - Microsoft Support
How to password protect a Word document to prevent unauthorized access.

Django(Python)で使ってみる

パスワード生成方法はPythonのドキュメントにサンプルがあります。

secrets --- 機密を扱うために安全な乱数を生成する
ソースコード: Lib/secrets.py The secrets module is used for generating cryptographically strong random num...

本格運用するなら、パスワードの生成方法は不完全です。
以下が参考になるかもしれません。

Build software better, together
GitHub is where people build software. More than 150 million people use GitHub to discover, fork, an...

試したところ、コマンドラインで起動するせいか一部の記号が使えませんでした。
回避方法があれば良いのですが調査中です。

(いろいろ省略)
# Excelファイルの書き込み
from openpyxl import Workbook
from openpyxl.styles.fonts import Font
#from openpyxl.styles.colors import Color
from openpyxl.styles.borders import Border, Side
from openpyxl.styles import PatternFill

# パスワード生成
import string
import secrets
import subprocess

SECURE_SPREADSHEET = '/home/www/wsgi/proj/npm/node_modules/.bin/secure-spreadsheet'
# settings.pyに記述すると汎用性が上がる
#from django.conf import settings
#SECURE_SPREADSHEET = getattr(settings, "SECURE_SPREADSHEET", None)

(いろいろ省略)

class SampleView(ListView):
    (いろいろ省略)
    #
    # Excelファイル作成
    # 行と列の指定は「1」から始まる。
    #
    def create_excelfile(self, fname):
        wb = Workbook()
        ws = wb.active

        # 印刷設定
        # 用紙の向き:横
        # 縦・横の印刷とも1ページにまとめる
        ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
        ws.page_setup.fitToWidth  = 1
        ws.page_setup.fitToHeight = 1
        ws.sheet_properties.pageSetUpPr.fitToPage = True

        ws.cell(row=1, column = 1).value = 'テストデータ'
        wb.save(fname)

    #
    # パスワード生成
    #
    def pass_gen(self, size):
        chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
       #chars += '!@#$%^&*()-_+=[]{}<>?'
        chars += '!#^*()-_+=[]{}'
        #return ''.join(secrets.choice(chars) for x in range(size))

        # アルファべットと数字からなり、小文字を少なくとも1つと数字を少なくとも3つ含む。
        while True:
            password = ''.join(secrets.choice(chars) for x in range(size))
            if ( any(c.islower() for c in password )
                    and any(c.isupper() for c in password)
                    and sum(c.isdigit() for c in password) >= 3):
                break
        return password

    #
    # Excelファイルにパスワードを設定
    #
    def password_excelfile(self, fname, passwd):
        cmd = 'cat ' + str(fname) + ' | ' + str(SECURE_SPREADSHEET) + ' --password ' + '"' + passwd + '"' + ' --input-format xlsx'
       #cmd = 'cat ' + str(fname) + ' | ' + str(SECURE_SPREADSHEET) + ' --password ' + passwd + ' --input-format xlsx'
        result = subprocess.check_output(cmd, shell=True)
        with open(fname, 'wb') as f:
            f.write(result)

    #
    # Submitの処理
    #
    def post(self, request, *args, **kwargs):
        fname_excel = '/home/www/wsgi/proj/media/test.xlsx'
        (いろいろ省略)
        # Excelファイルを生成する。
        self.create_excelfile(fname_excel)
        # パスワードを12文字で生成するように指定。
        self.password_excelfile(fname_excel, self.pass_gen(12))
        (いろいろ省略)

Windowsの場合(win32com)

OSがWindowsの場合は、Excelがインストールされていればwin32comモジュールを使うとパスワードが設定できるようです。

pythonでエクセルファイルにパスワードを設定したい | teratail
!(0bd70e025a85aac244e25a9d84d3993d.png)エクセルを開いた際に、pythonコード内でパスワードを設定してエクセルを保存したい。py

その他

popenを使ってもできるとの情報がありますが試していません。

Add password in xlsx using secure-spreadsheet
im try to put password in my excel filedef excel_file test = Axlsx::Package.new do |p| p.workbook.ad...

さいごに

生成したExcelにパスワードを設定することができました。
お役に立てば幸いです。
なお、「secure-spreadsheet」のサイトに掲載されているサンプルは、テキスト(CSVファイル)をbytes文字列として入力して、パスワード付きのExcelファイルに変換するものです。

Comments