Skip to content

Repository files navigation

protonpass-bitwarden-sync

Python scripts (standard library only, Python 3.9+) for cleaning up password vaults when migrating between Bitwarden and Proton Pass. The four tools share a small common.py helper module — keep it next to the scripts.

Script What it does
bw_protonpass_diff.py Diffs a Bitwarden export against a Proton Pass export and writes a Bitwarden-format JSON with only what's missing from (or newer than) Proton Pass — import it into Proton Pass without creating duplicates.
protonpass_bw_diff.py The same diff in the other direction: writes a Bitwarden-format JSON with only the Proton Pass items missing from (or newer than) Bitwarden — import it into Bitwarden without creating duplicates.
bw_dedupe.py Removes duplicates from a Bitwarden export and writes a clean Bitwarden-format JSON.
protonpass_dedupe.py Removes duplicates from a Proton Pass export and writes a clean import-ready zip.

Getting the exports

  • Bitwarden: Tools → Export vault → .json (unencrypted)
  • Proton Pass: Settings → Export → JSON (produces a zip; all scripts accept the zip directly, or the data.json inside it)

Usage

# What's in Bitwarden but not in Proton Pass?
python bw_protonpass_diff.py bitwarden.json "Proton Pass_export_*.zip" -o import_me.json
# -> import import_me.json in Proton Pass: Settings -> Import -> Bitwarden

# What's in Proton Pass but not in Bitwarden?
python protonpass_bw_diff.py "Proton Pass_export_*.zip" bitwarden.json -o import_me.json
# -> import import_me.json in Bitwarden: Tools -> Import data -> Bitwarden (.json)

# Deduplicate a Bitwarden vault (preview first)
python bw_dedupe.py bitwarden.json --dry-run
python bw_dedupe.py bitwarden.json -o bitwarden_clean.json
# -> wipe the Bitwarden vault, then import the clean file

# Deduplicate a Proton Pass vault (preview first)
python protonpass_dedupe.py "Proton Pass_export_*.zip" --dry-run
python protonpass_dedupe.py "Proton Pass_export_*.zip" -o protonpass_clean.zip
# -> wipe the Proton Pass vaults, then Settings -> Import -> Proton Pass

How duplicates are matched

Logins are grouped by (place, username) where place is:

  • the URL's host:port with www. stripped — the port matters so that different self-hosted services on the same machine don't collide
  • or an Android app package name (Bitwarden androidapp:// URIs, Proton Pass allowedApps)
  • or, when an item has no URL at all, its name — so entries that merely share an email address aren't treated as duplicates

Groups sharing any key are merged (union-find), so an item with several URLs can bridge two groups.

Within a duplicate group:

  • identical passwords → only the newest item is kept
  • different passwordsall are kept; the older ones get a [DUPLICATE? different passwords] name prefix so you can resolve them manually after import — no password is ever silently discarded
  • non-login items (notes, cards, identities) are deduplicated only on exactly identical content

Trashed items are dropped: something you deleted on purpose stays deleted. bw_protonpass_diff.py additionally refuses to re-import a Bitwarden login whose only match is in the Proton Pass trash (it lists them so you can double-check).

The diff scripts also skip notes and credit cards that already exist on the other side (notes match on name + note text, cards on name + card number), so re-running a diff after an import doesn't create duplicates. Other non-login items (identities, aliases, ...) can't be compared across formats and are always included for manual review.

protonpass_bw_diff.py converts Proton Pass items to Bitwarden's format: logins keep their URLs, TOTP, custom fields and Android app links (as androidapp:// URIs); credit cards are mapped to Bitwarden cards (card PINs go into the notes, which Bitwarden lacks a field for); notes stay notes; anything else (aliases, identities, ...) becomes a secure note tagged for manual review.

Safety notes

  • Exports contain all your passwords in plain text. Work in a private directory and securely delete every export and output file afterwards (shred -u on Linux).
  • The scripts never print passwords — reports show item names and dates only.
  • Always --dry-run first and skim the report.
  • Import into a new/empty vault and verify before deleting anything.
  • Passkeys never survive a JSON export/import — re-register them manually.
  • File attachments are not included in JSON exports.

About

Diff and deduplicate Bitwarden and Proton Pass vaults — migrate without duplicates

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages