2020-02-02

36: Setting Editing Password into an OpenDocument File with UNO

<The previous article in this series | The table of contents of this series | The next article in this series>

Using LibreOffice or OpenOffice, for an OpenDocument ('.odt', '.ods', or '.odp') file, from Java, C++, C#, Python, BeanShell, JavaScript, or Basic

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: the Java programming language
About: C++
About: Microsoft .NET Framework
About: the Python programming language
About: LibreOffice Basic
About: Apache OpenOffice Basic
About: BeanShell
About: JavaScript

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to set any editing password into an OpenDocument ('.odt', '.ods', or '.odp') file from his or her program.
Stage Direction
Here are Hypothesizer 7, Objector 36A, and Objector 36B in front of a computer.


Orientation


Hypothesizer 7
In this article, we will know how to set any editing password into an Office (OpenDocument format ('.odt', '.ods', or '.odp'), Microsoft Office binary format ('.doc', '.xls'), or Office Open XML format ('.docx', '.xlsx', '.pptx')) document file from our program.

Objector 36A
Really? The LibreOffice GUI cannot set any editing password into any Office Open XML format file, although it shows the option.

Hypothesizer 7
Sir, the GUI cannot, but by directly using the UNO API, you can.

Objector 36A
Hmm . . .

Hypothesizer 7
Before we implement the feature, we want to examine how much meaningful the act of setting the editing password is for which purpose.

Objector 36A
"we want"? I don't particularly want.

Hypothesizer 7
Sir, I want actually, and at least I will examine it.

Objector 36A
What do you mean by "how much meaningful"? It is very meaningful because anyone who doesn't know the password cannot modify the document!

Hypothesizer 7
Sir, let us be more specific. If a program just ignores the editing password, anyone can modify the document with the program.

Objector 36A
Huh? What do you mean by "a program just ignores"? What "program"? "ignores"?

Hypothesizer 7
As the document file is of a disclosed format, anyone who has the skill can create a program that reads and writes the file.

Objector 36A
So?

Hypothesizer 7
As the file has been already decrypted (otherwise, it will not be any situation in which the editing password works), the program knows the contents of the document and can let the user edit the contents and store the modified contents into the file, just ignoring the existence of the editing password.

Objector 36A
. . . I can just prohibit such a program from being installed into my network.

Hypothesizer 7
You will have to systematically prevent such a program from being installed into your network, not just prohibit as a rule, usually.

Objector 36A
. . . Of course.

Hypothesizer 7
You will also have to prevent such a program from being created inside your network, of course.

Objector 36A
Well, I have no intention of allowing any development environment inside my netwwork.

Hypothesizer 7
You will have to be sure that no GCC, Python, or whatever exists inside your network.

Objector 36A
No Python? . . . Hmm . . .

Hypothesizer 7
I wonder whether a shell script environment or an Office macro environment is enough for creating such a program; such an environment can certainly read and write the file all right, but a major issue will be how to implement cryptography.

Objector 36A
. . .

Hypothesizer 7
On the other hand, the file has to be prevented from being brought out from and brought into your network, because otherwise, the file will be modified outside your network and the original file will be replaced with the modified file.

Objector 36A
. . .

Hypothesizer 7
Anyway, a precondition for the editing password's guaranteed effectiveness as the prevention of modification is that your network is thoroughly quarantined.

Objector 36A
. . . You say of "guaranteed effectiveness", but actually, not many people will dare to modify the file via finding such a program, or, even less likely, taking great trouble of creating such a program.

Hypothesizer 7
I guess so. That is the reason why I did not declare that it was absolutely meaningless. In fact, it has at least some effect of psychological deterrence even if the measure is not any loophole-less protection (in fact, most security measures are really psychological deterrents instead of strict protections), although there is also certain effect of psychological incitement that makes some people feel obliged to demonstrate the existence of loopholes.

Objector 36A
. . .

Objector 36B
I think, the editing password is in order for the file not to be unintentionally modified, not in order for the file not to be maliciously modified.

Hypothesizer 7
Madam, I agree. That will be the appropriate recognition of editing password.

Hypothesizer 7
Anyway, we will discuss the OpenDocument formats in this part of the article.


Main Body


1: Creating a Hash of the Editing Password


Hypothesizer 7
In fact, the tedious part of setting the editing password into an Office document file is to create a hash of the editing password.

For any of the OpenDocument formats, we use the PBKDF2 (more specifically, PBKDF2-HMAC-SHA1) hashing algorithm, and whether a programming language has a package, a library, or something for the algorithm depends on the programming language.

In fact, I have implemented a Java version, a C++ version, a C# version, and a Python version, but I am not sure about LibreOffice or Apache OpenOffice Basic, BeanShell, or JavaScript.

An option for the latter macro programming languages is to create a UNO component that implements the hashing functionality in Java or C++ and register it into LibreOffice or Apache OpenOffice ( how to do so is described here).

Objector 36A
Hmm . . .

Hypothesizer 7
What we need are the salt and the hash, and in short, it is fine if you get them by whatever way possible in the programming language.

Objector 36A
. . . It is a bit too short. How can I get them in Java?

Hypothesizer 7
The standard 'javax.crypto' package supports PBKDF2-HMAC-SHA1, and I have created a class that creates the salt and the hash, like this.

@Java Source Code
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
Hmm, they are gotten as bytes arrays.

Hypothesizer 7
Note that Java byte is inevitably signed while in the other programming languages, we get unsigned bytes arrays.

Objector 36A
Does that signify what?

Hypothesizer 7
In fact, the required UNO type is 'byte's 'sequence' where 'byte' in UNO is signed. Accoridingly, in some programming languages, we have to transform the gotten bytes arrays before we pass them to UNO.

Objector 36A
How?

Hypothesizer 7
We will see later.

Objector 36A
. . .

Hypothesizer 7
As for C++, there seems to be no standard library that supports PBKDF2-HMAC-SHA1, and a candidate as an external library is OpenSSL, with which I have created a class that creates the salt and the hash, like this.

@C++ Source Code
#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;
			}
		}
	}
}

The C# standard 'System.Security.Cryptography' package supports PBKDF2-HMAC-SHA1, and I have created a class that creates the salt and the hash, like this.

@C# Source Code
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);
				}
			}
		}
	}
}

The Python standard 'hashlib' module supports PBKDF2-HMAC-SHA1, and I have created a class that creates the salt and the hash, like this.

@Python Source Code
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: Setting a Document-Storing Property


Hypothesizer 7
The document-storing property to be set is 'ModifyPasswordInfo', whose value is a 'sequence' of 'com.sun.star.beans.PropertyValue's.

Objector 36A
Um? What, the hell' is "document-storing property", in the first place?

Hypothesizer 7
Well, as is described in 'Starting Context', this article is proceeding on the assumption that you are equipped with the knowledge.

Objector 36A
. . . Which item in 'Starting Context', specifically?

Hypothesizer 7
The last one, which is about how to access the intended document and store the document into a file.

Objector 36A
. . .

Hypothesizer 7
The 'com.sun.star.beans.PropertyValue's have to be these (the datum types are UNO types).

property namedatum typeproperty value
algorithm-namestringthe hashing algorithm name: "PBKDF2"
saltsequence <byte>the salt
iteration-countlongthe number of hashing iterations
hashsequence <byte>the hashed password

Hypothesizer 7
In Java, we can use the salt bytes array and the hash bytes array as they are.

In C++, UNO 'sequence' is not mapped to C++ array, but to '::com::sun::star::uno::Sequence', so, we have to transform the salt 'unsigned char's array and the hash 'unsigned char's array to '::com::sun::star::uno::Sequence's of 'signed char's.

In C#, we can use the salt bytes array and the hash bytes array as they are.

Objector 36B
Wait! The unsigned bytes have to be transformed to signed bytes, right?

Hypothesizer 7
Not right, actually. The unsigned bytes will be automatically cast into signed bytes, with the bits arrays unchanged.

Objector 36B
Huh?

Hypothesizer 7
You know, the difference between unsigned and signed is just the interpretations of the bits array as numbers. So, the bits arrays are unchanged.

Objector 36B
Ah.

Hypothesizer 7
In Python, we have to transform the salt 'bytes' array and the hash 'bytes' array into lists of signed value integers.

Objector 36B
"lists of signed value integers"?

Hypothesizer 7
Yes. UNO 'sequence' is mapped to Python 'List', and as Python has no 'byte' type (oddly), we use Python 'int' instead.

Objector 36B
So, Python 'bytes' is not recognized as 'byte's 'sequence' in UNO . . .

Hypothesizer 7
Exactly.

Note that we have to transform each unsigned byte value into a signed integer value properly in this case, for example, from '255' to '-1'.

Objector 36B
Huh? Why is that necessary while that wasn't for C#?

Hypothesizer 7
Because in Python, 'int' is not a simple 32-bits array, but a class, and cannot be simply cast into a signed byte; so, any 'int' datum will be interpreted as a number.

Objector 36B
For example as '255'?

Hypothesizer 7
Yes. But as '255' is out of the 'signed byte' data range, an error would occur.

Objector 36B
I see .

Hypothesizer 7
Anyway, except in Java, we wrap each property value in a UNO 'Any' datum ('::com::sun::star::uno::Any' in C++, 'uno.Any' in C#, and 'uno.Any' in Python).

Objector 36B
Why except in Java?

Hypothesizer 7
Whether it is necessary or not depends on the UNO bridge (which is implemented per programming language). For example, it is obviously necessary for Python because without it, the list of integers cannot be known whether it is meant as a 'byte's 'sequence', a 'long's 'sequence', or whatever in UNO.

Objector 36B
Ah.


3: Executing Some Sample Programs


Hypothesizer 7
In fact, the editing-password-setting feature has been implementated into the file conversion sample programs introduced in a previous artcile (the concept, a Java implementation, a C++ implementation, a C# implementation, and a Python implementation).

How to get and build the project and how to execute the programs are described in the previous article, except the specifications of the file conversions order CSV file for setting editing passwords.

In fact, this is the specifications.

The converted file URL
The converted-to file URL
The document-storing filter name
The converted file opening password (optional)
The document tailor name (optional)
The converted-to file title (optional)
The converted-to file opening password (optional)
The converted-to file editing password (optional)

These are the names of the document-storing filters for which editing passwords can be specified (including non-OpenDocument formats).

filter namefile format
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 binary Word
MS Excel 97Microsoft binary Excel


4: The Conclusion and Beyond


Hypothesizer 7
Now, we know how to set any editing password into an OpenDocument file from our program.

Although we should be aware of how effective editing password is for what purpose, there will be some practically valid use cases.

We will see how to set any editing password into a Microsoft binary format file from our program, in the next part of this article.


References


<The previous article in this series | The table of contents of this series | The next article in this series>