#!/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"'] == '"form"' site = entry.fields['attributes']['"srvr"'].gsub!('"', '') path = entry.fields['attributes']['"path"'].gsub!('"', '') proto= entry.fields['attributes']['"ptcl"'].gsub!('"', '') user = entry.fields['attributes']['"acct"'] pass = entry.fields['data'] path = '' if path == '' 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