2020年3月29日日曜日

36: 編集パスワードをOpenDocumentファイルにUNOを用いてセットする

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

LibreOfficeまたはOpenOfficeを用いて、OpenDocument('.odt'、'.ods'、'.odp')ファイルへ、Java、C++、C#、Python、BeanShell、JavaScript、Basicから

話題


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Javaプログラミング言語
About: C++
About: Microsoft .NET Framework
About: Pythonプログラミング言語
About: LibreOffice Basic
About: Apache OpenOffice Basic
About: BeanShell
About: JavaScript

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、OpenDocument('.odt'、'.ods'、'.odp')ファイルに編集パスワードを自分のプログラムからセットする方法を知る。
ト書き
Hypothesizer 7、Objector 36A、Objector 36Bがコンピューターの前にいる。


オリエンテーション


Hypothesizer 7
本記事では、Office(OpenDocumentフォーマット('.odt'、'.ods'、'.odp')、Microsoft Officeバイナリーフォーマット('.doc'、'.xls')、Office Open XMLフォーマット('.docx', '.xlsx', '.pptx'))ドキュメントファイルに編集パスワードを私たちのプログラムからセットする方法を知ります。

Objector 36A
本当に?LibreOffice GUIは、Office Open XMLフォーマットファイルに編集パスワードをセットできないけど、オプションは示すくせに。

Hypothesizer 7
サー、GUIはできませんが、直接にUNO APIを用いることで、あなたはできます。

Objector 36A
ふーむ...

Hypothesizer 7
その機能を実装する前に、編集パスワードをセットするという行為に、どういう目的にどれほどの意義があるかを、私たちは吟味したいと思います。

Objector 36A
「私たちは〜したい」?別に私はしたくないけど。

Hypothesizer 7
サー、私はしたいのです、実のところ、そして、少なくとも私は吟味をします。

Objector 36A
「どれほどの意義があるか」ってどういうこと?とても意義があるでしょう、パスワードを知らない人はドキュメントを編集できないんだから!

Hypothesizer 7
サー、もっと綿密に考えてみましょう。あるプログラムが編集パスワードをただ無視すれば、そのプログラムを使って誰でもドキュメントを編集できます。

Objector 36A
はあ?「あるプログラムが〜ただ無視す」るってどういうこと?どの「プログラム」?「無視する」?

Hypothesizer 7
そのドキュメントファイルは、公開されたフォーマットのものなので、スキルがある者は誰でも、そのファイルを読み書きするプログラムを作成できます。

Objector 36A
だから?

Hypothesizer 7
そのファイルは既に暗号解読されている(そうでなければ、それは、編集パスワードが機能する状況ではありません)ので、そのプログラムはそのドキュメントのコンテンツを知っており、ユーザーに、そのコンテンツを編集させ、変更されたコンテンツをファイルに格納させてあげることができます、編集パスワードの存在などただ無視して。

Objector 36A
...そんなプログラムが私のネットワークにインストールされるのを禁止すればいいだけだけど。

Hypothesizer 7
そのようなプログラムがあなたのネットワークにインストールされるのを、システム的に阻止する必要があるでしょうね、ルールとして禁止するだけではなく、普通は。

Objector 36A
...もちろん。

Hypothesizer 7
そのようなプログラムがあなたのネットワーク内で作成されることもあなたは阻止する必要があるでしょうね、勿論。

Objector 36A
えーと、開発環境なんか、私のネットワーク内に許すつもりは全然ないけど。

Hypothesizer 7
GCC,Python、等もあなたのネットワーク内に確実に存在しないようにしなければならないでしょうね。

Objector 36A
Pythonもだめ?...ふーむ...

Hypothesizer 7
そのようなプログラムを作成するのに、シェルスクリプト環境やOfficeマクロ環境で十分なものかどうか。そういう環境で間違いなくファイルを問題なく読み書きできますが、主要な課題は、暗号化・復号化機能をどう実装するかでしょう。

Objector 36A
...

Hypothesizer 7
他方では、ファイルがあなたのネットワークから持ち出し・持ち込みされるのを阻止しなければなりません、なぜなら、さもなければ、ファイルは、あなたのネットワークの外で編集されて、元のファイルが編集されたファイルで置き換えられるでしょうから。

Objector 36A
...

Hypothesizer 7
とにかく、編集パスワードが変更阻止としての保証された効果を持つための前提条件は、あなたのネットワークが徹底して検疫されていることです。

Objector 36A
...「保証された効果」とかあなたは言ってるけど、実際には、そんなプログラムを見つけてまで、あえてファイルを変更しようとする奴なんて、そんなにいないよ、ましてや、そんなプログラムを作るという大変な手間を取るなんて。

Hypothesizer 7
そう推測します。それが、全く無意味だとは私が断言しなかった理由です。実のところ、少なくとも、いくらかの心理的抑止効果はあります、措置が抜け穴のない保護でないとしても(実際、セキュリティ上の措置というのは、ほとんどが、本当は心理的抑止です、厳格な保護ではなく)、その一方で、抜け穴の存在をデモンストレーションする責務を一部の人々に感じさせるという心理的奨励効果も、ある程度ありますが。

Objector 36A
...

Objector 36B
私が思うには、編集パスワードというのは、ファイルが意図せずに変更されるのを防ぐためのものであって、ファイルが悪意によって変更されるのを防ぐためのものではないのよ。

Hypothesizer 7
マダム、同意します。それが、編集パスワードの適切な認識でしょう。

Hypothesizer 7
とにかく、本記事の本パートでは、OpenDocumentフォーマット群を論じます。


本体


1: 編集パスワードのハッシュを生成する


Hypothesizer 7
実のところ、Officeドキュメントファイルに編集パスワードを設定することの面倒な部分は、編集パスワードのハッシュを生成することです。 

OpenDocumentフォーマット群には、私たちは、PBKDF2(もっと詳しく言うと、PBKDF2-HMAC-SHA1)ハッシュ化アルゴリズムを使用しますが、あるプログラミング言語がそのアルゴリズムのためのパッケージ、ライブラリ、等を持っているか否かは、そのプログラミング言語次第です。

実際、私は、Java版、C++版、C#版、Python版を実装しましたが、LibreOfficeまたはApache OpenOffice Basic、BeanShell、JavaScriptについてはよく知りません。

後者のマクロプログラミング言語群のための1つの選択肢は、ハッシュ化機能を実装したUNOコンポーネントをJavaまたはC++で作成し、それをLibreOfficeまたはApache OpenOfficeへ登録する(そうする方法は、ここに記述されています)ことです。

Objector 36A
ふーむ...

Hypothesizer 7
私たちが必要とするものは、ソルトおよびハッシュで、短く言うと、それらを当該プログラミング言語においていかなる方法ででも取得できれば、それで結構です。

Objector 36A
...それはちょっと短かすぎるな。それらをJavaではどのように取得できる?

Hypothesizer 7
標準'javax.crypto'パッケージがPBKDF2-HMAC-SHA1をサポートしており、ソルトおよびハッシュを生成するクラスを私は作成しました、以下のように。

@Java ソースコード
package theBiasPlanet.coreUtilities.cryptography;

import java.security.NoSuchAlgorithmException;
~
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class Hasher {
	~
	private static final String c_pbkdf2HashingAlgorismName = "PBKDF2WithHmacSHA1";
	~
	private static SecretKeyFactory s_pbkdf2SecretKeysFactory;
	private static SecureRandom s_sha1RandomDatumGenerator;
	
	static {
		try {
			~
			s_pbkdf2SecretKeysFactory = SecretKeyFactory.getInstance (c_pbkdf2HashingAlgorismName);
			s_sha1RandomDatumGenerator = SecureRandom.getInstance (c_sha1RandomDatumGenerationAlgorismName);
		}
		catch (NoSuchAlgorithmException l_exception) {
		}
	}
	
	public static byte [] createSalt (int a_numberOfBytes) {
		byte[] l_saltArray = new byte [a_numberOfBytes];
		s_sha1RandomDatumGenerator.nextBytes (l_saltArray);
		return l_saltArray;
	}
	
	~
	
	public static byte [] hashInPbkdf2 (String a_originalDatum, byte [] a_saltArray, int a_numberOfIteration, int a_keyLength) throws InvalidKeySpecException {
		PBEKeySpec l_pbeKeySpecification = new PBEKeySpec (a_originalDatum.toCharArray (), a_saltArray, a_numberOfIteration, a_keyLength * 8);
		return s_pbkdf2SecretKeysFactory.generateSecret (l_pbeKeySpecification).getEncoded ();
	}
}

Objector 36A
ふーむ、それらはバイト群配列として得られるのか。

Hypothesizer 7
ご注意いただきたいのですが、Javaのバイトは、不可避に符号付きです、他のプログラミング言語では、私たちは、符号なしバイト群配列を取得しますが。

Objector 36A
それがどういう意味を持つんだ?

Hypothesizer 7
実は、必要となるUNOタイプは’byte’群’sequence’ですが、UNOにおける'byte'は符号付きです。そのため、一部のプログラミング言語では、取得したバイト群配列を、UNOに引き渡す前に変換しなければなりません。

Objector 36A
どうやって?

Hypothesizer 7
後で見ます。

Objector 36A
...

Hypothesizer 7
C++では、PBKDF2-HMAC-SHA1をサポートする標準ライブラリはないようで、外部ライブラリでの1つの候補はOpenSSLで、それを用いて、ソルトおよびハッシュを生成するクラスを私は作成しました、以下のように。

@C++ ソースコード
#ifndef __theBiasPlanet_coreUtilities_cryptography_Hasher_hpp__
	#define __theBiasPlanet_coreUtilities_cryptography_Hasher_hpp__
	
	#include <random>
	#include <string>
	#include "theBiasPlanet/coreUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp"
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilities {
			namespace cryptography {
				class __theBiasPlanet_coreUtilities_symbolExportingOrImportingForVisualCplusplus__ Hasher {
					private:
						static random_device s_seedGenerator;
						static mt19937 s_randomNumberGenerator;
					public:
						static bool createSalt (unsigned char * const a_saltArray, int const & a_numberOfBytes);
						static bool hashInPbkdf2 (unsigned char * const a_hashArray, string const & a_originalDatum, unsigned char const * const a_saltArray, int const & a_saltLength, int const & a_numberOfIteration, int const & a_keyLength);
				};
			}
		}
	}
#endif

#include "theBiasPlanet/coreUtilities/cryptography/Hasher.hpp"
#include <algorithm>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>

namespace theBiasPlanet {
	namespace coreUtilities {
		namespace cryptography {
			bool Hasher::createSalt (unsigned char * const a_saltArray, int const & a_numberOfBytes) {
				mt19937::result_type l_randomNumber;
				int l_randomNumberBytesLength = sizeof (mt19937::result_type);
				for (int l_numberOfFilledBytes = 0; l_numberOfFilledBytes < a_numberOfBytes; l_numberOfFilledBytes += l_randomNumberBytesLength) {
					l_randomNumber = s_randomNumberGenerator ();
					copy (static_cast <unsigned char const *> (static_cast <void const *> (&l_randomNumber)), static_cast <unsigned char const *> (static_cast <void const *> (&l_randomNumber)) + min (a_numberOfBytes - l_numberOfFilledBytes, l_randomNumberBytesLength), a_saltArray + l_numberOfFilledBytes);
				}
				return true;
			}
			
			bool Hasher::hashInPbkdf2 (unsigned char * const a_hashArray, string const & a_originalDatum, unsigned char const * const a_saltArray, int const & a_saltLength, int const & a_numberOfIteration, int const & a_keyLength) {
				return PKCS5_PBKDF2_HMAC_SHA1 (a_originalDatum.c_str (), a_originalDatum.length (), a_saltArray, a_saltLength, a_numberOfIteration, a_keyLength, a_hashArray) != 0 ? true: false;
			}
		}
	}
}

C#の標準'System.Security.Cryptography'パッケージはPBKDF2-HMAC-SHA1をサポートしており、ソルトおよびハッシュを生成するクラスを私は作成しました、以下のように。

@C# ソースコード
namespace theBiasPlanet {
	namespace coreUtilities {
		namespace cryptography {
			using System;
			using System.Security.Cryptography;
			
			public sealed class Hasher {
				private static RNGCryptoServiceProvider s_randomNumberGenerator = new RNGCryptoServiceProvider ();
				
				public static byte [] createSalt (int a_numberOfBytes) {
					byte[] l_saltArray = new byte [a_numberOfBytes];
					s_randomNumberGenerator.GetBytes (l_saltArray);
					return l_saltArray;
				}
				
				public static byte [] hashInPbkdf2 (String a_originalDatum, byte [] a_saltArray, int a_numberOfIteration, int a_keyLength) {
					Rfc2898DeriveBytes l_pbkdf2SecretKeyGenerator = new Rfc2898DeriveBytes (a_originalDatum, a_saltArray, a_numberOfIteration);
					return l_pbkdf2SecretKeyGenerator.GetBytes (a_keyLength);
				}
			}
		}
	}
}

Pythonの標準'hashlib'モジュールはPBKDF2-HMAC-SHA1をサポートしており、ソルトおよびハッシュを生成するクラスを私は作成しました、以下のように。

@Python ソースコード
import hashlib
import os
from theBiasPlanet.coreUtilities.constantsGroups.EncodingNamesConstantsGroup import EncodingNamesConstantsGroup

class Hasher:
	c_sha1HashingAlgorismName: str = "sha1"
	~
	
	@staticmethod
	def createSalt (a_numberOfBytes: int) -> bytes:
		return os.urandom (a_numberOfBytes)
	
	@staticmethod
	def hashInPbkdf2 (a_originalDatum: str, a_saltArray: bytes, a_numberOfIteration: int, a_keyLength: int) -> bytes:
		return hashlib.pbkdf2_hmac (Hasher.c_sha1HashingAlgorismName, a_originalDatum.encode (EncodingNamesConstantsGroup.c_utf8EncodingName), a_saltArray, a_numberOfIteration, a_keyLength)


2: 1つのドキュメント格納プロパティをセットする


Hypothesizer 7
セットすべきドキュメント格納プロパティは、'ModifyPasswordInfo'であり、その値は、'com.sun.star.beans.PropertyValue'群の'sequence'です。

Objector 36A
うむ?一体全体、「ドキュメント格納プロパティ」って何だ、そもそも?

Hypothesizer 7
えーと、'開始コンテキスト'に記述されているとおり、本記事は、その知識を装備なさっているという想定のもとに進行しております。

Objector 36A
...'開始コンテキスト'中のどの項目だ、具体的には?

Hypothesizer 7
最後のものです、意図したドキュメントにアクセスしてそのドキュメントをファイルに格納する方法についての。

Objector 36A
...

Hypothesizer 7
'com.sun.star.beans.PropertyValue'群は、以下のようである必要があります(データタイプ群はUNOタイプです)。

プロパティ名データタイププロパティ値
algorithm-namestringハッシュ化アルゴリズム名: "PBKDF2"
saltsequence <byte>ソルト
iteration-countlongハッシュ化イテレーション数
hashsequence <byte>ハッシュ化されたパスワード

Hypothesizer 7
Javaにおいては、ソルトバイト群配列およびハッシュバイト群配列をそのまま使用できます。

C++においては、UNO 'sequence'は、C++配列にではなく、'::com::sun::star::uno::Sequence'にマップされおり、したがって、ソルト'unsigned char'群配列およびハッシュ'unsigned char'群配列を、それぞれ'signed char'群'::com::sun::star::uno::Sequence'へ変換しなければなりません。

C#においては、ソルトバイト群配列およびハッシュバイト群配列をそのまま使用できます。

Objector 36B
待って!符号なしバイト群を符号付きバイト群へ変換しないといけないでしょう?

Hypothesizer 7
そうではありません。符号なしバイト群は自動的に符号付きバイト群へキャストされます、ビット群配列たちは変化することなく。

Objector 36B
はあ?

Hypothesizer 7
お分かりのように、符号なしと符号付きの違いは、ビット群配列の、数字としての解釈の違いにすぎません。したがって、ビット群配列たちは変化しません。

Objector 36B
ああ。

Hypothesizer 7
Pythonにおいては、ソルト'bytes'配列およびハッシュ'bytes'配列を、符号付き値の整数群のリストに変換しなければなりません。

Objector 36B
「符号付き値の整数群のリスト」?

Hypothesizer 7
はい。UNO 'sequence'はPython 'List'にマップされ、Pythonには'byte'タイプがない(奇妙なことに)ので、代わりに、Python 'int'を使用します。

Objector 36B
じゃあ、Python 'bytes'はUNOでは'byte'群'sequence'と認識されないのね...

Hypothesizer 7
そのとおりです。

ご注意いただきたいのですが、この場合は、各符号なしバイト値を符号付き整数値に適切に変換しなければなりません、例えば、'255'から'-1'へ。

Objector 36B
はあ?それがなんで必要なわけ、C#では必要なかったのに?

Hypothesizer 7
なぜなら、Pythonにおいては、'int'は、単純な32ビット群配列ではなく、クラスであり、単純に符号付きバイトにキャストするというわけにはいかないからです。そのため、'int'データは数字として解釈されます。

Objector 36B
例えば、'255'として?

Hypothesizer 7
はい。しかし、'255'は'signed byte'のデータ範囲外なので、エラーが起こることになります。

Objector 36B
分かった。

Hypothesizer 7
とにかく、Javaの場合を除いて、私たちは、各プロパティ値をUNO 'Any'データ(C++における'::com::sun::star::uno::Any'、C#における'uno.Any'、Pythonにおける'uno.Any')でラップします。

Objector 36B
なんでJavaの場合を除くわけ?

Hypothesizer 7
それが必要か否かはUNOブリッジ(それはプログラミング言語毎に実装されています)に依存します。例えば、Pythonの場合に明らかに必要だというのは、それが無かったら、整数のリストが、UNOにおいて、'byte'群'sequence'と意図されているのか、'long'群'sequence'と意図されているのか、その他なのか、知りようがないからです。

Objector 36B
ああ。


3: サンプルプログラムを実行する


Hypothesizer 7
実は、編集パスワードセット機能は、以前の記事(コンセプトあるJava実装あるC++実装あるC#実装nPython実装)で紹介されたコンバージョンサンプルプログラム群に実装済みです。

当該プロジェクトを入手・ビルドする方法およびプログラム群を実行する方法は、上記以前の記事に記述されています、編集パスワードをセットするための、ファイルコンバージョン命令CSVファイル仕様を除いては。

実のところ、以下がその仕様です。

コンバート元ファイルURL
コンバート先ファイルURL
ドキュメント格納フィルター名
コンバート元ファイルオープンパスワード(オプション)
ドキュメントテーラー名(オプション)
コンバート先ファイルタイトル(オプション)
コンバート先ファイルオープンパスワード(オプション)
コンバート先ファイル編集パスワード(オプション)

以下が、編集パスワードを指定可能なドキュメント格納フィルターの名前群(非OpenDocumentフォーマット群を含む)です。

フィルター名ファイルフォーマット
writer8OpenDocument Text
calc8OpenDocument Spreadsheet
impress8OpenDocument Presentation
MS Word 2007 XMLOffice Open XML Document
Calc MS Excel 2007 XMLOffice Open XML Workbook
Impress MS PowerPoint 2007 XMLOffice Open XML Presentation
MS Word 97MicrosoftバイナリーWord
MS Excel 97MicrosoftバイナリーExcel


4: 結びとその先


Hypothesizer 7
これで、OpenDocumentファイルに編集パスワードを私たちのプログラムからセットする方法を知りました。

編集パスワードがいかなる目的のためにどれだけ効果的であるかを意識する必要がありますが、実際的に妥当なユースケースはいくらかあるでしょう。

Microsoftバイナリフォーマットファイルに編集パスワードを私たちのプログラムからセットする方法を、本記事の次のパートで見ましょう。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>