Page 1 of 1

[Solved] Trying to change passwords of odt+ods files via CLI

Posted: Tue May 11, 2021 10:08 am
by s3a
Hello to everyone who's reading this. :)

I don't know if this is the right place to post this question, but does anyone know how to use the GNU / Linux command-line interface to change passwords of odt (and ods, etc.) files (assuming that it is possible to do)?

I wasn't able to get it done with the libreoffice, lowriter and unoconv CLI utilities. (I'm trying to change passwords of files in bulk with a script.)

Any input would be greatly appreciated!

P.S.
Technically, I'm using LibreOffice, but I see no reason why this would be different from OpenOffice.

Re: Trying to change passwords of odt and ods files via the

Posted: Tue May 11, 2021 11:10 am
by robleyd
There are several ways in which passwords might be used for files - which are you referring to?

Re: Trying to change passwords of odt and ods files via the

Posted: Tue May 11, 2021 12:08 pm
by Villeroy
If we are talking about the password which encrypts an office document, you have to start LibreOffice in listening mode and write a Python script which connects to the listening office, opens and re-saves each document.

Re: Trying to change passwords of odt and ods files via the

Posted: Tue May 11, 2021 6:28 pm
by Villeroy
 Edit: I found the error in my Office class and fixed the code block. 
The following script works when I run it as a macro but fails when I run it as a stand-alone script ("memory access error" in loadComponentFromURL) and as stand-alone script as well.
There are 4 global variables:

1. g_Connection which is not used when the code is run in macro context. See comment text in the code.
2. g_Pattern = '/tmp/test/*.od?' specifies the path and name pattern
3. g_OldPwd is the password to open the files
4. g_NewPwd is the password to store the files

Store it under ~/.config/libreoffice/4/user/Scripts/python/reencrypt.py (where ~/.config/libreoffice/4/user/ is the path of your user profile)
Start the office from a terminal and call Tools>Macros>Organize>Python... browse "MyMacros">reencrypt > reEncrypt_Macro and push the [Run] button
On the terminal you get an output for each file, either Success: filename or ###FAIL: filename

Code: Select all

import glob
import uno
from com.sun.star.awt.MessageBoxType import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_ABORT_IGNORE_RETRY, BUTTONS_YES_NO_CANCEL, BUTTONS_YES_NO, BUTTONS_RETRY_CANCEL, DEFAULT_BUTTON_OK, DEFAULT_BUTTON_CANCEL, DEFAULT_BUTTON_RETRY, DEFAULT_BUTTON_YES, DEFAULT_BUTTON_NO, DEFAULT_BUTTON_IGNORE
from com.sun.star.awt.MessageBoxResults import CANCEL, OK, YES, NO, RETRY, IGNORE

#################################
'''
if you call the office suite like this:
 soffice --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"
then
g_Connect = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
'''
g_Connect = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
g_Pattern = '/tmp/test/*.od?'
g_OldPwd = 'bar'
g_NewPwd = 'foo'
#################################

class Office:
    '''Frequently used office objects and useful methods'''
    def __init__(self, connect_string = '', local_context = uno.getComponentContext()):
        if connect_string:
            # soffice.bin --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"
            resolver = local_context.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", local_context )
            self.ctx = resolver.resolve(connect_string)
        else:
            self.ctx = local_context
        self.smgr = self.ctx.ServiceManager
        self.tk = self.smgr.createInstance('com.sun.star.awt.Toolkit')
        self.StarDesktop = self.smgr.createInstance('com.sun.star.frame.Desktop')
        self.win = None

    def getDocument(self):
        return self.StarDesktop.CurrentFrame.Controller.getModel()
        
    def createUnoService(self, name,):
        return self.smgr.createInstance(name)
        
    def getURLStruct(self, sURL):
        url = uno.createUnoStruct('com.sun.star.util.URL')
        srv = self.createUnoService('com.sun.star.util.URLTransformer')
        url.Complete = sURL
        x = uno.invoke(srv,"parseStrict",(uno.Any('com.sun.star.util.URL',url),))
        return x[0]==True and x[1] or Null        
        
    def getPropertyValue(self, n, v):
        p = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
        p.Name = n
        p.Value = v
        return p

    def setMsgboxWindow(self, win):
        self.win = win

    def msgbox(self,title='<Dummy>',msg='<Hello World>',etyp=MESSAGEBOX,ebtn=BUTTONS_OK):
        mbox = self.tk.createMessageBox(self.win,etyp,ebtn,title,msg)
        return mbox.execute()        
    
    def mri(self, obj):
        mri = self.createUnoService("mytools.Mri")
        mri.inspect(obj)

def reEncrypt_Macro(**args):
    reEncrypt('')

def reEncrypt(sCon):
    ofc = Office(sCon)
    pv1 = ofc.getPropertyValue("Password", g_OldPwd)
    pv2 = ofc.getPropertyValue("Password", g_NewPwd)
    dtp = ofc.StarDesktop
    g = glob.iglob(g_Pattern)
    for f in g:
        url = uno.systemPathToFileUrl(f)
        try:
            doc = dtp.loadComponentFromURL(url, '_blank', 0, (pv1,))
            doc.storeAsURL(url, (pv2,))
        except:
            print('### FAIL: ' + f)
        else:
            doc.close(False)
            print('SUCCESS: ' + f)
            
if __name__ == "__main__":
    reEncrypt(g_Connect)

g_exportedScripts = reEncrypt_Macro,
I ran this several times changing the passwords between "foo" and "bar" and vice versa. When the files are unencrypted, the password is ignored.

In CLI mode it was intended to be started works like this:

Code: Select all

cd ~/.config/libreoffice/4/user/Scripts/python/
libreoffice --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" &
python3 reencrypt.py
and the output is

Code: Select all

func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/Untitled 1.ods
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/USA_Budget_-1789_2017.ods
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/musterbrief_auskunftsersuchen_und_widerruf.odt
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/untitled_1.ods
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/Logfile.odt
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/untitled_0.odt
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/Untitled 1.odt
func=xmlSecCheckVersionExt:file=xmlsec.c:line=188:obj=unknown:subj=unknown:error=19:invalid version:mode=abi compatible;expected minor version=2;real minor version=2;expected subminor version=25;real subminor version=26
SUCCESS: /tmp/test/O2_Kündigung.odt

Re: Trying to change passwords of odt and ods files via the

Posted: Thu May 13, 2021 11:52 pm
by s3a
Sorry for the delay, and thanks for responding!

I put a test odt file with a (regular / AES 256-bit, non-GPG / PGP) password in /tmp, but nothing happens with the file (as far as I can tell).:

Code: Select all

s3a@debian:~$ cd ~/.config/libreoffice/4/s3a/Scripts/python/
s3a@debian:~/.config/libreoffice/4/s3a/Scripts/python$ ls
reencrypt.py
s3a@debian:~/.config/libreoffice/4/s3a/Scripts/python$ libreoffice --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" &
[1] 55707
s3a@debian:~/.config/libreoffice/4/s3a/Scripts/python$ python3 reencrypt.py 
[1]+  Done                    libreoffice --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"
s3a@debian:~/.config/libreoffice/4/s3a/Scripts/python$ 
I vaguely recall there being some kind of error message involving gtk xapp or something like that, so I also did

Code: Select all

apt-get update && apt-get install -y python3-xapp xapps-common gir1.2-xapp-1.0 libxapp-dev libxapp1
, but it still doesn't do anything to the odt file in /tmp (as far as I can tell).

P.S.
This is for regular passwords, which I think are encrypted with AES 256 bits, not the GPG/PGP encryption.

Re: Trying to change passwords of odt and ods files via the

Posted: Fri May 14, 2021 6:55 am
by Villeroy
How can I know how you encrypted your documents?

This is what I was assuming and what most users mean when they are talking about password protected documents:
Load a new document, text or spreadsheet.
menu:File>Save As...
Check the password option.
Choose the path you have specified in the script variable g_Pattern. File type is Open Document Text (odt) or spreadsheet (ods) respectively.

When prompted for password and password confirmation, enter the password you have specified in the script variable g_OldPwd.
The password protected ods/odt document is a zip archive with encrypted contents.
Run the script or macro.
Open the document again using the password you have specified in the script variable g_NewPwd.

Re: Trying to change passwords of odt and ods files via the

Posted: Fri May 14, 2021 8:45 am
by s3a
Oops!

Sorry, I was (and am :() very tired; I didn't notice that the directory was /tmp/test; I kept seeing it as just /tmp.

This code you made for me is not only helpful for solving the main problem of this thread, but perhaps also for getting me to understand how to interface with LibreOffice and/or OpenOffice in code (as I analyze it more thoroughly later)!

Thanks again!

Re: [SOLVED] Trying to change passwords of odt+ods files via

Posted: Fri May 14, 2021 11:04 am
by Villeroy
Running Linux, I would not use OpenOffice for anything like this.
Just in case you want to try, there are subtile differences.

Code: Select all

cd ~/.openoffice/4/user/Scripts/python/
/opt/openoffice4/program/soffice -accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" &
/opt/openoffice4/program/python reencrypt.py
Storing the script in the profile folder is only important if you want to call it as a macro from within the running office.
The OpenOffice executable uses Sun style arguments with a single dash.
You have to call the Python runtime which is shipped with the office suite.

And yes, a serious Python program would take the variables as arguments. I just copied some macro code, a helper class and added some hard coded arguments.

Re: [SOLVED] Trying to change passwords of odt+ods files via

Posted: Tue May 18, 2021 1:02 am
by s3a
Sorry for the delay again, but I just wanted to say thanks for that extra information. You've been immensely helpful. :)