Psono Conversion

Password conversion.

So in my last post I talked about the Gorilla password manager and how I have made a custom syncing password safe using a bash script. This is the same password manager I use in my line of work however we are switching to a new system. That system is Psono, it is an open source web hosted solution and instead of using encrypted files on your drive you can host them behind a web portal that you can give other users access to see or modify.

This tool began after I asked what options Psono has to import old password safes and what format does Psono take and what format does Gorilla export? Turns out the answer to that question is a CSV file or comma seperated file which is just that. Strings seperated by ',' characters for example:

username,password,url,notes

Here is how I exported it in Gorilla:

psafe

psafe1

After exporting Gorilla CSV file it would look something like the following:

080e84d8-c503-4cea-73b1-f0fd76bd454f,Gentoo.Website,Website Login,www.gentoo.org,admin,ezpassword,Admin!
e1c3d1f6-93cc-4cac-7140-60b561e04fb6,Gentoo,Root Login,,Root,Toor,Test!
d93dfe61-c789-495e-57d0-1d251ae84710,VMWare,Hypervisor,,Admin,administratorpassword,

These are examples I made to showcase, they are not real logins before anyone says a word.

The values are as follows: UUID, Group, Title, URL, username, password.

The UUID in this case was not relevent to me at all, if you notice the title on the first line is “Gentoo.Website” that dot in the middle denotes that the “Website” part of it is a subgroup of “Gentoo”, I needed a way for the script to know that and create a required folder. The rest of the data I can extract.

You might be thinking why does this matter? Can’t you import this straight into Psono and it works? The answer to those questions is because Psono doesn’t keep the folder structure and dumped every password into one big folder thus we lost our organized safe structure.

I also noticed Psono accepted CSV files but it also accepted an XML file, this is where I decided to convert it into XML using python.

The script.

The sourcecode of the script itself may or may not be posted here and if it does end up on here it will be updated and added in the future.

The way my in which my script works is first we format the data using the csv library of python. Using a simple for loop I deleted the first element of every row of the data I read in using python using .pop(0). This got rid of the UUID which I had no use for within Psono.

After this I used the sorted() function which gave me something like the following:

Gentoo,Root Login,,Root,Toor,Test!
Gentoo.Website,Website Login,www.gentoo.org,admin,ezpassword,Admin!
VMWare,Hypervisor,,Admin,administratorpassword,

The program works more or less the following way from here on out:

1. Has the previous line already had a group made? No? Add the group and then add an entry.
2. Has the previous line already had a group made? Yes? Okay add in the credentials into this folder and keep adding in until we hit the end of this group.
3. Does this folder I am in have a subgroup? Yes? make that and do the same and if not repeat

This is all setup into an XML tree and outputted to a file output.xml, I then import this into Psono and this is what I get:

psafe2

I press upload and see the progress bar

psafe3

This is what was imported, if you compare to the previous Gorilla shortcuts you will notice that it’s the same credentials in the exact same order as Gorilla was keeping them.

psafe4

We can verify this by taking a look at the credentials and all seems to be correct.

psafe5

The source.

I decided to release the source for this as it’s only a one use script.

There are some bugs however such as if Gorilla has multiple nested subgroups it will only go two deep.

#!/usr/bin/python3
import csv
import sys
import xml.etree.cElementTree as ET


def create_entry(entry, notes, password, title, url, username):
    Keys = ["Notes", "Password", "Title", "URL", "UserName"]
    Values = [notes, password, title, url, username]
    entry_root = ET.SubElement(entry, "Entry")
    for i in range(5):
        string = ET.SubElement(entry_root, "String")
        ET.SubElement(string, "Key").text = Keys[i]
        ET.SubElement(string, "Value").text = Values[i]


def create_subgroup(root, groupname):
    grouproot = ET.SubElement(root, "Group")
    name = ET.SubElement(grouproot, "Name")
    name.text = groupname
    return grouproot

fileroot = ET.Element("KeePassFile")
root = ET.SubElement(fileroot, "Root")
database = create_subgroup(root, "Database")

try:
    if sys.argv[1] == "":
        pass
except:
    sys.exit("Please give a CSV file. Examples: \n$ python3 csvtoxml.py example.csv\n$ ./csvtoxml.py example.csv")

with open(sys.argv[1], 'r') as f:
    data = [line for line in csv.reader(f)]

for line in data:
    line.pop(0)

data = sorted(data)

index = 0

for row in data:
    if '.' in row[0]:
        split_row = row[0].split('.')
        old_split_row = data[index - 1][0].split('.')
        if split_row[-1] == old_split_row[-1]:
            create_entry(database[-1][-1], row[5], row[4], row[1], row[2], row[3])
            #print(database[-1][-1].text)
        else:
            group = create_subgroup(database[-1], split_row[-1])
            create_entry(group, row[5], row[4], row[1], row[2], row[3])
    if not '.' in row[0]:
        if row[0] == data[index - 1][0]:
            create_entry(database[-1], row[5], row[4], row[1], row[2], row[3])
        else:
            group = create_subgroup(database, row[0])
            create_entry(group, row[5], row[4], row[1], row[2], row[3])
    index += 1

tree = ET.ElementTree(fileroot)
tree.write("output.xml")

print("Successfully made the XML, saving as output.xml in current directory. Remember to import it into Psono as \"KeePass.info (XML)\"")