2020-02-09

37: Editing Password into a Binary Word/Excel 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 a Microsoft '.doc' or .'xls' 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 a Microsoft binary Word or Excel ('.doc' or '.xls') file from his or her program.
Stage Direction
Here are Hypothesizer 7, Objector 37A, and Objector 37B in front of a computer.


Orientation


Hypothesizer 7
In the 1st part of this article, we have discussed how to set any editing password into an OpenDocument format file from our program.

In this part of this article, we will know how to set any editing password into a Microsoft Office binary Word or Excel format ('.doc' or '.xls') file from our program.

Objector 37A
Actually, the LibreOffice GUI does not properly set the editing password into any '.doc' file. I mean, Microsoft Word program refuses to let me edit the file with the password I have specified.

Hypothesizer 7
Sir, that is a bug of LibreOffice.

Objector 37A
A bug?

Hypothesizer 7
Yes. The logic for creating the hash of the password is flawed.

Objector 37A
Aren't many people complaining about the bug? That bug seems to have been left unrectified for a long time . . .

Hypothesizer 7
Probably, not many want the feature anyway.

Objector 37A
Why?

Hypothesizer 7
Probably because the editing password is not much efective in preventing the file from being maliciously modified, as we have discussed in 'Orientation' of the 1st part of this article, although I think that there is certain usefullness as a measure for preventing the file from being unintentionally modified. Or maybe just the older format is not being used much any more.

Objector 37B
In fact, I want to use the feature in order to prevent myself from mistakenly editing some files that are supposed to be fixed, in my private environment where nobody else is ever imagined to touch the files.

Hypothesizer 7
Madam, I think that that is a valid usage.

Objector 37A
Anyway, can't I set the password properly either by the way you will introduce in this article?

Hypothesizer 7
In fact, you can, because your program will correctly create the hash.

Objector 37A
Oh, so, my program will create the hash . . .

Hypothesizer 7
Yes. The hashing logics are the main theme of this article.


Main Body


1: Creating a Hash of the Editing Password


Hypothesizer 7
The tedious part of setting the editing password into an Office document file is to create the hash of the editing password.

In fact, the hashing algorithm is different for each Microsoft binary format.

This is my Java function that creates the hash for any '.doc' file.

@Java Source Code
package theBiasPlanet.unoUtilities.cryptography;

public class MicrosoftPasswordsHasher {
	private static final int c_numberOfInitializationCodes = 15;
	private static final short [] c_initializationCodes = new short [] {(short) 0xE1F0, (short) 0x1D0F, (short) 0xCC9C, (short) 0x84C0, (short) 0x110C, (short) 0x0E10, (short) 0xF1CE, (short) 0x313E, (short) 0x1872, (short) 0xE139, (short) 0xD40F, (short) 0x84F9, (short) 0x280C, (short) 0xA96A, (short) 0x4EC3};
	private static final int c_numberOfEncryptionMatrixColumns = 7;
	private static final short [] [] c_encryptionMatrix = new short [] [] {
		{ (short) 0xAEFC, (short) 0x4DD9, (short) 0x9BB2, (short) 0x2745, (short) 0x4E8A, (short) 0x9D14, (short) 0x2A09},
		{ (short) 0x7B61, (short) 0xF6C2, (short) 0xFDA5, (short) 0xEB6B, (short) 0xC6F7, (short) 0x9DCF, (short) 0x2BBF},
		{ (short) 0x4563, (short) 0x8AC6, (short) 0x05AD, (short) 0x0B5A, (short) 0x16B4, (short) 0x2D68, (short) 0x5AD0},
		{ (short) 0x0375, (short) 0x06EA, (short) 0x0DD4, (short) 0x1BA8, (short) 0x3750, (short) 0x6EA0, (short) 0xDD40},
		{ (short) 0xD849, (short) 0xA0B3, (short) 0x5147, (short) 0xA28E, (short) 0x553D, (short) 0xAA7A, (short) 0x44D5},
		{ (short) 0x6F45, (short) 0xDE8A, (short) 0xAD35, (short) 0x4A4B, (short) 0x9496, (short) 0x390D, (short) 0x721A},
		{ (short) 0xEB23, (short) 0xC667, (short) 0x9CEF, (short) 0x29FF, (short) 0x53FE, (short) 0xA7FC, (short) 0x5FD9},
		{ (short) 0x47D3, (short) 0x8FA6, (short) 0x8FA6, (short) 0x1EDA, (short) 0x3DB4, (short) 0x7B68, (short) 0xF6D0},
		{ (short) 0xB861, (short) 0x60E3, (short) 0xC1C6, (short) 0x93AD, (short) 0x377B, (short) 0x6EF6, (short) 0xDDEC},
		{ (short) 0x45A0, (short) 0x8B40, (short) 0x06A1, (short) 0x0D42, (short) 0x1A84, (short) 0x3508, (short) 0x6A10},
		{ (short) 0xAA51, (short) 0x4483, (short) 0x8906, (short) 0x022D, (short) 0x045A, (short) 0x08B4, (short) 0x1168},
		{ (short) 0x76B4, (short) 0xED68, (short) 0xCAF1, (short) 0x85C3, (short) 0x1BA7, (short) 0x374E, (short) 0x6E9C},
		{ (short) 0x3730, (short) 0x6E60, (short) 0xDCC0, (short) 0xA9A1, (short) 0x4363, (short) 0x86C6, (short) 0x1DAD},
		{ (short) 0x3331, (short) 0x6662, (short) 0xCCC4, (short) 0x89A9, (short) 0x0373, (short) 0x06E6, (short) 0x0DCC},
		{ (short) 0x1021, (short) 0x2042, (short) 0x4084, (short) 0x8108, (short) 0x1231, (short) 0x2462, (short) 0x48C4}
	};
	
	public static int hashIn32bits (String a_originalDatum) {
		int l_hash = 0;
		int l_originalDatumLength = a_originalDatum.length ();
		if (l_originalDatumLength > 0) {
			if (l_originalDatumLength > c_numberOfInitializationCodes) {
		   		l_originalDatumLength = c_numberOfInitializationCodes;
			}
			int l_highHash = c_initializationCodes [l_originalDatumLength - 1];
			int l_lowHash = 0;
			char l_character = 0x0000;
			byte l_byte = 0x00;
			byte l_highByte = 0x00;
			byte l_lowByte = 0x00;
			for (int l_characterIndex = 0; l_characterIndex < l_originalDatumLength; l_characterIndex ++) {
				l_character = a_originalDatum.charAt (l_characterIndex);
				l_highByte = (byte) (l_character >>> 8);
				l_lowByte = (byte) (l_character & 0xFF);
				l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
				for (int l_matrixcolumnIndex = 0; l_matrixcolumnIndex < c_numberOfEncryptionMatrixColumns; l_matrixcolumnIndex ++) {
					if ((l_byte & (1 << l_matrixcolumnIndex)) != 0) {
						l_highHash = (short) (l_highHash ^ c_encryptionMatrix [c_numberOfInitializationCodes - l_originalDatumLength + l_characterIndex] [l_matrixcolumnIndex]);
					}
				}
				l_character = a_originalDatum.charAt (l_originalDatumLength -1 - l_characterIndex);
				l_highByte = (byte) (l_character >>> 8);
				l_lowByte = (byte) (l_character & 0xFF);
				l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
				l_lowHash = ( ( (l_lowHash >>> 14) & 0x0001 ) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_byte;
			}
			l_lowHash =  ( ( (l_lowHash >>> 14) & 0x0001) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_originalDatumLength ^ 0xCE4B;
			l_hash = (l_highHash << 16) | l_lowHash;
		}
		return l_hash;
	}
	
	~
}

Objector 37B
. . . Is the hash an integer?

Hypothesizer 7
It is a 32-bits array that will be ultimately turned into a UNO 'long' datum.

Objector 37B
Hmm.

Hypothesizer 7
Note that 'l_highHash' and 'l_lowhHash' are really 16-bits arrays, but as Java bits left shift operation automatically (gratuitously, in my opinion) changes any 'short' datum into an 'int' datum, I use 'int' variables, not 'short' variables, for them.

Objector 37B
Huh?

Hypothesizer 7
For example, we want
'0b1111111111111111' ->
'0b1111111111111110' ->
'0b0111111111111111'
by '(0b1111111111111111 << 1) >>> 1', but Java does
'0b1111111111111111' ->
'0b11111111111111111111111111111111' ->
'0b11111111111111111111111111111110' ->
'0b01111111111111111111111111111111', which is a problem.

Objector 37B
. . . You mean, it's OK if the last 16 bits are correct, but they are not?

Hypothesizer 7
Exactly. So, we have it in 'int' from the beginning, making it
'0b00000000000000001111111111111111' ->
'0b00000000000000011111111111111110' ->
'0b00000000000000001111111111111111'.

Objector 37B
Ah, the last 16 bits are as we hope them to be.

Hypothesizer 7
These are my C++, C#, and Python functions that create the hash for any '.doc' file.

@C++ Source Code
#ifndef __theBiasPlanet_unoUtilities_cryptography_MicrosoftPasswordsHasher_hpp__
	#define __theBiasPlanet_unoUtilities_cryptography_MicrosoftPasswordsHasher_hpp__
	
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace unoUtilities {
			namespace cryptography {
				class __theBiasPlanet_unoUtilities_symbolExportingOrImportingForVisualCplusplus__ MicrosoftPasswordsHasher {
					private:
						static int const c_numberOfInitializationCodes;
						static unsigned short const c_initializationCodes [];
						static int const c_numberOfEncryptionMatrixColumns = 7;
						static unsigned short const c_encryptionMatrix [] [c_numberOfEncryptionMatrixColumns];
					public:
						static long hashIn32bits (string const & a_originalDatum);
						~
				};
			}
		}
	}
#endif

#include "theBiasPlanet/unoUtilities/cryptography/MicrosoftPasswordsHasher.hpp"

namespace theBiasPlanet {
	namespace unoUtilities {
		namespace cryptography {
			int const MicrosoftPasswordsHasher::c_numberOfInitializationCodes = 15;
			unsigned short const MicrosoftPasswordsHasher::c_initializationCodes [] = {(unsigned short) 0xE1F0, (unsigned short) 0x1D0F, (unsigned short) 0xCC9C, (unsigned short) 0x84C0, (unsigned short) 0x110C, (unsigned short) 0x0E10, (unsigned short) 0xF1CE, (unsigned short) 0x313E, (unsigned short) 0x1872, (unsigned short) 0xE139, (unsigned short) 0xD40F, (unsigned short) 0x84F9, (unsigned short) 0x280C, (unsigned short) 0xA96A, (unsigned short) 0x4EC3};
			int const MicrosoftPasswordsHasher::c_numberOfEncryptionMatrixColumns;
			unsigned short const MicrosoftPasswordsHasher::c_encryptionMatrix [] [MicrosoftPasswordsHasher::c_numberOfEncryptionMatrixColumns] = {
				{ (unsigned short) 0xAEFC, (unsigned short) 0x4DD9, (unsigned short) 0x9BB2, (unsigned short) 0x2745, (unsigned short) 0x4E8A, (unsigned short) 0x9D14, (unsigned short) 0x2A09},
				{ (unsigned short) 0x7B61, (unsigned short) 0xF6C2, (unsigned short) 0xFDA5, (unsigned short) 0xEB6B, (unsigned short) 0xC6F7, (unsigned short) 0x9DCF, (unsigned short) 0x2BBF},
				{ (unsigned short) 0x4563, (unsigned short) 0x8AC6, (unsigned short) 0x05AD, (unsigned short) 0x0B5A, (unsigned short) 0x16B4, (unsigned short) 0x2D68, (unsigned short) 0x5AD0},
				{ (unsigned short) 0x0375, (unsigned short) 0x06EA, (unsigned short) 0x0DD4, (unsigned short) 0x1BA8, (unsigned short) 0x3750, (unsigned short) 0x6EA0, (unsigned short) 0xDD40},
				{ (unsigned short) 0xD849, (unsigned short) 0xA0B3, (unsigned short) 0x5147, (unsigned short) 0xA28E, (unsigned short) 0x553D, (unsigned short) 0xAA7A, (unsigned short) 0x44D5},
				{ (unsigned short) 0x6F45, (unsigned short) 0xDE8A, (unsigned short) 0xAD35, (unsigned short) 0x4A4B, (unsigned short) 0x9496, (unsigned short) 0x390D, (unsigned short) 0x721A},
				{ (unsigned short) 0xEB23, (unsigned short) 0xC667, (unsigned short) 0x9CEF, (unsigned short) 0x29FF, (unsigned short) 0x53FE, (unsigned short) 0xA7FC, (unsigned short) 0x5FD9},
				{ (unsigned short) 0x47D3, (unsigned short) 0x8FA6, (unsigned short) 0x8FA6, (unsigned short) 0x1EDA, (unsigned short) 0x3DB4, (unsigned short) 0x7B68, (unsigned short) 0xF6D0},
				{ (unsigned short) 0xB861, (unsigned short) 0x60E3, (unsigned short) 0xC1C6, (unsigned short) 0x93AD, (unsigned short) 0x377B, (unsigned short) 0x6EF6, (unsigned short) 0xDDEC},
				{ (unsigned short) 0x45A0, (unsigned short) 0x8B40, (unsigned short) 0x06A1, (unsigned short) 0x0D42, (unsigned short) 0x1A84, (unsigned short) 0x3508, (unsigned short) 0x6A10},
				{ (unsigned short) 0xAA51, (unsigned short) 0x4483, (unsigned short) 0x8906, (unsigned short) 0x022D, (unsigned short) 0x045A, (unsigned short) 0x08B4, (unsigned short) 0x1168},
				{ (unsigned short) 0x76B4, (unsigned short) 0xED68, (unsigned short) 0xCAF1, (unsigned short) 0x85C3, (unsigned short) 0x1BA7, (unsigned short) 0x374E, (unsigned short) 0x6E9C},
				{ (unsigned short) 0x3730, (unsigned short) 0x6E60, (unsigned short) 0xDCC0, (unsigned short) 0xA9A1, (unsigned short) 0x4363, (unsigned short) 0x86C6, (unsigned short) 0x1DAD},
				{ (unsigned short) 0x3331, (unsigned short) 0x6662, (unsigned short) 0xCCC4, (unsigned short) 0x89A9, (unsigned short) 0x0373, (unsigned short) 0x06E6, (unsigned short) 0x0DCC},
				{ (unsigned short) 0x1021, (unsigned short) 0x2042, (unsigned short) 0x4084, (unsigned short) 0x8108, (unsigned short) 0x1231, (unsigned short) 0x2462, (unsigned short) 0x48C4}
			};
		}
	}
}

#include "theBiasPlanet/unoUtilities/cryptography/MicrosoftPasswordsHasher.hpp"
#include <limits>
#include "theBiasPlanet/coreUtilities/stringsHandling/StringHandler.hpp"

using namespace ::theBiasPlanet::coreUtilities::stringsHandling;

namespace theBiasPlanet {
	namespace unoUtilities {
		namespace cryptography {
			long MicrosoftPasswordsHasher::hashIn32bits (string const & a_originalDatum) {
				unsigned long l_hash = 0;
				u16string l_originalDatumInUtf16 = StringHandler::getUtf16String (a_originalDatum);
				int l_originalDatumLength = l_originalDatumInUtf16.length ();
				if (l_originalDatumLength > 0) {
					if (l_originalDatumLength > c_numberOfInitializationCodes) {
		  		 		l_originalDatumLength = c_numberOfInitializationCodes;
					}
					unsigned short l_highHash = c_initializationCodes [l_originalDatumLength - 1];
					unsigned short l_lowHash = 0;
					unsigned short l_character = 0x0000;
					unsigned char l_byte = 0x00;
					unsigned char l_highByte = 0x00;
					unsigned char l_lowByte = 0x00;
					for (int l_characterIndex = 0; l_characterIndex < l_originalDatumLength; l_characterIndex ++) {
						l_character = l_originalDatumInUtf16 [l_characterIndex];
						l_highByte = (unsigned char) (l_character >> 8);
						l_lowByte = (unsigned char) (l_character & 0xFF);
						l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
						for (int l_matrixcolumnIndex = 0; l_matrixcolumnIndex < c_numberOfEncryptionMatrixColumns; l_matrixcolumnIndex ++) {
							if ((l_byte & (1 << l_matrixcolumnIndex)) != 0) {
								l_highHash = (unsigned short) (l_highHash ^ c_encryptionMatrix [c_numberOfInitializationCodes - l_originalDatumLength + l_characterIndex] [l_matrixcolumnIndex]);
							}
						}
						l_character = l_originalDatumInUtf16 [l_originalDatumLength -1 - l_characterIndex];
						l_highByte = (unsigned char) (l_character >> 8);
						l_lowByte = (unsigned char) (l_character & 0xFF);
						l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
						l_lowHash = ( ( (l_lowHash >> 14) & 0x0001 ) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_byte;
					}
					l_lowHash =  ( ( (l_lowHash >> 14) & 0x0001) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_originalDatumLength ^ 0xCE4B;
					l_hash = (l_highHash << 16) | l_lowHash;
				}
				return (long) l_hash;
			}
			
			~
		}
	}
}

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilities {
		namespace cryptography {
			using System;
			using System.Text;
			
			public class MicrosoftPasswordsHasher {
				private const int c_numberOfInitializationCodes = 15;
				private static readonly ushort [] c_initializationCodes = new ushort [] {(ushort) 0xE1F0, (ushort) 0x1D0F, (ushort) 0xCC9C, (ushort) 0x84C0, (ushort) 0x110C, (ushort) 0x0E10, (ushort) 0xF1CE, (ushort) 0x313E, (ushort) 0x1872, (ushort) 0xE139, (ushort) 0xD40F, (ushort) 0x84F9, (ushort) 0x280C, (ushort) 0xA96A, (ushort) 0x4EC3};
				private const int c_numberOfEncryptionMatrixColumns = 7;
				private static readonly ushort [, ] c_encryptionMatrix = new ushort [, ] {
					{ (ushort) 0xAEFC, (ushort) 0x4DD9, (ushort) 0x9BB2, (ushort) 0x2745, (ushort) 0x4E8A, (ushort) 0x9D14, (ushort) 0x2A09},
					{ (ushort) 0x7B61, (ushort) 0xF6C2, (ushort) 0xFDA5, (ushort) 0xEB6B, (ushort) 0xC6F7, (ushort) 0x9DCF, (ushort) 0x2BBF},
					{ (ushort) 0x4563, (ushort) 0x8AC6, (ushort) 0x05AD, (ushort) 0x0B5A, (ushort) 0x16B4, (ushort) 0x2D68, (ushort) 0x5AD0},
					{ (ushort) 0x0375, (ushort) 0x06EA, (ushort) 0x0DD4, (ushort) 0x1BA8, (ushort) 0x3750, (ushort) 0x6EA0, (ushort) 0xDD40},
					{ (ushort) 0xD849, (ushort) 0xA0B3, (ushort) 0x5147, (ushort) 0xA28E, (ushort) 0x553D, (ushort) 0xAA7A, (ushort) 0x44D5},
					{ (ushort) 0x6F45, (ushort) 0xDE8A, (ushort) 0xAD35, (ushort) 0x4A4B, (ushort) 0x9496, (ushort) 0x390D, (ushort) 0x721A},
					{ (ushort) 0xEB23, (ushort) 0xC667, (ushort) 0x9CEF, (ushort) 0x29FF, (ushort) 0x53FE, (ushort) 0xA7FC, (ushort) 0x5FD9},
					{ (ushort) 0x47D3, (ushort) 0x8FA6, (ushort) 0x8FA6, (ushort) 0x1EDA, (ushort) 0x3DB4, (ushort) 0x7B68, (ushort) 0xF6D0},
					{ (ushort) 0xB861, (ushort) 0x60E3, (ushort) 0xC1C6, (ushort) 0x93AD, (ushort) 0x377B, (ushort) 0x6EF6, (ushort) 0xDDEC},
					{ (ushort) 0x45A0, (ushort) 0x8B40, (ushort) 0x06A1, (ushort) 0x0D42, (ushort) 0x1A84, (ushort) 0x3508, (ushort) 0x6A10},
					{ (ushort) 0xAA51, (ushort) 0x4483, (ushort) 0x8906, (ushort) 0x022D, (ushort) 0x045A, (ushort) 0x08B4, (ushort) 0x1168},
					{ (ushort) 0x76B4, (ushort) 0xED68, (ushort) 0xCAF1, (ushort) 0x85C3, (ushort) 0x1BA7, (ushort) 0x374E, (ushort) 0x6E9C},
					{ (ushort) 0x3730, (ushort) 0x6E60, (ushort) 0xDCC0, (ushort) 0xA9A1, (ushort) 0x4363, (ushort) 0x86C6, (ushort) 0x1DAD},
					{ (ushort) 0x3331, (ushort) 0x6662, (ushort) 0xCCC4, (ushort) 0x89A9, (ushort) 0x0373, (ushort) 0x06E6, (ushort) 0x0DCC},
					{ (ushort) 0x1021, (ushort) 0x2042, (ushort) 0x4084, (ushort) 0x8108, (ushort) 0x1231, (ushort) 0x2462, (ushort) 0x48C4}
				};
				
				public static int hashIn32bits (String a_originalDatum) {
					uint l_hash = 0;
					int l_originalDatumLength = a_originalDatum.Length;
					if (l_originalDatumLength > 0) {
						if (l_originalDatumLength > c_numberOfInitializationCodes) {
					   		l_originalDatumLength = c_numberOfInitializationCodes;
						}
						ushort l_highHash = c_initializationCodes [l_originalDatumLength - 1];
						ushort l_lowHash = 0;
						char l_character = (char) 0x0000;
						byte l_byte = 0x00;
						byte l_highByte = 0x00;
						byte l_lowByte = 0x00;
						for (int l_characterIndex = 0; l_characterIndex < l_originalDatumLength; l_characterIndex ++) {
							l_character = a_originalDatum [l_characterIndex];
							l_highByte = (byte) (l_character >> 8);
							l_lowByte = (byte) (l_character & 0xFF);
							l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
							for (int l_matrixcolumnIndex = 0; l_matrixcolumnIndex < c_numberOfEncryptionMatrixColumns; l_matrixcolumnIndex ++) {
								if ((l_byte & (1 << l_matrixcolumnIndex)) != 0) {
									l_highHash = (ushort) (l_highHash ^ c_encryptionMatrix [c_numberOfInitializationCodes - l_originalDatumLength + l_characterIndex, l_matrixcolumnIndex]);
								}
							}
							l_character = a_originalDatum [l_originalDatumLength -1 - l_characterIndex];
							l_highByte = (byte) (l_character >> 8);
							l_lowByte = (byte) (l_character & 0xFF);
							l_byte = l_lowByte != 0x00 ? l_lowByte : l_highByte;
							l_lowHash = (ushort) (( ( (l_lowHash >> 14) & 0x0001 ) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_byte);
						}
						l_lowHash =  (ushort) (( ( (l_lowHash >> 14) & 0x0001) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_originalDatumLength ^ 0xCE4B);
						l_hash = (uint) ((l_highHash << 16) | l_lowHash);
					}
					return (int) l_hash;
				}
				
				~
			}
		}
	}
}

@Python Source Code
from typing import List

class MicrosoftPasswordsHasher:
	c_numberOfInitializationCodes: int = 15
	c_initializationCodes: List [int] = [0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3]
	c_numberOfEncryptionMatrixColumns: int = 7
	c_encryptionMatrix: List [List [int]] = [
		[ 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09],
		[ 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF],
		[ 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0],
		[ 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40],
		[ 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5],
		[ 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A],
		[ 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9],
		[ 0x47D3, 0x8FA6, 0x8FA6, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0],
		[ 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC],
		[ 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10],
		[ 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168],
		[ 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C],
		[ 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD],
		[ 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC],
		[ 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4]
	]
	
	@staticmethod
	def hashIn32bits (a_originalDatum: str) -> int:
		l_hash: int = 0
		l_originalDatumLength: int = len (a_originalDatum)
		if l_originalDatumLength > 0:
			if l_originalDatumLength > MicrosoftPasswordsHasher.c_numberOfInitializationCodes:
		   		l_originalDatumLength = MicrosoftPasswordsHasher.c_numberOfInitializationCodes
			l_highHash: int = MicrosoftPasswordsHasher.c_initializationCodes [l_originalDatumLength - 1]
			l_lowHash: int = 0
			l_character: int = 0x0000
			l_byte: int = 0x00
			l_highByte: int = 0x00
			l_lowByte: int = 0x00
			l_characterIndex: int = 0
			for l_characterIndex in range (0, l_originalDatumLength, 1):
				l_character = ord (a_originalDatum [l_characterIndex])
				l_highByte = (l_character >> 8)
				l_lowByte = (l_character & 0xFF)
				l_byte = l_lowByte if l_lowByte != 0x00 else l_highByte
				l_matrixcolumnIndex: int = 0
				for l_matrixcolumnIndex in range (0, MicrosoftPasswordsHasher.c_numberOfEncryptionMatrixColumns, 1):
					if (l_byte & (1 << l_matrixcolumnIndex)) != 0:
						l_highHash = (l_highHash ^ MicrosoftPasswordsHasher.c_encryptionMatrix [MicrosoftPasswordsHasher.c_numberOfInitializationCodes - l_originalDatumLength + l_characterIndex] [l_matrixcolumnIndex])
				l_character = ord (a_originalDatum [l_originalDatumLength -1 - l_characterIndex])
				l_highByte = (l_character >> 8)
				l_lowByte = (l_character & 0xFF)
				l_byte = l_lowByte if l_lowByte != 0x00 else l_highByte
				l_lowHash = ( ( (l_lowHash >> 14) & 0x0001 ) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_byte
			l_lowHash =  ( ( (l_lowHash >> 14) & 0x0001) | ( (l_lowHash << 1) & 0x7FFF)) ^ l_originalDatumLength ^ 0xCE4B
			l_hash = (l_highHash << 16) | l_lowHash
		return l_hash
	
	~


Objector 37B
Hmm.

Hypothesizer 7
On the other hand, this is my Java function that creates the hash for any '.xls' file.

@Java Source Code
package theBiasPlanet.unoUtilities.cryptography;

public class MicrosoftPasswordsHasher {
	~
	public static short hashIn16bits (String a_originalDatum) {
		int l_hash = 0;
		byte [] l_originalDatumUtf8BytesArray = null;
		try {
			l_originalDatumUtf8BytesArray = a_originalDatum.getBytes ("UTF-8");
		}
		catch (Exception l_exception) {
			// Is supposed to never happen.
		}
		int l_originalDatumUtf8BytesArrayLength = l_originalDatumUtf8BytesArray.length;
		if (l_originalDatumUtf8BytesArrayLength <= Short.MAX_VALUE) {
			for (int l_byteIndex = l_originalDatumUtf8BytesArrayLength - 1; l_byteIndex >= 0; l_byteIndex --) {
				l_hash = ( (l_hash >>> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF);
				l_hash ^= l_originalDatumUtf8BytesArray [l_byteIndex];
			}
			l_hash = ( (l_hash >>> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF);
			l_hash ^= (0x8000 | ('N' << 8) | 'K');
			l_hash ^= l_originalDatumUtf8BytesArrayLength;
		}
		return (short) l_hash;
	}
}

Objector 37B
. . . This time, the hash is a 16-bits array?

Hypothesizer 7
Yes, but also it will be ultimately turned into a UNO 'long' datum.

Objector 37B
So, the upper 16-bits of the 'long' datum should be all '0'?

Hypothesizer 7
Yes.

Again, 'l_hash' is really a 16-bits arrays, but I use an 'int' variable because of the same reason cited above.

Objector 37B
Of course.

Hypothesizer 7
These are my C++, C#, and Python functions that create the hash for any '.xls' file.

@C++ Source Code
#ifndef __theBiasPlanet_unoUtilities_cryptography_MicrosoftPasswordsHasher_hpp__
	#define __theBiasPlanet_unoUtilities_cryptography_MicrosoftPasswordsHasher_hpp__
	
	#include <string>
	#include "theBiasPlanet/unoUtilities/visualCplusplusSpecificHeaders/VisualCplusplusSpecificDefinitions.hpp"
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace unoUtilities {
			namespace cryptography {
				class __theBiasPlanet_unoUtilities_symbolExportingOrImportingForVisualCplusplus__ MicrosoftPasswordsHasher {
					private:
						~
					public:
						~
						static short hashIn16bits (string const & a_originalDatum);
				};
			}
		}
	}
#endif

#include "theBiasPlanet/unoUtilities/cryptography/MicrosoftPasswordsHasher.hpp"
#include <limits>
#include "theBiasPlanet/coreUtilities/stringsHandling/StringHandler.hpp"

using namespace ::theBiasPlanet::coreUtilities::stringsHandling;

namespace theBiasPlanet {
	namespace unoUtilities {
		namespace cryptography {
			~
			short MicrosoftPasswordsHasher::hashIn16bits (string const & a_originalDatum) {
				unsigned short l_hash = 0;
				int l_originalDatumLength = a_originalDatum.length ();
				if (l_originalDatumLength <= numeric_limits <short>::max ()) {
					for (int l_byteIndex = l_originalDatumLength - 1; l_byteIndex >= 0; l_byteIndex --) {
						l_hash = ( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF);
						l_hash ^= a_originalDatum [l_byteIndex];
					}
					l_hash = ( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF);
					l_hash ^= (0x8000 | ('N' << 8) | 'K');
					l_hash ^= l_originalDatumLength;
				}
				return (short) l_hash;
			}
		}
	}
}

@C# Source Code
namespace theBiasPlanet {
	namespace unoUtilities {
		namespace cryptography {
			using System;
			using System.Text;
			
			public class MicrosoftPasswordsHasher {
				~
				
				public static short hashIn16bits (String a_originalDatum) {
					ushort l_hash = 0;
					byte [] l_originalDatumUtf8BytesArray = null;
					try {
						l_originalDatumUtf8BytesArray = Encoding.UTF8.GetBytes (a_originalDatum);
					}
					catch (Exception l_exception) {
						// Is supposed to never happen.
					}
					int l_originalDatumUtf8BytesArrayLength = l_originalDatumUtf8BytesArray.Length;
					if (l_originalDatumUtf8BytesArrayLength <= Int16.MaxValue) {
						for (int l_byteIndex = l_originalDatumUtf8BytesArrayLength - 1; l_byteIndex >= 0; l_byteIndex --) {
							l_hash = (ushort) (( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF));
							l_hash ^= l_originalDatumUtf8BytesArray [l_byteIndex];
						}
						l_hash = (ushort) (( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF));
						l_hash ^= (0x8000 | ('N' << 8) | 'K');
						l_hash ^= (ushort) l_originalDatumUtf8BytesArrayLength;
					}
					return (short)l_hash;
				}
			}
		}
	}
}

@Python Source Code
from typing import List

class MicrosoftPasswordsHasher:
	~
	
	@staticmethod
	def hashIn16bits (a_originalDatum: str) -> int:
		l_hash: int = 0
		l_originalDatumUtf8BytesArray: bytes = b''
		try:
			l_originalDatumUtf8BytesArray = a_originalDatum.encode (UTF-8)
		except (Exception) as l_exception:
			# Is supposed to never happen.
			None
		l_originalDatumUtf8BytesArrayLength: int = len (l_originalDatumUtf8BytesArray)
		if l_originalDatumUtf8BytesArrayLength <= 2**15 - 1:
			l_byteIndex: int = 0
			for l_byteIndex in range (l_originalDatumUtf8BytesArrayLength - 1, 0 - 1, -1):
				l_hash = ( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF)
				l_hash ^= l_originalDatumUtf8BytesArray [l_byteIndex]
			l_hash = ( (l_hash >> 14) & 0x01) | ( (l_hash << 1) & 0x7FFF)
			l_hash ^= (0x8000 | (ord ('N') << 8) | ord ('K'))
			l_hash ^= l_originalDatumUtf8BytesArrayLength
		return l_hash


Objector 37B
What have happened to the implementations for the other languages cited in your 'Topics'?

Hypothesizer 7
I will leave them to anyone who want them.

Objector 37B
. . .


2: Setting a Document-Storing Property


Hypothesizer 7
The document-storing property to be set is 'ModifyPasswordInfo' as is for the OpenDocument formats, but this time, the value is a UNO 'long' datum.

In Java, we can just put the hash gotten in the previous section into the property.

In C++ and C#, we wrap the hash gotten in the previous section in UNO 'Any', and put the UNO 'Any' datum into the property.

In Python, we get the 'signed long' (for the '.doc' format) or 'signed short' (for the '.xls' format) datum of the hash gotten in the previous section, wrap the signed datum in UNO 'Any', and put the UNO 'Any' datum into the property.

Objector 37A
Huh? Why the fuss only for Python?

Hypothesizer 7
That is because we have set the desired bits array into the hash 'int' datum in the previous section, but UNO will not see the bits array itself, but the number value of the 'int' variable.

Objector 37A
Huh?

Hypothesizer 7
For example, we have set '0xFFFFFFFF' into the hash 'int' datum, but it is not '-1', but '4294967295', which is out of the 'long' range, causing an error; so we have to turn '4294967295' into '-1', which will become '0xFFFFFFFF' in UNO.

Objector 37A
Hmm . . ., whatever.


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, and the specifications of the file conversions order CSV file for setting editing passwords are described in the 1st part of this article.


4: The Conclusion and Beyond


Hypothesizer 7
Now, we know how to set any editing password into a Microsoft binary Word or Excel ('.doc' or '.xls') 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 an Office Open XML 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>