2020年9月27日日曜日

42: 編集パスワードをOffice Open XMLファイルにUNOを用いてセットする

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

LibreOfficeまたはOpenOfficeを用いて、Office Open XML .docx、.xlsx、.pptxファイルへ、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

この記事の目次


開始コンテキスト



ターゲットコンテキスト



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


オリエンテーション


Hypothesizer 7
本記事の第1パートおよび第2パートでは、任意の編集パスワードを、OpenDocumentフォーマットファイルおよびMicrosoft OfficeバイナリWordまたはExcelフォーマットファイルへ、自分のプログラムから設定する方法を知りました。

本記事の本パートでは、Office Open XMLフォーマット('.docx', '.xlsx', '.pptx')ファイルに任意の編集パスワードを私たちのプログラムからセットする方法を知ります。

Objector 42A
LibreOffice GUIは、編集パスワードをセットさせてくれるけど、何の効果も見られない...

Hypothesizer 7
はい、サー、LibreOffice GUIは、Office Open XMLフォーマットファイルに編集パスワードを全くセットしません。

Objector 42A
それはバグなのか?

Hypothesizer 7
えーと、バグというより放棄です。

Objector 42A
どういう意味だ?

Hypothesizer 7
LibreOffice GUIは、編集パスワードをセットしようと努力さえしていません。

Objector 42A
「努力さえしていません」?

Hypothesizer 7
その通りです。

Objector 42A
じゃあ、なんで、GUIはそのオプションを示すのだ?

Hypothesizer 7
私は知りません。全く実装されていないそのオプションを無効化する手間をとるつもりもないようです。

Objector 42A
...少なくとも、その手間ぐらいはとるべきだと思うが。

Hypothesizer 7
同感です。

Objector 42A
それでは、GUIがその機能を実装していないにもかかわらず、編集パスワードを設定する方法をこの記事は説明する、と君は言っているのだな?

Hypothesizer 7
はい、そうです。

Objector 42A
...オーケー。

Hypothesizer 7
いずれにせよ、その機能を実装し始める前に、その機能の意味するところを考慮なさっておきたいかもしれません。

Objector 42B
どういう意味?

Hypothesizer 7
マダム、本記事の第1パートの'オリエンテーション'で論じられたように、編集パスワードでプロテクトされたいかなるドキュメントも、そのプロテクションを尊重しない任意のプログラムで編集できてしまいます。

Objector 42B
...それって、「プロテクション」と呼ぶべきかしら?

Hypothesizer 7
むしろ、'ほのめかし'とお呼びになりたいかもしれません。

Objector 42B
または、'訴え'か。

Hypothesizer 7
実際、LibreOfficeはその保護を尊重しないので、編集パスワード付きのファイルは、パスワードを知らなくても、LibreOfficeによって編集できてしまいます。


本体


1: メカニズム


Hypothesizer 7
Office Open XMLフォーマット('.docx'、'.xlsx'、'.pptx')ファイルに編集パスワードをセットするためのメカニズムは、OpenDocumentフォーマットファイルやMicrosoft OfficeバイナリWordまたはExcelフォーマットファイルにそうする方法とは根本的に異なります。

OpenDocumentフォーマットファイルやMicrosoft OfficeバイナリWordまたはExcelフォーマットファイルの場合には、あるドキュメント格納プロパティをセットすることになりますが、Office Open XMLフォーマットファイルの場合には、あるドキュメントプロパティをセットすることになります。

Objector 42B
何が違うわけ?

Hypothesizer 7
ドキュメント格納プロパティというのは、ドキュメントをファイルに格納するためのパラメータであり、ドキュメントプロパティというのは、オープンされているドキュメントへセットされるものです。

Objector 42B
それじゃあ、今回は、ドキュメントを格納するためのパラメータをセットするわけじゃないのね。


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


Hypothesizer 7
編集パスワードをオフィスドキュメントファイルへセットすることの面倒な部分は、その編集パスワードのハッシュを生成することです。

Objector 42B
アルゴリズムは何なの?

Hypothesizer 7
実は、アルゴリズムは、スタンダードなアルゴリズムではなく、あるオリジナルのアルゴリズムです。

Objector 42B
ふーん。

Hypothesizer 7
ハッシュを生成する前に、ソルトを生成します。

Objector 42B
どうやって?

Hypothesizer 7
お好きなように、ただ、多分、ソルトをランダムになさりたいかと思います。私の場合、本記事の第1パートでご紹介したファンクションを使用します。

Objector 42B
ふーん。

Hypothesizer 7
実際には、ハッシュ化アルゴリズムは、2つのフェーズから成り、第1フェーズは、本記事の第2パートで紹介された、32ビットへのハッシュ化アルゴリズムそのものです。 .

Objector 42B
'.doc'ファイル用のあのアルゴリズムのこと?

Hypothesizer 7
はい。

以下が、第2フェーズの私のJavaファンクションです。

theBiasPlanet.coreUtilities.cryptography.Hasher

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

import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
~

public class Hasher {
	private static final String c_sha1HashingAlgorismName = "SHA-1";
	~
	private static MessageDigest s_sha1MessageDigester;
	~
	
	static {
		try {
			s_sha1MessageDigester = MessageDigest.getInstance (c_sha1HashingAlgorismName);
			~
		}
		catch (NoSuchAlgorithmException l_exception) {
		}
	}
	
	~
	
	public static byte [] hashInSha1 (byte [] a_originalDatumBytesArray, byte [] a_saltBytesArray) {
		s_sha1MessageDigester.update (a_saltBytesArray);
		return s_sha1MessageDigester.digest (a_originalDatumBytesArray);
	}
	
	~
}

theBiasPlanet.unoUtilities.cryptography.MicrosoftPasswordsHasher

@Java ソースコード
package theBiasPlanet.unoUtilities.cryptography;
import java.io.UnsupportedEncodingException;
~
import theBiasPlanet.coreUtilities.cryptography.Hasher;

public class MicrosoftPasswordsHasher {
~

	public static byte [] hashHashWithSalt (int a_32bitsHash, byte [] a_saltArray, int a_numberOfIteration) {
		int l_hashBytesLength = Integer.SIZE / Byte.SIZE;
		byte [] l_hash = null;
		byte l_byte = 0x00;
		StringBuilder l_bytesReversedHexadecimalStringBuilder = new StringBuilder ();
		byte [] l_bytesReversedHexadecimalStringUtf16BytesArray = null;
		for (int l_byteIndex = 0; l_byteIndex < l_hashBytesLength; l_byteIndex ++) {
			l_byte = (byte) ((a_32bitsHash >>> (Byte.SIZE * l_byteIndex)) & 0x000000FF);
			l_bytesReversedHexadecimalStringBuilder.append (String.format ("%02X", (l_byte >= 0) ? l_byte: l_byte + 256));
		}
		try {
			l_bytesReversedHexadecimalStringUtf16BytesArray = l_bytesReversedHexadecimalStringBuilder.toString ().getBytes ("UTF-16LE");
		}
		catch (UnsupportedEncodingException l_exception) {
			// Supposed to not happen.
		}
		int l_saltLength = a_saltArray.length;
		int l_bytesReversedHexadecimalStringUtf16BytesArrayLength = l_bytesReversedHexadecimalStringUtf16BytesArray.length;
		l_hash = Hasher.hashInSha1 (l_bytesReversedHexadecimalStringUtf16BytesArray, a_saltArray);
		byte [] l_iterationSaltBytesArray = new byte [l_hashBytesLength];
		for (int l_iterationIndex = 0; l_iterationIndex < a_numberOfIteration; l_iterationIndex ++) {
			l_iterationSaltBytesArray [0] = (byte) ((l_iterationIndex >>> (Byte.SIZE * 0)) & 0x000000FF);
			l_iterationSaltBytesArray [0 + 1] = (byte) ((l_iterationIndex >>> (Byte.SIZE * 1)) & 0x000000FF);
			l_iterationSaltBytesArray [0 + 2] = (byte) ((l_iterationIndex >>> (Byte.SIZE * 2)) & 0x000000FF);
			l_iterationSaltBytesArray [0 + 3] = (byte) ((l_iterationIndex >>> (Byte.SIZE * 3)) & 0x000000FF);
			l_hash = Hasher.hashInSha1 (l_iterationSaltBytesArray, l_hash);
		}
		return l_hash;
	}

Objector 42B
...その第1引数に、第1フェーズのアウトプットが渡されるってこと?

Hypothesizer 7
はい。

Objector 42B
それじゃあ、C#バージョンを見せてちょうだい。

Hypothesizer 7
実は、Javaバージョンしか用意していません、今のところ。

Objector 42B
何ですって?

Hypothesizer 7
将来的には、C++バージョン、C#バージョン、Pythonバージョンもお示しするように意図していますが、当座は、上記Javaバージョンからあなたのバージョンを導き出せるでしょう。

Objector 42B
...


3: あるドキュメントプロパティを設定する


Hypothesizer 7
セットするべきドキュメントプロパティは、'InteropGrabBag'であり、その値は、'::com::sun::star::beans::PropertyValue'のシーケンスであり、その内の1つは、'DocumentProtection'プロパティであり、その値は、'::com::sun::star::beans::PropertyValue'のシーケンスであり、それらは、以下のとおりです(タイプ群はUNOデータタイプです)。

名前タイプ
editstringプロテクトされるもの: 'none' -> 何も、'readOnly' -> 全体、'comments' -> コメント群、'trackedChanges' -> 追跡された変更群、'forms' -> フォーム群
enforcementstringプロテクションが強制される: 'true'、'false'
formattingstringフォーマットがプロテクトされる: 'true'、'false'
cryptProviderTypestring暗号化プロバイダタイプ: 'invalid'、'rsaAES'、'rsaFull'
cryptAlgorithmClassstring暗号化アルゴリズムクラス: 'hash'
cryptAlgorithmTypestring暗号化アルゴリズムタイプ: 'typeAny'
cryptAlgorithmSidstring暗号化アルゴリズムID: '1' -> MD2、'2' -> MD4、'3' -> MD5、'4' -> SHA1
cryptSpinCountstringアルゴリズムスピン回数
hashstringハッシュ化されたパスワードのBase64表現
saltstringハッシュ化ソルトのBase64表現

Objector 42A
「Base64表現」?

Hypothesizer 7
はい。例えば、以下が、指定されたバイト配列のBase64文字列を取得する、私のファンクションです。

theBiasPlanet.coreUtilities.stringsHandling.StringHandler

@ ソースコード
package theBiasPlanet.coreUtilities.stringsHandling;

~
import java.util.Base64;
~

public class StringHandler {
	~
	static private final Base64.Encoder c_base64Encoder;
	
	static {
		~
		c_base64Encoder = Base64.getEncoder ();
	}
	
	~
	
	public static String getBase64String (byte [] a_bytesArray) {
		return c_base64Encoder.encodeToString (a_bytesArray);
	}
	
	~
}

Objector 42A
「invalid」っていつ使うべきなの?

Hypothesizer 7
私は知りません。実のところ、私は、16バイトソルトで、'readOnly'、'true'、'true'、'rsaFull'、'hash'、'typeAny'、'4'、'50000'という単一のコンビネーションしか試したことがありません。別のコンビネーションは、ご自分のご裁量でお試しください。


4: 結びとその先


Hypothesizer 7
これで、任意の編集パスワードを、オフィスOpen XML(.docx、.xlsx、.pptx)ファイルへ、自分のプログラムからセットする方法を知りました。

編集パスワードがいかなる目的のためにいかに効果的かを意識しておくべきですが、実際的に妥当なユースケースも一部にあるでしょう。

任意のOpenDocumentフォーマットファイルをGPGによって暗号化する方法を、以降の記事にて見ます。


参考資料


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