Skip to content

Instantly share code, notes, and snippets.

@cyberfox
Created September 18, 2011 06:03
Show Gist options
  • Save cyberfox/1224792 to your computer and use it in GitHub Desktop.
Save cyberfox/1224792 to your computer and use it in GitHub Desktop.
Convert OS X Keychain exported entries into logins for 1Password import
#!/usr/bin/env ruby
#
# Usage:
# security dump-keychain -d login.keychain > keychain_logins.txt
# # Lots of clicking 'Always Allow', or just 'Allow', until it's done...
# curl -O curl -O https://raw.github.com/gist/1224792/06fff24412311714ad6534ab700a7d603c0a56c9/keychain.rb
# chmod a+x ./keychain.rb
# ./keychain.rb keychain_logins.txt | sort > logins.csv
#
# Then import logins.csv in 1Password using the format:
# Title, URL/Location, Username, Password
# Remember to check 'Fields are quoted', and the Delimiter character of 'Comma'.
class KeychainEntry
attr_accessor :fields
def initialize(keychain)
last_key = nil
@fields = {}
data = nil
aggregate = nil
lines = keychain.split("\n")
lines.each do |line|
# Everything after the 'data:' statement is data.
if data != nil
data << line
elsif aggregate != nil
if line[0] == 32
keyvalue = line.split('=', 2).collect { |kv| kv.strip }
aggregate[keyvalue.first] = keyvalue.last
else
@fields[last_key] = aggregate
aggregate = nil
end
end
if aggregate == nil
parts = line.split(':').collect { |piece| piece.strip }
if parts.length > 1
@fields[parts.first] = parts.last
else
last_key = parts.first
data = [] if parts.first == "data"
aggregate = {}
end
end
end
@fields["data"] = data if data
end
end
def q(string)
"\"#{string}\""
end
def process_entry(entry_string)
entry = KeychainEntry.new(entry_string)
if entry.fields['class'] == '"inet"' && entry.fields['attributes']['"atyp"<blob>'] == '"form"'
site = entry.fields['attributes']['"srvr"<blob>'].gsub!('"', '')
path = entry.fields['attributes']['"path"<blob>'].gsub!('"', '')
proto= entry.fields['attributes']['"ptcl"<uint32>'].gsub!('"', '')
user = entry.fields['attributes']['"acct"<blob>']
pass = entry.fields['data']
path = '' if path == '<NULL>'
url = "#{proto}://#{site}#{path}"
puts "#{q(site)}, #{q(url)}, #{user}, #{pass}"
end
end
accum = ''
ARGF.each_line do |line|
if line =~ /^keychain: /
unless accum.empty?
process_entry(accum)
accum = ''
end
end
accum += line
end
@pslambe
Copy link

pslambe commented Oct 2, 2013

This looks good, but what I want to do is extract just my Secure Notes from KeyChain. I have searched everywhere and this is the most promising.
But I am lost in the script.
I realise that Secure Notes are generic password items "genp" and that I need to get "svce" into the script, but I need some help with this.

@planart
Copy link

planart commented Nov 11, 2013

Doesn't work for Ruby 1.8.7, OSX 10.8.3, throws an error:

keychain.rb:59:in process_entry': undefined method[]' for nil:NilClass (NoMethodError)
from ./keychain3.rb:76
from ./keychain3.rb:73:in `each_line'
from ./keychain3.rb:73

@amadeusp
Copy link

I would also be very interested in a script that converts exported secure notes to a importable format...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment