Permalink
Browse files

Updated

  • Loading branch information...
0 parents commit dca0da995f19e20563f438fed1b95398bffa0004 @anirudhjoshi committed Aug 9, 2012
Showing with 254 additions and 0 deletions.
  1. +59 −0 README.pod
  2. +160 −0 lastpass2keepass.py
  3. +35 −0 test_generator.py
@@ -0,0 +1,59 @@
+=head1 DESCRIPTION
+
+Allows you to convert the LastPass export to a KeePass XML import.
+
+=head2 REQUIRES
+
+=over
+
+=item * Python 2.6 (sys, csv, time, datetime, itertools, re, operator, xml)
+
+=back
+
+=head2 SUPPORTS
+
+=over
+
+=item * KeePassXML
+
+=back
+
+=head2 USAGE
+
+ python lastpass2keepass.py exportedTextFile
+
+Then import the "exportedTextFile.export.xml" into KeePassx via:
+
+ File --> Import from... --> KeePassX XML (*.xml)
+
+=head2 TESTS/DEMO
+
+ python test_generator.py
+ python lastpass2keepass.py test_passwords.txt
+
+Then import the "test_passwords.txt.export.xml" into KeePassx via:
+
+ File --> Import from... --> KeePassX XML (*.xml)
+
+=head2 UTF-8
+
+This is UTF-8 compliant on *nix systems, with Python 2.6.
+
+=head1 ACKNOWLEDGEMENTS
+
+I<Python XML processing with lxml>, I<John W. Shipman>, L<http://infohost.nmt.edu/tcc/help/pubs/pylxml/>.
+I<ElementTree Overview>, I<Fredrik Lundh>, L<http://effbot.org/zone/element-index.htm>.
+
+=head1 COPYRIGHT
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see L<http://www.gnu.org/licenses/>.
+
+=head1 WARRANTY
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
@@ -0,0 +1,160 @@
+# lastpass2keepass
+# Supports:
+# Keepass XML - keepassxml
+# USAGE: python lastpass2keepass.py exportedTextFile
+# The LastPass Export format;
+# url,username,password,1extra,name,grouping(\ delimited),last_touch,launch_count,fav
+
+import sys, csv, time, datetime, itertools, re, operator # Toolkit
+import xml.etree.ElementTree as ET # Saves data, easier to type
+
+# Strings
+
+fileError = "You either need more permissions or the file does not exist."
+lineBreak = "____________________________________________________________\n"
+
+def formattedPrint(string):
+ print lineBreak
+ print string
+ print lineBreak
+
+# Files
+# Check for existence/read/write.
+
+try:
+ inputFile = sys.argv[1]
+except:
+ formattedPrint("USAGE: python lastpass2keepass.py exportedTextFile")
+ sys.exit()
+
+try:
+ f = open(inputFile)
+except IOError:
+ formattedPrint("Cannot read file: '%s' Error: '%s'" % (inputFile, fileError) )
+ sys.exit()
+
+# Create XML file.
+outputFile = inputFile + ".export.xml"
+
+try:
+ open(outputFile, "w").close() # Clean.
+ w = open(outputFile, "a")
+except IOError:
+ formattedPrint("Cannot write to disk... exiting. Error: '%s'" % (fileError) )
+ sys.exit()
+
+# Parser
+# Parse w/ delimter being comma, and entries separted by newlines
+
+h = re.compile('^http') # Fix multi-line lastpass problems
+q = re.compile(',\d\n')
+
+for line in f.readlines():
+
+ if h.match( line ):
+ w.write( "\n" + line.strip() ) # Each new line is based on this
+ elif q.search( line ):
+ w.write( line.strip() ) # Remove end line
+ else:
+ w.write( line.replace( '\n', '|\t|' ) ) # Place holder for new lines in extra stuff
+
+f.close() # Close the read file.
+
+w.close() # reuse same file - stringIO isn't working
+
+w = open(outputFile, "r") # open for reading - windows problems with reader on stringIO
+
+reader = csv.reader( w, delimiter=',', quotechar='"' ) # use quotechar to fix parsing
+
+# Create a list of the entries, allow us to manipulate it.
+# Can't be done with reader object.
+
+allEntries = []
+
+for x in reader:
+ allEntries.append(x)
+
+w.close() # reader appears to be lazily evaluated leave - close w here
+
+w = open(outputFile, "w") # clean the file - prepare for xml tree write
+
+allEntries.pop(0) # Remove LP format string.
+
+# Keepass XML generator
+
+# Add doctype to head, clear file.
+
+w.write("<!DOCTYPE KEEPASSX_DATABASE>")
+
+# Generate Creation date
+# Form current time expression.
+
+now = datetime.datetime.now()
+formattedNow = now.strftime("%Y-%m-%dT%H:%M")
+
+# Initialize tree
+# build a tree structure
+
+page = ET.Element('database')
+doc = ET.ElementTree(page)
+
+# A dictionary, organising the categories.
+
+resultant = {}
+
+# Parses allEntries into a resultant dict.
+
+for entry in allEntries:
+ resultant.setdefault( entry[5], [] ).append( entry )
+
+sorted_resultant = sorted(resultant.iteritems(), key=operator.itemgetter(1)) # sort for xml tree
+
+# Sort by categories.
+
+# Initilize and loop through all entries
+
+for categoryEntries in sorted_resultant:
+
+ # Place hold sorted data
+
+ category = categoryEntries[0]
+ entries = categoryEntries[1]
+
+ # Create head of group elements
+
+ headElement = ET.SubElement(page, "group")
+ ET.SubElement(headElement, "title").text = str(category).decode("utf-8")
+ ET.SubElement(headElement, "icon").text = "0" # Lastpass does not retain icons.
+
+ for entry in entries:
+
+ # entryElement information
+
+ # Each entryElement
+
+ entryElement = ET.SubElement(headElement, "entry")
+
+ # entryElement tree
+
+ ET.SubElement(entryElement, 'title').text = str(entry[4]).decode("utf-8") # Use decode for windows el appending errors
+ ET.SubElement(entryElement, 'username').text = str(entry[1]).decode("utf-8")
+ ET.SubElement(entryElement, 'password').text = str(entry[2]).decode("utf-8")
+ ET.SubElement(entryElement, 'url').text = str(entry[0]).decode("utf-8")
+ ET.SubElement(entryElement, 'comment').text = str(entry[3]).replace( '|\t|', '\n').strip('"').decode("utf-8") # fix place holder
+ ET.SubElement(entryElement, 'icon').text = "0"
+ ET.SubElement(entryElement, 'creation').text = formattedNow
+ ET.SubElement(entryElement, 'lastaccess').text = str(entry[6]).decode("utf-8")
+ ET.SubElement(entryElement, 'lastmod').text = formattedNow
+ ET.SubElement(entryElement, 'expire').text = "Never"
+
+# Write out tree
+# wrap it in an ElementTree instance, and save as XML
+
+doc.write(w)
+
+w.close()
+
+print lineBreak
+print "\n'%s' has been succesfully converted to the KeePassXML format." %(inputFile)
+print "Converted data can be found in the '%s' file.\n" %(outputFile)
+print lineBreak
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+# url,username,password,1extra,name,grouping(\ delimited),last_touch,launch_count,fav
+import random, datetime, unicodedata, string
+
+now = datetime.datetime.now()
+formattedNow = now.strftime("%Y-%m-%dT%H:%M")
+
+appendToFile = open("test_passwords.txt", "w" ).close()
+appendToFile = open("test_passwords.txt", "a" )
+
+unicode_glyphs = ''.join(
+ unichr(char)
+ for char in xrange(65533) # 0x10ffff + 1
+ if unicodedata.category(unichr(char))[0] in ('LMNPSZ')
+ )
+
+# Generator
+
+for i in range(1, 250):
+
+ url = "http://www." + "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(4)]) + ".com"
+ username = "username_" + "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(4)] )
+ password = "password_" + "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(15)] )
+ extra = "extra_" + "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(4)] )
+ name = "WEBSITE_NAME_" + "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(4)] )
+ grouping = "All\Main"
+ last_touch = formattedNow
+ launch_count = str(i)
+ fav = "".join( [random.choice(unicode_glyphs).encode('utf-8') for i in xrange(1)] )
+
+ entry = [url, username, password, extra, name, grouping, last_touch, launch_count, fav]
+
+ appendToFile.write(",".join(entry)+'\n')
+
+appendToFile.close()

0 comments on commit dca0da9

Please sign in to comment.