Skip to content

Instantly share code, notes, and snippets.

@flaksp
Last active September 26, 2023 16:13
  • Star 20 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Convert BitWarden JSON export file to Apple iCloud Keychain CSV import file saving TOTP and notes

BitWarden to Apple iCloud Keychain passwords converter

This Python scripts allows you to move your passwords from BitWarden to Apple iCloud.

You need to know:

  • It ignores secure notes, credit cards and other types that are not passwords.
  • It ignores BitWarden entries without usernames, passwords and URLs.
  • It also ignores URLs that do not start with http:// or https://.
  • It normalizes all TOTP tokens, e.g. wskg vtqa h5kl bhb4 v4v2 ybyo woc6 qme2 will be converted to otpauth://totp/example.com:dude@foo.bar?secret=WSKGVTQAH5KLBHB4V4V2YBYOWOC6QME2&issuer=example.com&algorithm=SHA1&digits=6&period=30.
  • It preserves notes attached to accounts.
  • It preserves all custom fields attached to accounts. They will be prepended to notes.
  • BitWarden export does not contain any information about attachments. You should care about them manually.

All ignored (filtered) BitWarden entries will be logged to the console during convertion process.

Getting started

Export your BitWarden passwords

You should export your BitWarden vault in JSON format (not encrypted). See "Export Vault Data". Exported file will be named like bitwarden_export_20220426113920.json, you should rename it to bitwarden.json.

Run script

Download Python file attached to the Gist. Place this file in the same directory with bitwarden.json file and run it:

python3 convert.py

It will generate icloud.csv file.

Import passwords to iCloud

To import passwords, you should use generated icloud.csv file. See "Import bookmarks, history, and passwords in Safari on Mac".

Bonus

Get a shortcut to quickly open Passwords on your Apple devices: https://www.reddit.com/r/shortcuts/comments/w1sa8w/updated_for_macos_13_ventura_and_ios_16_go/

import csv
import json
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import urlencode
from urllib.parse import parse_qs
json_file = open('bitwarden.json')
parsed_json = json.load(json_file)
print('Found {:d} accounts in BitWarden'.format(len(parsed_json['items'])))
def is_account_credentials(credentials_entry):
name = credentials_entry['name']
type = credentials_entry['type']
if type != 1:
print('Skipping "{:s}" because it has wrong type: {:d}'.format(name, type))
return False
if 'uris' not in credentials_entry['login']:
print('Skipping "{:s}" because it does not have website URLs'.format(name))
return False
if credentials_entry['login']['username'] is None:
print('Skipping "{:s}" because it missing username'.format(name))
return False
if credentials_entry['login']['password'] is None:
print('Skipping "{:s}" because it missing password'.format(name))
return False
return True
birwarden_accounts = filter(is_account_credentials, parsed_json['items'])
def format_totp_url(totp, hostname, username):
if totp.startswith('otpauth://'):
parsed_totp = urlparse(totp)
totp = parse_qs(parsed_totp.query)['secret'][0]
totp = totp.upper().replace(' ', '')
return urlunparse((
'otpauth',
'totp',
(hostname + ':' + username),
'',
urlencode({
'secret': totp,
'issuer': hostname,
'algorithm': 'SHA1',
'digits': '6',
'period': '30',
}),
''
))
def convert_bitwarden_credentials_to_icloud(credentials_entry):
icloud_credentials = []
for credentials_entry_uri_entry in credentials_entry['login']['uris']:
parsed_url = urlparse(credentials_entry_uri_entry['uri'])
if parsed_url.scheme != 'http' and parsed_url.scheme != 'https':
continue
notes = []
if 'fields' in credentials_entry:
notes = list(map(lambda field: "{:s}: {:s}".format(field['name'], field['value']), credentials_entry['fields']))
if credentials_entry['notes'] is not None:
notes.append(credentials_entry['notes'].strip())
hostname = urlparse(credentials_entry_uri_entry['uri']).hostname
username = credentials_entry['login']['username']
icloud_credentials.append([
hostname,
credentials_entry_uri_entry['uri'],
username,
credentials_entry['login']['password'],
"\n\n".join(notes),
'' if credentials_entry['login']['totp'] is None else format_totp_url(
credentials_entry['login']['totp'],
hostname,
username,
)
])
return icloud_credentials
icloud_accounts = []
for credentials_entry in birwarden_accounts:
icloud_accounts.extend(convert_bitwarden_credentials_to_icloud(credentials_entry))
print('Saving {:d} accounts to iCloud CSV'.format(len(icloud_accounts)))
with open('icloud.csv', 'w', encoding='UTF8', newline='') as csv_file:
writer = csv.writer(csv_file)
writer.writerow(['Title', 'URL', 'Username', 'Password', 'Notes', 'OTPAuth'])
writer.writerows(icloud_accounts)
@kraphtdiner
Copy link

I have the exact same error as skripter888. Do you know what is wrong? Thank you!

@sombuddhachatterjee
Copy link

Thanks!!!

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