{"id":8437,"date":"2013-04-24T21:12:26","date_gmt":"2013-04-25T01:12:26","guid":{"rendered":"http:\/\/scruss.com\/blog\/?p=8437"},"modified":"2013-11-23T16:09:19","modified_gmt":"2013-11-23T21:09:19","slug":"mac-to-linux-1password-to-keepassx","status":"publish","type":"post","link":"https:\/\/scruss.com\/blog\/2013\/04\/24\/mac-to-linux-1password-to-keepassx\/","title":{"rendered":"Mac to Linux: 1Password to KeePassX"},"content":{"rendered":"<p>I have too many passwords to remember, so I&#8217;ve been using a password manager for years. First there was <a href=\"http:\/\/gnukeyring.sourceforge.net\/\">Keyring<\/a> for Palm OS, then <a href=\"https:\/\/agilebits.com\/onepassword\">1Password<\/a> on the Mac. 1Password&#8217;s a very polished commercial program, but it only has Mac and Windows desktop clients. Sadly, it had to go.<\/p>\n<p>Finding a replacement was tough. It needed to be free, and yet cross-platform. It needed to work on iOS and Android. It also needed to integrate with a cloud service like <a href=\"https:\/\/www.dropbox.com\/home\">Dropbox<\/a> so I could keep my passwords in sync. The only program that met all of these requirements was <a href=\"https:\/\/www.keepassx.org\/\">KeePassX<\/a>. I&#8217;ve stuck with the stable (v 0.4.3) branch rather than the flashy 2.0 version, as the older database format does all I need and is fully portable. <a href=\"https:\/\/itunes.apple.com\/ca\/app\/minikeepass-secure-password\/id451661808?mt=8\">MiniKeePass<\/a> on iOS and <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.android.keepass\">KeePassDroid<\/a> on Android look after my mobile needs. But first, I needed to get my password data out of 1Password.<\/p>\n<p>1Password offers two export formats: a delimited text format (which seemed to drop some of the more obscure fields), and the <em>1Password Interchange Format<\/em> (<wbr><\/wbr>1PIF). The latter is a\u00c2\u00a0<a href=\"http:\/\/www.json.org\/\">JSON<\/a>ish format (\u00e0\u00b2\u00a0_\u00e0\u00b2\u00a0) containing a dump of all of the internal data structures. There is, of course, no documentation for this file format, because no-one would <em>ever<\/em> move away from this lovely commercial software, no &#8230;<\/p>\n<p>So armed with my favourite <a href=\"http:\/\/www.perl.org\/\">swiss army chainsaw<\/a>, I set about picking the file apart. <a href=\"http:\/\/search.cpan.org\/~mlehmann\/JSON-XS-2.33\/XS.pm\">JSON::XS<\/a> and <a href=\"http:\/\/search.cpan.org\/~ovid\/Data-Dumper-Simple-0.11\/lib\/Data\/Dumper\/Simple.pm\">Data::Dumper::Simple<\/a> were invaluable for this process, and pretty soon I had all the fields picked apart that I cared about. I decided to write a converter that wrote KeePassX 1.<em>x<\/em> XML, since it was readily imported into KeePassX, would could then write a database readable by all of the <a href=\"http:\/\/keepass.info\/\">KeePass<\/a> variants.<\/p>\n<p>To run this converter you&#8217;ll need Perl, the <a href=\"http:\/\/search.cpan.org\/~mlehmann\/JSON-XS-2.33\/XS.pm\">JSON::XS<\/a> and <a href=\"http:\/\/search.cpan.org\/~ovid\/Data-Dumper-Simple-0.11\/lib\/Data\/Dumper\/Simple.pm\">Data::Dumper::Simple<\/a> modules, and if your Perl is older than about 5.12, the <a href=\"http:\/\/search.cpan.org\/~msergeant\/Time-Piece-1.20\/Piece.pm\">Time::Piece<\/a> module (it&#8217;s a core module for newer Perls, so you don&#8217;t have to install it). Here&#8217;s the code:<\/p>\n<pre class=\"brush: perl; title: ; notranslate\" title=\"\">\r\n#!\/usr\/bin\/perl -w\r\n# 1pw2kpxxml.pl - convert 1Password Exchange file to KeePassX XML\r\n# created by scruss on 02013\/04\/21\r\n\r\nuse strict;\r\nuse JSON::XS;\r\nuse HTML::Entities;\r\nuse Time::Piece;\r\n\r\n# print xml header\r\nprint &lt;&lt;HEADER;\r\n&lt;!DOCTYPE KEEPASSX_DATABASE&gt;\r\n&lt;database&gt;\r\n &lt;group&gt;\r\n  &lt;title&gt;General&lt;\/title&gt;\r\n  &lt;icon&gt;2&lt;\/icon&gt;\r\nHEADER\r\n\r\n##############################################################\r\n# Field Map\r\n#\r\n# 1Password\t\t\tKeePassX\r\n# ============================  ==============================\r\n# title        \t\t\ttitle\r\n# username\t\t\tusername\r\n# password\t\t\tpassword\r\n# location\t\t\turl\r\n# notesPlain\t\t\tcomment\r\n#    -\t\t\t\ticon\r\n# createdAt\t\t\tcreation\r\n#    -\t\t\t\tlastaccess\t(use updatedAt)\r\n# updatedAt\t\t\tlastmod\r\n#    -\t\t\t\texpire\t\t('Never')\r\n\r\n# 1PW exchange files are made of single lines of JSON (O_o)\r\n# interleaved with separators that start '**'\r\nwhile (&lt;&gt;) {\r\n    next if (\/^\\*\\*\/);    # skip separator\r\n    my $rec = decode_json($_);\r\n\r\n    # throw out records we don't want:\r\n    #  - 'trashed' entries\r\n    #  -  system.sync.Point entries\r\n    next if ( exists( $rec-&gt;{'trashed'} ) );\r\n    next if ( $rec-&gt;{'typeName'} eq 'system.sync.Point' );\r\n\r\n    print '  &lt;entry&gt;', &quot;\\n&quot;;    # begin entry\r\n\r\n    ################\r\n    # title field\r\n    print '   &lt;title&gt;', xq( $rec-&gt;{'title'} ), '&lt;\/title&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # username field - can be in one of two places\r\n    my $username = '';\r\n\r\n    # 1. check secureContents as array\r\n    foreach ( @{ $rec-&gt;{'secureContents'}-&gt;{'fields'} } ) {\r\n        if (\r\n            (\r\n                exists( $_-&gt;{'designation'} )\r\n                &amp;&amp; ( $_-&gt;{'designation'} eq 'username' )\r\n            )\r\n          )\r\n        {\r\n            $username = $_-&gt;{'value'};\r\n        }\r\n    }\r\n\r\n    # 2.  check secureContents as scalar\r\n    if ( $username eq '' ) {\r\n        $username = $rec-&gt;{'secureContents'}-&gt;{'username'}\r\n          if ( exists( $rec-&gt;{'secureContents'}-&gt;{'username'} ) );\r\n    }\r\n\r\n    print '   &lt;username&gt;', xq($username), '&lt;\/username&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # password field - as username\r\n    my $password = '';\r\n\r\n    # 1. check secureContents as array\r\n    foreach ( @{ $rec-&gt;{'secureContents'}-&gt;{'fields'} } ) {\r\n        if (\r\n            (\r\n                exists( $_-&gt;{'designation'} )\r\n                &amp;&amp; ( $_-&gt;{'designation'} eq 'password' )\r\n            )\r\n          )\r\n        {\r\n            $password = $_-&gt;{'value'};\r\n        }\r\n    }\r\n\r\n    # 2.  check secureContents as scalar\r\n    if ( $password eq '' ) {\r\n        $password = $rec-&gt;{'secureContents'}-&gt;{'password'}\r\n          if ( exists( $rec-&gt;{'secureContents'}-&gt;{'password'} ) );\r\n    }\r\n\r\n    print '   &lt;password&gt;', xq($password), '&lt;\/password&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # url field\r\n    print '   &lt;url&gt;', xq( $rec-&gt;{'location'} ), '&lt;\/url&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # comment field\r\n    my $comment = '';\r\n    $comment = $rec-&gt;{'secureContents'}-&gt;{'notesPlain'}\r\n      if ( exists( $rec-&gt;{'secureContents'}-&gt;{'notesPlain'} ) );\r\n    $comment = xq($comment);    # pre-quote\r\n    $comment =~ s,\\\\n,&lt;br\/&gt;,g;  # replace escaped NL with HTML\r\n    $comment =~ s,\\n,&lt;br\/&gt;,mg;  # replace NL with HTML\r\n    print '   &lt;comment&gt;', $comment, '&lt;\/comment&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # icon field (placeholder)\r\n    print '   &lt;icon&gt;2&lt;\/icon&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # creation field\r\n    my $creation = localtime( $rec-&gt;{'createdAt'} );\r\n    print '   &lt;creation&gt;', $creation-&gt;datetime, '&lt;\/creation&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # lastaccess field\r\n    my $lastaccess = localtime( $rec-&gt;{'updatedAt'} );\r\n    print '   &lt;lastaccess&gt;', $lastaccess-&gt;datetime, '&lt;\/lastaccess&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # lastmod field (= lastaccess)\r\n    print '   &lt;lastmod&gt;', $lastaccess-&gt;datetime, '&lt;\/lastmod&gt;', &quot;\\n&quot;;\r\n\r\n    ################\r\n    # expire field (placeholder)\r\n    print '   &lt;expire&gt;Never&lt;\/expire&gt;', &quot;\\n&quot;;\r\n\r\n    print '  &lt;\/entry&gt;', &quot;\\n&quot;;    # end entry\r\n}\r\n\r\n# print xml footer\r\nprint &lt;&lt;FOOTER;\r\n &lt;\/group&gt;\r\n&lt;\/database&gt;\r\nFOOTER\r\n\r\nexit;\r\n\r\nsub xq {                         # encode string for XML\r\n    $_ = shift;\r\n    return encode_entities( $_, q\/&lt;&gt;&amp;&quot;'\/ );\r\n}\r\n<\/pre>\n<p>To run it,<\/p>\n<pre>.\/1pw2kpxxml.pl data.1pif &gt; data.xml<\/pre>\n<p>You can then import data.xml into KeePassX.<\/p>\n<p>Please be careful to delete the 1PIF file and the data.xml once you&#8217;ve finished the export\/import. These files contain all of your passwords in plain text; if they fell into the wrong hands, it would be a disaster for your online identity. Be careful that none of these files accidentally slip onto backups, too. Also note that, while I think I&#8217;m quite a trustworthy bloke, to you, I&#8217;m <em><strong>Some Random Guy On The Internet<\/strong><\/em>. Check this code accordingly; I don&#8217;t warrant it for anything save for looking like line noise.<\/p>\n<p>Now on github: <a href=\"https:\/\/github.com\/scruss\/1pw2kpxxml\">scruss \/ 1pw2kpxxml<\/a>, or download: <a href=\"http:\/\/scruss.com\/wordpress\/wp-content\/uploads\/2013\/04\/1pw2kpxxml.zip\">1pw2kpxxml.zip<\/a> (gpg signature: <a href=\"http:\/\/scruss.com\/wordpress\/wp-content\/uploads\/2013\/04\/1pw2kpxxml.zip.sig_.txt\">1pw2kpxxml.zip.sig<\/a>)<\/p>\n<p>SHA1 Checksums:<\/p>\n<ul>\n<li>3c25eb72b2cfe3034ebc2d251869d5333db74592 \u00e2\u20ac\u201d 1pw2kpxxml.pl<\/li>\n<li>99b7705ff30a2b157be3cfd29bb1d4f137920c25 \u00e2\u20ac\u201d readme.txt<\/li>\n<li>de4a51fbe0dd6371b8d68674f71311a67da76812 \u00e2\u20ac\u201d 1pw2kpxxml.zip<\/li>\n<li>f6bd12e33b927bff6999e9e80506aef53e6a08fa \u00e2\u20ac\u201d 1pw2kpxxml.zip.sig.txt<\/li>\n<\/ul>\n<p>The converter has some limitations:<\/p>\n<ul>\n<li>All attached files in the database are lost.<\/li>\n<li>All entries are stored under the same folder, with the same icon.<\/li>\n<li>It has not been widely tested, and as I&#8217;m satisfied with its conversion, it will not be developed further.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I have too many passwords to remember, so I&#8217;ve been using a password manager for years. First there was Keyring for Palm OS, then 1Password on the Mac. 1Password&#8217;s a very polished commercial program, but it only has Mac and Windows desktop clients. Sadly, it had to go. Finding a replacement was tough. It needed [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[7],"tags":[2659,164,2661,2660,270,269,1662,187,1287],"class_list":["post-8437","post","type-post","status-publish","format-standard","hentry","category-computers-suck","tag-1password","tag-export","tag-json","tag-keepassx","tag-linux","tag-mac","tag-password","tag-perl","tag-xml"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pQNZZ-2c5","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/8437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/comments?post=8437"}],"version-history":[{"count":14,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/8437\/revisions"}],"predecessor-version":[{"id":10164,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/8437\/revisions\/10164"}],"wp:attachment":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/media?parent=8437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/categories?post=8437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/tags?post=8437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}