Skip to content

Instantly share code, notes, and snippets.

@ollybh
Created July 18, 2021 14:45
Show Gist options
  • Save ollybh/7b1b508d717854341b7864d73aa50730 to your computer and use it in GitHub Desktop.
Save ollybh/7b1b508d717854341b7864d73aa50730 to your computer and use it in GitHub Desktop.
Script for transferring passwords from LastPass to Password Safe
#!/bin/sh
# Converts a CSV export of a LastPass [https://www.lastpass.com/] vault into a
# format that can be imported by Password Safe [https://pwsafe.org]. Uses
# csvkit [https://github.com/wireservice/csvkit] to manipulate the CSV data.
if [ $# -ne 2 ]; then
echo "Usage: $(basename $0) [INFILE] [OUTFILE]"
exit 1
fi
SRCFILE=$1
DSTFILE=$2
# The LastPass export has these fields:
# 1: url
# 2: username
# 3: password
# 4: totp
# 5: extra
# 6: name
# 7: grouping
# 8: fav
#
# The fields that the Password Safe plain text exporter/importer recognises are:
# 1: Group/Title
# 2: Username
# 3: Password
# 4: URL
# 5: AutoType
# 6: Created Time
# 7: Password Modified Time
# 8: Last Access Time
# 9: Password Expiry Date
# 10: Password Expiry Interval
# 11: Record Modified Time
# 12: Password Policy
# 13: Password Policy Name
# 14: History
# 15: Run Command
# 16: DCA
# 17: Shift+DCA
# 18: e-mail
# 19: Protected
# 20: Symbols
# 21: Notes
#
# This gives a LastPass to Password Safe mapping of:
# grouping + name → Group/Title
# username → Username
# password → Password
# url → URL
# extra → Notes
#
# In LastPass the group hierarchy uses backslash as a separator. Password Safe
# uses a period, which is also the separator between the group and title fields.
# LastPass uses line breaks for multiline notes, Password Safe uses double
# right quotation marks (»), which is also used to replace any periods in the
# group and title names.
#
# A 'Created Time' field is added to the output file with a value of 'now',
# which tells the importer to use the current date and time. An undocumented
# feature of the Password Safe plain text importer is that the Notes field must
# be the last one. If it isn't, everything later in the row is assumed to be
# part of the Notes and the importer falls over with errors from missing
# fields.
#
# LastPass secure notes could be handled better. These appear in the LastPass
# export with a blank password, a URL of http://sn and the note information
# stored as text in the 'extra' field. Password Safe refuses to import anything
# with a missing password, so this is set to a period to ensure that the note
# gets imported.
QUERY="
SELECT REPLACE(REPLACE(COALESCE(grouping, ''), '.', '»'), '\\', '.')
||
'.'
||
REPLACE(name, '.', '»') AS 'Group/Title',
COALESCE(username, '') AS 'Username',
COALESCE(password, '.') AS 'Password',
COALESCE(url, '') AS 'URL',
'now' AS 'Created Time',
REPLACE(COALESCE(extra, ''), CHAR(10), '»') AS 'Notes'
FROM $(basename ${SRCFILE%.*})
"
# Password Safe doesn't like missing Notes fields, they must be shown by an
# emptry string. This is done by telling csvformat to quote all non-numeric
# values. This causes the values in the header row to also be quoted, which
# results in a failed import, so sed is used to strip the quotes on the first
# line.
csvsql --query "${QUERY}" ${SRCFILE} \
| csvformat --out-tabs --out-quoting 2 \
| sed -e '1s/\"//g' \
> ${DSTFILE}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment