PasswordSafe to KeePassX Conversion Tool

PasswordSafe to KeePassX Conversion Tool

Postby dbuckwalter » 22 May 2008, 04:19

For those of you that are interested, I wrote a perl script to convert PasswordSafe databases (exported in Plain Text format [tab seperated]) into KeePassX XML Databases.

Code: Select all
#!/usr/bin/perl

# Author: Dan Buckwalter
#        dbuckwalter@gmail.com
# Feel free to use/modify this in any way shape or form you wish.
#
# Export your PasswordSafe database into plain text format the use this script to
# convert it into a KeepPassX XML database
#
# Usage:
#         perl passwordsafe2keepassx.pl <passwordsafe_export>.txt

use warnings;
use strict;

# Define last_group var, this is used when there is more than one group of entries
my $last_group = "0";

# Backreference each tab seperated value
my $regex = qr/^(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)$/;

# xml header
print "<!DOCTYPE KEEPASSX_DATABASE>\n<database>\n";

while (<>) {
   next if /^Group/; # ignore column headers, will ignore groups that begin with "Group"
   my ($group, $username, $password, $url, $autotype, $create_time, $pw_mod_time, $access_time, $expire, $rec_mod_time, $history, $notes) = /$regex/;
   
   # PasswordSafe exports it's entries GroupName.EntryName, so split them into their own vars
   my $entry;
   ($group, $entry) = split(/\./, $group);
   
   # Fix some export weirdness in the entries
   $entry =~ s/ª/./g;
   $expire = 'Never' if ! $expire; # if no date, password never expires
   $create_time =~ s/\//-/g;
   $access_time =~ s/\//-/g;
   $rec_mod_time =~ s/\//-/g;
   $entry =~ s/&/-/g;
   $notes =~ s/"//g;
   $notes =~ s/ª/-/g;
   
   # Check to see if this entry is in a new group, if it is close previous group and start a new one
   if ($group ne $last_group) {
      print " </group>\n" unless $last_group eq "0";
      $last_group = $group;
      print " <group>\n  <title>$group</title>\n  <icon>0</icon>\n";
   }
   
   # shazam
   print "   <entry>\n    <title>$entry</title>\n";
   print "    <username>$username</username>\n";
   print "    <password>$password</password>\n";
   print "    <url>$url</url>\n";
   print "    <comment>$notes    </comment>\n";
   print "    <icon>0</icon>\n";
   print "    <creation>$create_time</creation>\n";
   print "    <lastaccess>$access_time</lastaccess>\n";
   print "    <lastmod>$rec_mod_time</lastmod>\n";
   print "    <expire>$expire</expire>\n";
   print "   </entry>\n";
}

# print xml footer
print " </group>\n</database>";


It works without issue on my PasswordSafe database but if you find any errors with yours let me know.
dbuckwalter
KPX user
 
Posts: 2
Joined: 20 May 2008, 23:18

My update for PasswordSafe to KeePassX Conversion Tool

Postby bswarner » 30 Jul 2008, 00:32

Here is my update to Dan's excellent code. It handles some special XML characters. Most of the changes have to do with handling nested groups.

Comments are welcome.

#!/usr/bin/perl

# Original author: Dan Buckwalter
# dbuckwalter@gmail.com
#
# Update by: Bob Swarner July 29, 2008
# bobswarner@gmail.com
# Updated to account for nested groups and XML characters in the text
# Also, prettyize the XML output
# Trying to elegantly handle the nesting ended up greatly complicating Dan's fine code
#
# Feel free to use/modify this in any way shape or form you wish.
#
# Export your PasswordSafe database into plain text format the use this script to
# convert it into a KeepPassX XML database
#
# Usage:
# perl passwordsafe2keepassx.pl <passwordsafe_export >keepass_import.xml

use warnings;
use strict;

my (%DATA);
sub get_entry_ref( $ $ @ );
sub write_group( $ $ );

my ($TAB) = " ";


#---- clean_text ---------------------------------------------------------------------------

# clean up text data to make it XML'ized

sub clean_text($) {
my ($data) = @_;
$data =~ s/ª/./g;
$data =~ s/&/&amp;/g;
$data =~ s/>/&gt;/g;
$data =~ s/</&lt;/g;
return $data;
}

#---- clean_time ---------------------------------------------------------------------------

# clean up time data

sub clean_time($) {
my ($data) = @_;
$data =~ s|/|-|g;
return $data;
}

#---- get_entry_ref -----------------------------------------------------------------

# Given a group array and a key, create a hash to hold the entry
# Call recursively to handle sub-groups
# This is the most complicated bit, but it makes outputing the data a breeze

sub get_entry_ref( $ $ @ ) {
my ($key, $data_ref, @groups) = @_;

my ($group, $count, $ret);

# find how many groups in the array
$count = @groups;

# capture the current group and strip from the @groups array
$group = shift @groups;

if ($count == 0) {
# since we have used up all of our groups, create a hash for the entry and return the reference
$data_ref->{KEYS}{$key}{icon} = "0";
$ret = \%{$data_ref->{KEYS}{$key}};
} else {
# since we still have sub-group(s), create a hash for the group and call recursively to the sub-group(s)
# The {x}=x is just to ensure the hash is created - I imagine there is a better way to do it.
$data_ref->{GROUPS}{$group}{x} = "x";
$ret = get_entry_ref( $key, \%{$data_ref->{GROUPS}{$group}}, @groups );
}
return $ret;
}

#---- read_pwsafe --------------------------------------------------------------------

# Read Password Safe tab-delimited file from standard input and save to memory

sub read_pwsafe() {
my (@list, @groups, $entry, $username, $key, $entry_ref, $comment);
my $is_first = 1;

while (<>) {
# skip the first line with column headings
if ($is_first) {
$is_first = 0;
next;
}

# split the line and strip quotes if present
chop while (/[\n\r]$/);
@list = split /\t/, $_;

# Here is the breakdown of @list (in version 3.13 of Password Safe)
#
# 0: Group/Title
# 1: Username
# 2: Password
# 3: URL
# 4: AutoType
# 5: Created Time
# 6: Password Modified Time
# 7: Last Access Time
# 8: Password Expiry Date
# 9: Password Expiry Interval
# 10: Record Modified Time
# 11: Password Policy
# 12: History
# 13: Notes

# PasswordSafe exports it's entries GroupName[.Groupname].EntryName, so split them into their own vars
# capture as an array to get nested groups
(@groups) = split(/\./, $list[0] );
$entry = pop @groups;

# create an entry in a hash table nested by group and subgroups
$username = clean_text( $list[1] );
$key = "${username} at ${entry}";
$entry_ref = get_entry_ref( $key, \%DATA, @groups );

# save the important bits in the hash reference for this entry, cleaning the text as we go
$entry_ref->{title} = $entry;
$entry_ref->{username} = $username;
$entry_ref->{password} = clean_text( $list[2] );
$entry_ref->{url} = clean_text( $list[3] );
$comment = clean_text( $list[13] );
$comment =~ s/"//g;
$comment =~ s/»/\n/g;
$entry_ref->{comment} = $comment;
$entry_ref->{creation} = clean_time( $list[5] );
$entry_ref->{lastaccess} = clean_time( $list[7] );
$entry_ref->{lastmod} = clean_time( $list[10] );
$entry_ref->{expire} = ($list[8] ? clean_time($list[8]) : "Never" );
}
}

#---- write_entry --------------------------------------------------------------------

# writes the data associated with a particular entry in XML padded by group depth

sub write_entry( $ $ ) {
my ($data_ref,$depth) = @_;
my ($pad) = $TAB x $depth;
my ($padplus) = $TAB x ($depth+1);
my ($key);

print "$pad<entry>\n";
# for each saved key, output in XML format
foreach $key (sort keys %$data_ref ) {
print "$padplus<$key>$data_ref->{$key}</$key>\n";
}
print "$pad</entry>\n";
}

#---- write_group --------------------------------------------------------------------

# writes entries (if any) and sub-groups (if any) for a particular group
# -- call recursively to handle the sub-groups

sub write_group( $ $ ) {
my ($data_ref,$depth) = @_;
my ($group,$key,$entry_ref);
my ($pad) = $TAB x $depth;
my ($padplus) = $TAB x ($depth+1);

# for each entry, call write_entry and pass the hash reference to the entry data
foreach $key (sort keys %{$data_ref->{KEYS}}) {
write_entry( \%{$data_ref->{KEYS}{$key}}, $depth );
}

# for each group, print the group info, then call write_group and pass the hash reference to the group data
foreach $group (sort keys %{$data_ref->{GROUPS}}) {
print "$pad<group>\n";
print "$padplus<title>$group</title>\n";
print "$padplus<icon>0</icon>\n";
write_group( \%{$data_ref->{GROUPS}{$group}}, $depth+1 );
print "$pad</group>\n";
}
}

#---- write_keepassx -----------------------------------------------------------------

# write the main XML wrapper and call write_group for the base group hash

sub write_keepassx() {
my $group;

print "<!DOCTYPE KEEPASSX_DATABASE>\n<database>\n";
write_group( \%DATA, 1 );
print "</database>\n";
}

#==== main ============================================================================

# read the data from standard in

read_pwsafe();

# write the XML to standard out

write_keepassx();

exit(0);
bswarner
KPX user
 
Posts: 1
Joined: 30 Jul 2008, 00:25

PHP script to convert Password Gorilla text files to KeePass

Postby jsheth » 12 Oct 2008, 19:36

Hi,

I wrote this quick PHP script to convert Password Gorilla text files to KeePass (before I saw this post).

Password Gorilla is a TCL version of Password Safe (that runs on Mac OS X too, with Wish shell).
It doesn't handle sub-groups yet - it just imports them in a flat format (group.subgroup).
Also, it assumes the user is using a modified version of Password Gorilla that also outputs the URL field. I've included the TCL code for this in the comments below.

I hope this code comes in handy to others. Let me know if you have suggestions or feedback.
Code: Select all
<?php
/*
Utility to convert Password Gorilla (http://www.fpx.de/fp/Software/Gorilla/) files into KeePassX's (http://www.keepassx.org/) XML format.
Usage:
php psafe_convert.php /tmp/input_password_gorilla.txt /tmp/output_keepass.xml

TODO: prompt for new file if destination file exists.

Note: This program assumes that the imported text file was generated using a modified version of Password Gorilla,
that also includes the URL field after the title field.

To do this, open gorilla.tcl and add these lines ...
# URL (added by J.S. 10/12/2008)
if {[$::gorilla::db existsField $rn 13]} {
    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 13]
}

after these lines:
# Title
if {[$::gorilla::db existsField $rn 3]} {
    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 3]
}
puts -nonewline $txtFile $separator

Requirements: PHP 5

By Jayesh Sheth (psafe [[at]] jsheth07 [[dot]] com), 2008
*/
if( isset($argv[1])  && is_file($argv[1]) && isset($argv[2]) ){
    $passwordSafeFile = $argv[1];
    $lines = file($passwordSafeFile);
    $exportArr = array();
    foreach($lines as $line){
        list($recordId, $group, $title, $url, $username, $password, $comments) = explode(',' , $line);
        if(strlen($group) == 0){
            $group = 'ungrouped';
        }
        $exportArr[$group][] = array('username' => $username,
                                   'password' => $password,
                                   'title' => htmlentities($title),
                                   'url' => $url,
                                   'comments' => htmlentities($comments),
                                   );
    }
   
    $exportXML = '<!DOCTYPE KEEPASSX_DATABASE>
                  <database>';
    foreach($exportArr as $group => $records){
        $exportXML .= "\n<group>
                       <title>{$group}</title>
                        <icon>1</icon>";
         $now = date('Y-m-d') . 'T' . date('H:i:s');
         foreach($records as $record){ 
            $exportXML .= "
             \n<entry>
             <title>{$record['title']}</title>
             <username>{$record['username']}</username>
             <password>{$record['password']}</password>
             <url>{$record['url']}</url>
             <comment>{$record['']}</comment>
             <icon>0</icon>
             <creation>$now</creation>
             <lastaccess>$now</lastaccess>
             <lastmod>$now</lastmod>
             <expire>Never</expire>
            </entry>\n";
         }
           $exportXML .=  "</group>\n";
       
    }
   
    $exportXML .= '</database>';
    $keepassXMLFile = $argv[2];
    if( file_exists($keepassXMLFile) ){
        echo "Error: $keepassXMLFile exists\n";
    }
    else if( file_put_contents($keepassXMLFile, $exportXML) ){
        echo "All done! Your KeePass XML file has been written to $keepassXMLFile\n";
    }
    else{
        echo "Error - could not write to $keepassXMLFile\n";
    }
   
}
else{
    echo "Error. Usage: php psafe_convert.php /tmp/password_safe.txt /tmp/keepass.xml\n";
}

?>
[/code]
jsheth
KPX user
 
Posts: 2
Joined: 12 Oct 2008, 19:25

PHP script to convert Password Gorilla text files to KeePass

Postby jsheth » 13 Oct 2008, 02:37

Hi,

I am posting a small correction to my previously posted script, where comments were not imported correctly.
This line was changed:
Code: Select all
<comment>{$record['comments']}</comment>

Sorry about that.

Code: Select all
<?php
/*
Utility to convert Password Gorilla (http://www.fpx.de/fp/Software/Gorilla/) files into KeePassX's (http://www.keepassx.org/) XML format.
Usage:
php psafe_convert.php /tmp/input_password_gorilla.txt /tmp/output_keepass.xml

TODO: prompt for new file if destination file exists.

Note: This program assumes that the imported text file was generated using a modified version of Password Gorilla,
that also includes the URL field after the title field.

To do this, open gorilla.tcl and add these lines ...
# URL (added by J.S. 10/12/2008)
if {[$::gorilla::db existsField $rn 13]} {
    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 13]
}

after these lines:
# Title
if {[$::gorilla::db existsField $rn 3]} {
    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 3]
}
puts -nonewline $txtFile $separator

Requirements: PHP 5

By Jayesh Sheth (psafe [[at]] jsheth07 [[dot]] com), 2008
*/
if( isset($argv[1])  && is_file($argv[1]) && isset($argv[2]) ){
    $passwordSafeFile = $argv[1];
    $lines = file($passwordSafeFile);
    $exportArr = array();
    foreach($lines as $line){
        list($recordId, $group, $title, $url, $username, $password, $comments) = explode(',' , $line);
        if(strlen($group) == 0){
            $group = 'ungrouped';
        }
        $exportArr[$group][] = array('username' => $username,
                                   'password' => $password,
                                   'title' => htmlentities($title),
                                   'url' => $url,
                                   'comments' => htmlentities($comments),
                                   );
    }
   
    $exportXML = '<!DOCTYPE KEEPASSX_DATABASE>
                  <database>';
    foreach($exportArr as $group => $records){
        $exportXML .= "\n<group>
                       <title>{$group}</title>
                        <icon>1</icon>";
         $now = date('Y-m-d') . 'T' . date('H:i:s');
         foreach($records as $record){ 
            $exportXML .= "
             \n<entry>
             <title>{$record['title']}</title>
             <username>{$record['username']}</username>
             <password>{$record['password']}</password>
             <url>{$record['url']}</url>
             <comment>{$record['comments']}</comment>
             <icon>0</icon>
             <creation>$now</creation>
             <lastaccess>$now</lastaccess>
             <lastmod>$now</lastmod>
             <expire>Never</expire>
            </entry>\n";
         }
           $exportXML .=  "</group>\n";
       
    }
   
    $exportXML .= '</database>';
    $keepassXMLFile = $argv[2];
    if( file_exists($keepassXMLFile) ){
        echo "Error: $keepassXMLFile exists\n";
    }
    else if( file_put_contents($keepassXMLFile, $exportXML) ){
        echo "All done! Your KeePass XML file has been written to $keepassXMLFile\n";
    }
    else{
        echo "Error - could not write to $keepassXMLFile\n";
    }
   
}
else{
    echo "Error. Usage: php psafe_convert.php /tmp/password_safe.txt /tmp/keepass.xml\n";
}
?>
[/code]
jsheth
KPX user
 
Posts: 2
Joined: 12 Oct 2008, 19:25

Postby render » 21 Jan 2009, 16:55

If you have access to Windows, you could also use keepass 1.x and the pwsafedbimport plugin, to convert it without exporting any data to an unencrypted format
http://keepass.info/plugins.html#pwsafedbimport
I'm not related in any way to this plugin, just a happy user ;-)
render
KPX user
 
Posts: 1
Joined: 21 Jan 2009, 16:49
Location: Potsdam

Re: PasswordSafe to KeePassX Conversion Tool

Postby higbdesign » 27 Dec 2009, 13:50

How i use this script? (the script for conversion from passwordsafe to keepassx)
I've tried to use this script under apple script (and probably this script is not intended for it) but it tell me there is an error...
higbdesign
KPX user
 
Posts: 1
Joined: 27 Dec 2009, 13:23

Re: PasswordSafe to KeePassX Conversion Tool

Postby wcrighton » 14 Jan 2010, 14:38

Great Information!
I was able to use the second perl script to xfer _all_ of my password safe v3 (psafe3) data over to KeePassX.
I was unable to get the plug to work - I believe it only works with the 'KeePass' version - not KeePassX.
In order to get the password safe 'Notes' to xfer I had to modify the script to pull 'Notes' from value 17 (instead of 13).
wcrighton
KPX user
 
Posts: 2
Joined: 14 Jan 2010, 14:33

Re: PasswordSafe to KeePassX Conversion Tool

Postby wcrighton » 14 Jan 2010, 14:41

higbdesign wrote:How i use this script? (the script for conversion from passwordsafe to keepassx)
I've tried to use this script under apple script (and probably this script is not intended for it) but it tell me there is an error...


It is a perl script. so type 'perl <script name> <source file> > <destination file>'
You will have to chmod the script to be executable first.

In order to use the script you'll have to export your password safe database to a text file (use all the defaults), and then run the script giving as the one argument the exported file name.
Hope that helps. It's a big vague if you aren't familiar with perl/linux.
wcrighton
KPX user
 
Posts: 2
Joined: 14 Jan 2010, 14:33

Re: PasswordSafe to KeePassX Conversion Tool

Postby postalbunny » 29 Jan 2010, 23:25

Needed to modify to work...

From:

$comment = clean_text( $list[13] );
$comment =~ s/"//g;
$comment =~ s/\xbb/\n/g;

To :

$comment = clean_text( $list[15] );
$comment =~ s/"//g;
$comment =~ s/\xc2\xbb/\n/g;

To get comments to work with pwsafe 3.18… just in case you get stuck in that boat too.
postalbunny
KPX user
 
Posts: 1
Joined: 29 Jan 2010, 23:21

Re: PasswordSafe to KeePassX Conversion Tool

Postby eddified » 16 Feb 2011, 07:43

I ran the perl script and the output looks good, but when I try to import it into KeePassX, I get "Parsing Error: File is no valid KeePassX XML file."

Where is the keepassx xml format spec?
eddified
KPX user
 
Posts: 2
Joined: 16 Feb 2011, 07:41
Full Name: Eddie Bishop

Re: PasswordSafe to KeePassX Conversion Tool

Postby eddified » 16 Feb 2011, 08:04

OK, I just exported to KeePassXML so I could look at the format.

I had to use '16' instead of '13' to get the Notes to come over.

I also had to add
Code: Select all
<group>
  <title>Imported</title>
  <icon>0</icon>

after the <database> tag, and I had to add

Code: Select all
</group>

Near the end of the generated file (before the closing </database> tag).

I don't know Perl very well, so I didn't edit the script to this effect, instead I just manually edited the output from the script to make the import work.

In addition, I had to manually change the » character to a period (.) in <title> tags. (I actually think this was a problem caused by the password safe exporter, not the perl script)


Update: I am using PasswordSafe v3.21 on windows, and KeePassX 0.4.3 on Mac OS X.
eddified
KPX user
 
Posts: 2
Joined: 16 Feb 2011, 07:41
Full Name: Eddie Bishop


Return to Open Discussion

Who is online

Users browsing this forum: No registered users and 0 guests