Create a gist now

Instantly share code, notes, and snippets.

@rwest /README forked from symposion/README
Created Jan 9, 2012

Convert OS X Keychain exported entries into logins for 1Password import
These two files should help you to import passwords from mac OS X keychains to 1password.
Assumptions:
1) You have some experience with scripting/are a power-user. These scripts worked for me
but they haven't been extensively tested and if they don't work, you're on your own!
Please read this whole document before starting this process. If any of it seems
incomprehensible/frightening/over your head please do not use these scripts. You will
probably do something Very Bad and I wouldn't want that.
2) You have ruby 1.9.2 installed on your machine. This comes as standard with Lion, previous
versions of OS X may have earlier versions of ruby, which *may* work, but then again, they
may not :-) You can check by opening the terminal application and typing ruby -v.
NB. The script has now been modified to work with ruby 1.8.7 (which is actually standard with Lion.
3) *THIS IS IMPORTANT* None of your passwords, usernames or site names contains a comma. It's
highly unlikely that a site name will contain a comma, fairly unlikely that usernames will,
but eminently possible that your passwords might. If they do, this script *will not work*
as supplied. You can modify it to quote all the values (there's a function for this already
in the script) before it outputs them, but beware: if any of your passwords contains a "
character it will break if you do this. If you have both quotes and commas in your passwords,
well, damn, you're fresh out of luck. The best you can do is to find the passwords with commas
in and remove them manually from the exported keychain (I'll mention where to do this below)
Instructions:
0) Save keychain.rb and click_allow.scpt in your home directory.
1) Enable full GUI scripting by going to the Universal Access System Preference Pane
and checking "Enable access for assistive devices"
2) Open the Terminal application and run the following command:
security dump-keychain -d login.keychain > keychain.txt
(If you have multiple keychains you should repeat this whole process once from step 2 onwards for
each one. You will have to change 'login.keychain' to 'foo.keychain' or somesuch.)
3) When you run the above command, the system will ask for permission to use your keychain. If you
have a separate keychain password/have paranoid settings on your keychain, you may need to enter
a password now. Otherwise, you will be presented with a dialog box asking you whether you want to
allow permission to access the first item in your keychain. You will be asked this once for every
item in your keychain (zzz). This is where the other file comes in:
4) Find the click_allow.scpt in your home directory using Finder, double click it. It will open in
the AppleScript editor. Click the run button. If all is well, the script will click the "Allow"
button for you lots of times until all of your keychain entries have been exported. Shouldn't
take more than a few minutes even for hundreds of entries.
5) When that finishes, go back to the Terminal window and run the following command:
ruby keychain.rb keychain.txt | sort > keychain.csv
6) If all is well, that command will finish very quickly without any message. If it spouts an error
at you, sorry, you'll have to fix the script, something's broken. Otherwise you should try opening
up keychain.csv in your favourite text editor (TextEdit? <shiver>) to make sure it contains a list
of keychain entries. Now is the time to search for passwords containing a comma (you may need regular
expressions to do this if you have a lot of keychain entries, since it's a comma-separated file)
and delete them to stop them hosing the 1password import. You'll have to enter these manually, hopefully
it isn't too many.
7) Fire up 1password and choose File>Import. You want to import keychain.csv as a "CSV or Delimited Text"
file. The process is fairly self-explanatory, make sure you select "comma" as the delimiter at the
appropriate point. You will have to tell it which columns correspond to which fields (this is pretty
obvious) and you should check that there are exactly five columns. If you're seeing more than five
columns, one of your values contains a rogue comma and you need to fix it manually before you import the
file or it won't work. The 5th column is optional - it's the last modified date for the keychain entry;
unfortunately 1password won't let you import this as the "modified date" for the password but I put
it in a notes field just in case since I often find it helpful to know when a password was set.
8) IMPORTANT: You now have 2 files on your hard disk that contain unencrypted passwords. You need to delete
these securely if you are concerned about the possibility that someone might get your passwords. You have
two options. The easy option is to use Finder to move them to Trash, and then Secure Empty Trash. If you
are one of these funny people who likes to use their Trash Can as a temporary storage location and don't
want to empty it, you can go back to the terminal and issue rm keychain.csv keychain.txt, and then fire up Disk
Utility and use the "Erase Free Space" command on the relevant hard disk to securely blank all the free
space on your drive (this may take some time). NB: If you have an SSD drive in your computer there will be
no Secure Empty Trash (only plain Empty Trash) and there will be no "Erase Free Space" in Disk Utility.
This is because some SSDs delete things much more permanently than traditional hard disks by default, so
these commands are redundant. Simply emptying the trash/rm-ing the file from the terminal will suffice in
this case.
Acknowledgements: The original ruby script was written by Morgan Schweers of https://github.com/cyberfox. I've merely fixed bits that didn't work for me, and added the script to push the Allow button + this documentation.
tell application "System Events"
repeat while exists (processes where name is "SecurityAgent")
tell process "SecurityAgent"
click button "Allow" of group 1 of window 1
end tell
delay 0.2
end repeat
end tell
#!/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'.
require 'date'
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 || line[0] == " " )
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.join(" ") if data
end
end
def q(string)
"\"#{string}\""
end
def process_entry(entry_string)
entry = KeychainEntry.new(entry_string)
if entry.fields['class'] == '"inet"' && ['"form"', '"dflt"'].include?(entry.fields['attributes']['"atyp"<blob>'])
site = entry.fields['attributes']['"srvr"<blob>'].gsub!('"', '')
path = entry.fields['attributes']['"path"<blob>'].gsub!('"', '')
proto= entry.fields['attributes']['"ptcl"<uint32>'].gsub!('"', '')
proto.gsub!('htps', 'https');
user = entry.fields['attributes']['"acct"<blob>'].gsub!('"', '')
#user = entry.fields['attributes']['0x00000007 <blob>'].gsub!('"', '')
date_string = entry.fields['attributes']['"mdat"<timedate>'].gsub(/0x[^ ]+[ ]+/, '').gsub!('"', '')
date = DateTime.parse(date_string)
pass = entry.fields['data'][1..-2]
path = '' if path == '<NULL>'
url = "#{proto}://#{site}#{path}"
puts "#{site},#{url},#{user},#{pass},#{date}"
#puts "#{user}, #{pass}, #{date}"
end
end
accum = ''
ARGF.each_line do |line|
if line =~ /^keychain: /
unless accum.empty?
process_entry(accum)
accum = ''
end
end
accum += line
end
@symposion

Hmm, strange I must have got myself confused with RVM versions then. Looking again I see that you're right about 1.8.7. It would be nice if Apple occasionally shipped remotely recent versions of software with their OS, but hey ho. Thanks for fixing.

@eliotr
eliotr commented Dec 13, 2012

When I entered the terminal command in Step 5 above, I got the error below. Any suggestions? Thanks!
I looked thru my keychain.rb file, and all the "{" seemed paired with a "}". Using Ruby 1.8.7.

Eliots:~ ejr$ ruby keychain.rb keychain.txt | sort > keychain.csv
keychain.rb:1: syntax error, unexpected $undefined, expecting '}'
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
^

@Franue
Franue commented Feb 16, 2013

Works fine for standard login.keychain but gives an empty csv for manually built keychains, like here:

keychain: "/Users/franue/Library/Keychains/test.keychain"
class: "genp"
attributes:
0x00000007 ="http://www.apple.com/de"
0x00000008 =
"acct"="frank"
"cdat"=0x32303133303231363039303830355A00 "20130216090805Z\000"
"crtr"=
"cusi"=
"desc"=
"gena"=
"icmt"=
"invi"=
"mdat"=0x32303133303231363039303931365A00 "20130216090916Z\000"
"nega"=
"prot"=
"scrp"=
"svce"="www.apple.com/de"
"type"=
data:
"frank"

Does anybody can help? Thx

@planart
planart commented Nov 11, 2013

was done but need to modify the line 81 ARGF.each_line do | line |
to
ARGF.each_line('keychain: "/MYUSERSPATH/MYUSERNAME/Library/Keychains/login.keychain"') do | line |

THE /MYUSERSPATH/MYUSERNAME/Library/Keychains/login.keychain was write "as record separator" in keychain.txt

Ruby 1.8.7, OSX 10.8.3

@techstalks

this is awesome. at first i did not trust this, but when i applied this with little brave, i saw that this works like real awesome. i love this and love the coder of this. thanks sooooooooooooooo much.....

@rkh
rkh commented Dec 1, 2013

1password puts the plain text password in the notes after import, which is very very uncool. Any idea how I clean out all the notes?

@jmoretti
jmoretti commented Dec 3, 2013

@rkh, I was seeing the same thing to, but found this forum: http://discussions.agilebits.com/discussion/16427/how-do-i-import-my-keychain-passwords-into-1password-4-for-mac Basically, the importers for 1password 4 are not really complete. You need to download the trial of 1password 3 and use its importers, export to the native 1password file format, and then import to 1password 4. I was able to successfully import all my keychains using this method.

@techerausa

using ruby 2.0.0p247 on os x 10.9

when i run ruby keychain.rb keychain.txt | sort > keychain.csv the csv file is blank.

@franue mentioned this a long time ago. also my keychain.txt is from my login.keychain.

any ideas?

@macmsb
macmsb commented Feb 19, 2014

Just ran the script but couldn't see any keychain.txt being generated... anywhere. Any ideas?

btw, ran the first command "security dump-keychain -d login.keychain > keychain.txt" correctly, came up with all the security warnings, and ran "click_allow.scpt" which allowed all passwords...

@Ben-Oni
Ben-Oni commented Feb 22, 2014

No need to remove commas from passwords. Tabs work great as delimiters, and I've yet to see an URL, user name or password with a tab character in it. The fix is simple: In the script keychain.rb, in the line beginning with "puts", replace every comma (",") with "\t" (without the quotes). 1Password recognizes and imports tab-CSVs just fine, despite the "C" in CSV.

@RSully
RSully commented Mar 18, 2014

Hmm, I guess security doesn't treat iCloud keychain like a normal keychain. Has anyone successfully dumped theirs?

@seriema
seriema commented Apr 12, 2014

Line 66 caused a crash for me when proto was nil. A simple if-statement around it solved the problem. Check my fork.

But the click button script just said

error "System Events got an error: Can’t get window 1 of process \"SecurityAgent\". Invalid index." number -1719 from window 1 of process "SecurityAgent"
@nxg
nxg commented May 7, 2014

In your notes you suggest rm keychain.csv keychain.txt then Erase Free Space. You can avoid the latter with the command srm keychain.csv keychain.txt -- srm is a variant of the rm command which erases the contents of the file rather than simply unlinking it. See man srm for details.

@raamana
raamana commented Jul 23, 2014

For Yosemite Beta 4 ( and mountain lion also I think), you need to remove the "of group 1" in line 4 of click_allow.scpt to get it to work.

Thanks for the help.

@herr-lehmann

I was struggeling with commas in my passwords. To get rid of these I imported the csv file into Libre Office and checked where the timestamps did not align correctly - that was much easier for me than using regex ;)

@mackoj
mackoj commented Aug 17, 2014

Hi,

Thanks for the script.
I'm a n00b in AppleScript but I patched it to work in French. I hope it's help someone.

In order to make it work you have to replace this line from the click_allow.scpt

                click button "Allow" of group 1 of window 1

with this

            try
                click button "Allow" of group 1 of window 1
            on error
                click button "Autoriser" of group 1 of window 1
            end try

Cheers,

@amilor
amilor commented Aug 28, 2014

hi, everything worked great up to the point where I was to use the ruby script, the generated csv was empty (same as techerausa). I'm on osx mavericks which has ruby version 2.0.0p451, any idea how to get around this?

@jgrodziski

Hi,

Just test the script on Yosemite and had to change it as (remove of Group 1):

tell application "System Events"
    repeat while exists (processes where name is "SecurityAgent")
        tell process "SecurityAgent"
            click button "Allow" of window 1
        end tell
        delay 0.2
    end repeat
end tell
@christophervalles

@amilor, @techerausa it did came blank for me too. Turns out I store general passwords not associated with websites or things like that so the script ignores them. To fix it a new block must be added on the process_entry method. It should look like follows:

def process_entry(entry_string)
  entry = KeychainEntry.new(entry_string)


  if entry.fields['class'] == '"genp"'
    title = entry.fields['attributes']['0x00000007 <blob>'].gsub!('"', '')
    user = entry.fields['attributes']['"acct"<blob>'].gsub!('"', '')
    location = entry.fields['attributes']['"svce"<blob>'].gsub!('"', '')
    pass = entry.fields['data'][1..-2]
    puts "\"#{title}\",\"#{location}\",\"#{user}\",\"#{pass}\""
  end

  if entry.fields['class'] == '"inet"' && ['"form"', '"dflt"'].include?(entry.fields['attributes']['"atyp"<blob>'])
    site = entry.fields['attributes']['"srvr"<blob>'].gsub!('"', '')
    path = entry.fields['attributes']['"path"<blob>'].gsub!('"', '')
    proto= entry.fields['attributes']['"ptcl"<uint32>'].gsub!('"', '')
    proto.gsub!('htps', 'https');
    user = entry.fields['attributes']['"acct"<blob>'].gsub!('"', '')
    #user = entry.fields['attributes']['0x00000007 <blob>'].gsub!('"', '')
    date_string = entry.fields['attributes']['"mdat"<timedate>'].gsub(/0x[^ ]+[ ]+/, '').gsub!('"', '')
    date = DateTime.parse(date_string)
    pass = entry.fields['data'][1..-2]
    path = '' if path == '<NULL>'
    url = "#{proto}://#{site}#{path}"

    puts "#{site},#{url},#{user},#{pass},#{date}"
    #puts "#{user}, #{pass}, #{date}"
  end
end
@tinyapps

In addition to removing "of group 1" as raamana and jgrodziski kindly pointed out, Yosemite users will need to change step 1 of the instructions ("Enable access for assistive devices") to:

  1. System Preferences > Security & Privacy > Privacy > Accessibility
  2. Click the padlock icon > enter password
  3. Click the plus symbol
  4. Browse to Script Editor (/Applications/Utilities/Script Editor) and double click it
@MarcelWaldvogel

click_allow.scpt did not work for me on OSX Yosemite, claiming System Events got an error: Can’t get group 1 of window 1 of process "SecurityAgent". Invalid index. After some playing around, I got the following hack:

tell application "System Events"
    repeat while exists (processes where name is "SecurityAgent")
        tell process "SecurityAgent"
            keystroke " "
        end tell
        delay 1
    end repeat
end tell

After starting security, you need to run this script and then click on the dialog waiting for your Allow once. Don't be tempted to speed up the clicking; at delay 0.2, I had to force-power-off my Mac…

@echohn
echohn commented Sep 23, 2015

awesome !
@rwest I found a bug that it will not record the last data by running the ruby script . Because the last line of keychain.txt is not like /^keychain: / , so accum is not empty . the last assume cannot be running by the method process_entry .

You can insert process_entry(accum) after the last line of keychain.rb .

@hot-ham-water

Thanks all. In El Capitan, neither of these work:

tell application "System Events"
repeat while exists (processes where name is "SecurityAgent")
    tell process "SecurityAgent"
        click button "Allow" of window 1
    end tell
    delay 0.2
end repeat
end tell

(this ends in the same "System Events got an error: Can’t get group 1 of window 1 of process "SecurityAgent". Invalid index" type error after about 100 successful 'allow' events)

tell application "System Events"
repeat while exists (processes where name is "SecurityAgent")
    tell process "SecurityAgent"
        keystroke " "
    end tell
    delay 1
end repeat
end tell

(this basically freezes and starts a rhythmic ticking sound and doesn't do anything after I click the first 'alloe' button as Marcel instructed).

Does anyone have a good solution for this process in El Capitan? Am I doing something wrong? Thanks!

@DmytroSoroka

Same issue as described by hot-ham-water (weird name)

@roquie
roquie commented Nov 30, 2015

Bugfix for error (ruby 2.0):

keychain.rb:66:in `process_entry': undefined method `gsub!' for nil:NilClass (NoMethodError)
    from keychain.rb:84:in `block in <main>'
    from keychain.rb:81:in `each_line'
    from keychain.rb:81:in `each_line'
    from keychain.rb:81:in `<main>'

...and bugfix import csv to 1password 5.4 ver. (add double-quotes)

#!/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'.                                                                                                                                             
require 'date'

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 || line[0] == " " )
          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.join(" ") if data
  end
end

def q(string)
  "\"#{string}\""
end

def process_entry(entry_string)
  entry = KeychainEntry.new(entry_string)


  if entry.fields['class'] == '"inet"' && ['"form"', '"dflt"'].include?(entry.fields['attributes']['"atyp"<blob>'])
    site = entry.fields['attributes']['"srvr"<blob>'].to_s.gsub!('"', '')
    path = entry.fields['attributes']['"path"<blob>'].to_s.gsub!('"', '')
    proto= entry.fields['attributes']['"ptcl"<uint32>'].to_s.gsub!('"', '')
    proto.to_s.gsub!('htps', 'https');
    user = entry.fields['attributes']['"acct"<blob>'].to_s.gsub!('"', '')
    #user = entry.fields['attributes']['0x00000007 <blob>'].gsub!('"', '')
    date_string = entry.fields['attributes']['"mdat"<timedate>'].to_s.gsub(/0x[^ ]+[ ]+/, '').to_s.gsub!('"', '')
    date = DateTime.parse(date_string)
    pass = entry.fields['data'][1..-2]
    path = '' if path == '<NULL>'
    url = "#{proto}://#{site}#{path}"

    puts "\"#{site}\",\"#{url}\",\"#{user}\",\"#{pass}\",\"#{date}\""
    #puts "#{user}, #{pass}, #{date}"
  end
end

accum = ''
ARGF.each_line do |line|
  if line =~ /^keychain: /
    unless accum.empty?
      process_entry(accum)
      accum = ''
    end
  end
  accum += line
end
@lambchoplee

On El Capitan try this:

tell application "System Events"
repeat while exists (processes where name is "SecurityAgent")
tell process "SecurityAgent"
click button 3 of window 1
end tell
delay 1
end repeat
end tell

@landry314

when you run the command to dump, just run it with sudo and you don't have to type in a password more than twice... click_allow.scpt is elegant in a way but a waste of time. instead use:

sudo security dump-keychain -d login.keychain > keychain.txt

then type in your login password... simple.

@landry314

This is weird... If the card had a pencil icon in Apple Keychain Access, it does not get into the CSV. It doesn't matter if it says "application password" or "Internet password" or the format of the URL field.

Apple doesn't let you change which kind of entry it is. When you created the entry originally you either put a URL or a Name in there and apple picked the type of entry to make it even though they seem completely the same. What was the point? No idea.

But why does the script ignore the entries that were originally not made with URLs? I need to manually go through all those entries now and copy the data out... easier than figuring out what is wrong in the script. Maybe someone else would like to try.

@epu
epu commented Apr 29, 2016 edited

Thanks lambchoplee that super did it.

@David-C-Harris

Thanks to @RWest for original - very helpful.!!!

@Roquie - Am working with your update, thanks, but also am N00b in Applescript - have three keychains Login (98), iCloud (260), System (52) and running El Capitan 10.11.5. The Login dump is working fine but when I change to iCloud and System it dumps blank.

Do I have the sudo correct (thanks @landry314).?

  1. sudo security dump-keychain -d login.keychain > login.txt
  2. sudo security dump-keychain -d icloud.keychain > icloud.txt
  3. sudo security dump-keychain -d system.keychain > system.txt

I'm possibly getting confused in what to execute in the original eight steps. Can someone please confirm and/or update them to work with El Capitan if not already, and ability to also export iCloud or any other keychain to both 1Password and also CSV. I also couldn't find /system preferences/security & privacy/privacy "Enable access for assistive devices either, but maybe that's part of the issue, and then I couldn't get the System Events script to work either - possibly related.

And finally, I can't change password to blank without risking losing the keychain either as my OSX user login is sync'd with Apple ID, but at worst I'm still happy to click 'Allow' 400 times - surely it's better than default Apple method of typing credentials.

Appreciate some help please.

@rainbowdash12345
rainbowdash12345 commented Nov 11, 2016 edited

Thanks for this project!
I had to make one small mod to the Apple Script to get this beast to run on my computer (running MacOS 10.12.1).
Change: removed "of group 1" from the Apple Script.
Also, working in MS Excel, I was able to quickly find any passwords containing commas by looking for rows that did not contain a time stamp in column E.

@jclark5093

Having the same problem as David above, iCloud.keychain (where all the useful stuff is) dumps an empty file.

Also, the login.keychain creates a massive file for me (good), but when I run the ruby script, it only gives me a csv with one entry line (correctly formatted login and password for something, but it's the very last one in my keychain, nothing before it gets processed. Odd, because I would expect it to get to a certain one and then break, but to skip all of them and only process the final one? Seems odd... Any alternatives known for this?

btw, it has an @ symbol instead of a pencil ... symbol as the "type" so maybe that's why it worked? Landry314 mentioned this above as well...

El Capitan 10.11.6

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