2

From a Bash terminal in a popular GNU/Linux distro, how can a non-password-protected [OpenDocument Text (.odt)](OpenDocument Text) document be transformed into a password-protected one?

Unfortunately, the LibreOffice man-page does not even contain the string "password", so is not much help here.

I do not wish to wrap the file in an encrypted container such as a ZIP file that will require it to be extracted before it can be opened by LibreOffice. It is important the the recipient of the file will be able to open it without needing anything except LibreOffice and knowledge of the file's password.

I would prefer to achieve the desired result using LibreOffice itself and/or standard GNU/Linux utilities, if possible. Otherwise, I would prefer to use only free software applications that are commonly packaged for GNU/Linux distros.

  • 1
    You can create a macro for this task (save file with password encryption) in libreoffice (try tools->macros->record macro), modify that for your needs, save somewhere, and then from some script call libreoffice writer headless: soffice --headless <filename> <macroname> – ridgy Mar 26 '17 at 20:27
  • Consider gpg with a shared secret / symmetric key. – Jeff Schaller Mar 26 '17 at 23:22
  • @JeffSchaller, thanks, but using gpg would not meet the stated requirement: "It is important the the recipient of the file will be able to open it without needing anything except LibreOffice and knowledge of the file's password." The recipient will have LibreOffice installed, but not necessarily gpg. – sampablokuper Mar 30 '17 at 0:09
  • @ridgy, thanks. Interesting comment. Maybe write it up as an answer? Can LibreOffice macros prompt for input (in this case, the desired password) at the command line? Or would I have to record one macro per password? – sampablokuper Mar 30 '17 at 0:12
  • I think you can do both. Youl'd have to get familiar with libreoffice macros, as there are lots of ways to do that. Search for "soffice macro headless", you will find very different answers. If you can wait and I can spend some time, I'll come back with a working macro - may take a week or two. – ridgy Mar 30 '17 at 9:41
3

The module I created is a quick hack; no error checking, so you might destroy your valuable documents. Use at your own risk, have a reliable backup at hand, and never run this with root rights!!

Tl;dr:

  1. Download the module content here and save it somewhere.
  2. Start Libreoffice and go to the macro organisation via tools->macros->organize macros->libreoffice basic.
  3. Make sure from the macro listing on the left there is a library named "Tools" in container "LibreOffice Macros" (the macros in there are needed)
  4. Select "Organizer" on the right of the dialog
  5. Open the container "My Macros", select "Standard", and on the right select "New"
  6. Give the module the name "saveWithPassword"
  7. With the newly created module highlighted, select "Edit". This will open the IDE with the new module displayed
  8. Make sure you did select the correct module, then delete the content of the editor window.
  9. Open the downloaded module file with any GUI editor (e.g. gedit),select all and then cut&paste the complete sourcecode into the LibreOffice editor window. Save and Close and you're done.
  10. If you are familiar with programming and debugging, you can open a .odt file, open the macro IDE (like above), and step through the macro, look at the content of variables & the like.

Now, how to use the macros?

Suppose there is a list of .odt files that should be protected with the same password. The protected files shall be saved in a different target folder (has to exist) to keep the originals unchanged. Then you call

$ lowriter --invisible <list of .odt files> macro:///standard.saveWithPasswd.saveWithPasswd

(supposed you did create the module as suggested). After loading the files, the macro will be started and you will be asked for the target directory and the password. The documents will then be saved in the target directory, protected with the password supplied.

If you do not want to have all the documents opened in windows, there is a second (silent) method:

Make sure there is no instance of LibreOffice running. Then run

$ export password=<the password to use>
$ export target=<full path to the target directory>
$ lowriter --headless <list of .odt files> macro:///standard.saveWithPasswd.saveWithPasswd

Now for the details

The main function is to write the file, protected by password. There are different ways to do that; if you just record what will be done interactively you end up with something like

document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")

dim args1(2) as new com.sun.star.beans.PropertyValue
args1(0).Name = "URL"
args1(0).Value = "file:///home/user/Untitled%201.odt"
args1(1).Name = "FilterName"
args1(1).Value = "writer8"
args1(2).Name = "EncryptionData"
args1(2).Value = Array(Array("PackageSHA256UTF8EncryptionKey",Array(-14,-81,-47,-54,-53,84,65,-91,-26,90,122,70,10,95,-104,-104,-73,-71,-117,8,-86,99,35,-94,-27,60,-117,-102,-106,-122,-51,-122)),Array("PackageSHA1UTF8EncryptionKey",Array(24,97,84,113,43,45,95,103,-111,-40,91,-102,9,-121,-71,-113,-94,49,119,-100)),Array("PackageSHA1MS1252EncryptionKey",Array(24,97,84,113,43,45,95,103,-111,-40,91,-102,9,-121,-71,-113,-94,49,119,-100)))

dispatcher.executeDispatch(document, ".uno:SaveAs", "", 0, args1())

what is far too complicated.

In [2] 5.8.3 I found the much easier solution:

Dim args(0) As New com.sun.star.beans.PropertyValue
Dim sURL$
args(0).Name ="Password"
args(0).Value = "test"
sURL=ConvertToURL("/andrew0/home/andy/test.odt")
ThisComponent.storeToURL(sURL, args())

The most part of the work to do now was to

  • pass the password and target filename/directory to the macro
  • iterate over the open documents
  • work with environment variables and dialogues
  • find and use existing library functions

Some parts of the module I will describe more deeply (I think the rest is trivial):

GlobalScope.BasicLibraries.loadLibrary("Tools")

This loads the global LibreOffice library "Tools". Some of the macros in there are used for handling of strings etc.

oComponents = StarDesktop.getComponents()
oDocs = oComponents.createEnumeration()
Do While oDocs.hasMoreElements()
  oDoc = oDocs.nextElement()
  .
  . 
Loop

This iterates over the currently open documents/frames. As for testing and debugging there was also the IDE open, I had to test if the frame really contained a valid document:

sUrl     = oDoc.getUrl()
If sUrl <> "" Then

Now if the document has an URL defined (don't test here if it really exists...) a new URL is created and the document saved there with password:

sName    = FileNameoutofPath(sUrl)
sName    = myTargetDir & "/" & sName
sUrl     = ConvertToURL(sName)
aArgs(0).Name = "Password"
aArgs(0).Value = myPassword
oDoc.storeToURL(sURL, aArgs())
oDoc.close(true)

Here the functions of the "Tools" library are used. Especially when calling lowriter with option --headless, the document must be closed at the end, as otherwise lowriter does not terminate.

I could not manage to pass "password" and "target directory" as parameters to the module, so I use the two ways: As shell environment variables, as this can be fully automated, and as dialogue input.

Getting the password is just trivial using the "Environ" function and the default "InputBox". If you want to have the password hidden while inputting, you have to create your own dialog.

Picking the target folder is a bit more tricky, as this is no simple runtime function:

oFileDialog = CreateUnoService("com.sun.star.ui.dialogs.FolderPicker")
oUcb = createUnoService("com.sun.star.ucb.SimpleFileAccess")
InitPath = GetPathSettings("Work")
If oUcb.Exists(InitPath) Then
  oFileDialog.SetDisplayDirectory(InitPath)
End If

If you decide to have a static "InitPath" (the path where the "FolderPicker" starts when opened), you can define that and cut everything from oUcb = to End If, except oFileDialog.SetDisplayDirectory. When run from the command line you also might set InitPath = Environ("PWD"). For now the start is the folder you have defined as your "My Documents" folder in the LibreOffice settings.

oFileDialog.SetTitle(sTitle)
iAccept = oFileDialog.Execute()
If iAccept = 1 Then
  sPath = oFileDialog.Directory
  getFolderName = sPath
End If

I think this is self-explaining. But beware, there is no error checking here - if you cancel the file dialog, the TargetPath will not be defined. This should lead to a complete break of the macro, or you have to define a while loop instead of the If in askForTarget:

Do While myTargetDir = "" 
  myTargetDir = getFolderName("Please select output directory") 
Loop

You got the idea...

There are lots of modifications I could think of. At first, do error checking to be sure not to overwrite the original files (e.g. test if TargetDir is empty) or do other harm. Or, create a text file with the original filesnames, the target and the password, and pass that to the macro to be done sequentially (you got the idea?). Or you have all the original documents in one folder, pass that folder path to the macro, and then iterate the directory listing (there are examples as well).

During research, I found (and used) some good documentation with lots of examples:

[1] OpenOffice Macros Explained by Andrew Pitonyak (also available as german version)

[2] Example macros by the same author: Examples described and Untested macros

[3] LibreOffice API documentation

and questions/answers like https://ask.libreoffice.org/en/question/29997/how-to-run-a-macro-on-multiple-files-batch/

Finally, thank you for your question and your patience. It was a lot of fun to come back to macro programming, and I have refreshed some knowledge and learned many new things.

  • In the end it is not so complicated; but it is over 15 years since I used BASIC for the last time (Office 2000). So I had to get into it again and find some useful documentation (see the list at the end). – ridgy Apr 15 '17 at 9:15
  • Thanks :) It's certainly more complicated than I'd hoped! Your warning at the top is well taken. Upvoted your answer for the evident effort you've expended. Will mark as correct if/when I find time to actually fire up a VM (for safety) and try it out! – sampablokuper Apr 20 '17 at 10:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.