Migrate a Proton Pass export into Bitwarden, including passkeys that stock importers drop. A verify-before-write Rust CLI.
Find a file
2026-05-31 21:48:51 +01:00
src Map Proton item types in direct conversion 2026-05-31 21:17:28 +01:00
tests Implement Rust passkey migration tool 2026-05-31 17:43:04 +01:00
.gitignore Implement Rust passkey migration tool 2026-05-31 17:43:04 +01:00
Cargo.lock Rename installed binary to import_passkeys_p2b 2026-05-31 20:34:48 +01:00
Cargo.toml Add README, BSD 2-Clause license, and crate license metadata 2026-05-31 21:46:16 +01:00
LICENSE Add README, BSD 2-Clause license, and crate license metadata 2026-05-31 21:46:16 +01:00
README.md chore: Correct typos on the README 2026-05-31 21:48:51 +01:00

import_passkeys_p2b

Migrate your Proton Pass vault — including passkeys — into Bitwarden, safely and verifiably.

Made with Rust Edition 2021 Tests Version License: BSD-2-Clause Status

Bitwarden's importers can ingest a Proton Pass JSON export, but they silently drop passkeys — Proton stores them in a format Bitwarden's import path doesn't understand. This tool decodes Proton's passkey key material, re-encodes it into Bitwarden's FIDO2 credential schema, and produces a Bitwarden-ready JSON file. It can either build a complete vault from scratch or graft passkeys onto an export you already trust.

Every write is checked by an independent verification pass before it hits disk, and a detailed report tells you exactly what happened to each passkey.


Table of Contents


Features

  • Passkey migration — decodes Proton Pass passkeys (ES256 / P-256) and re-encodes them as Bitwarden FIDO2 credentials, the one thing stock importers throw away.
  • Full vault conversion — logins, secure notes, credit cards, identities and custom items are mapped to their native Bitwarden types; Proton vaults become Bitwarden folders.
  • Graft mode — attach passkeys to an existing Bitwarden export, matching them to the right login item by relying-party ID and username.
  • Detailed reporting — a per-passkey report (plain-text or JSON) records every attach, duplicate, skip and failure with a reason.
  • Independent verification — generated and patched vaults are re-validated before they're written; the tool refuses to emit a file that doesn't match its own intent.
  • Dry-run mode — preview every action without writing the output vault.
  • Strict mode — exit non-zero if any passkey needs manual review, ideal for scripting and CI.
  • Single static binary — no runtime dependencies, pinned crate versions for reproducible builds.

How it works

                ┌───────────────────────────┐
 Proton Pass    │  import_passkeys_p2b      │   Bitwarden JSON
 JSON export ──▶│  ├─ decode passkeys       │──▶ (importable)
                │  ├─ map item types        │
 (optional)     │  ├─ attach / duplicate    │   report.txt / .json
 Bitwarden  ───▶│  └─ verify before write   │──▶ (what happened)
 export         └───────────────────────────┘

Two modes are available:

Mode When to use What it produces
Direct You want Bitwarden to receive everything from Proton. A complete Bitwarden vault built from the Proton export.
Graft You already imported into Bitwarden and only need the missing passkeys. A copy of your Bitwarden export with passkeys attached to matching items.

Installation

Requires a Rust toolchain (stable).

# Build a release binary
cargo build --release
# ./target/release/import_passkeys_p2b

# Or install it onto your PATH
cargo install --path .

Usage

Convert a Proton Pass export into a ready-to-import Bitwarden vault. Omit --bitwarden to select direct mode.

import_passkeys_p2b \
  --proton proton_export.json \
  --out bitwarden_vault.json \
  --report report.txt

Then import bitwarden_vault.json in Bitwarden via Tools → Import data → Bitwarden (json).

Graft mode

Already have a Bitwarden export and just want the passkeys added? Pass --bitwarden and the tool will patch a copy of it, matching each passkey to the right login item.

import_passkeys_p2b \
  --proton proton_export.json \
  --bitwarden existing_bitwarden_export.json \
  --out bitwarden_with_passkeys.json \
  --report report.json

The Bitwarden input must be an unencrypted JSON export containing an items array.

Dry runs

See exactly what would happen without writing the output vault. The report is still generated.

import_passkeys_p2b --proton proton_export.json --dry-run --report preview.txt

For automation, combine --strict so the process exits non-zero when any passkey needs manual attention:

import_passkeys_p2b --proton proton_export.json --strict || echo "Manual review required"

CLI reference

Flag Default Description
--proton <PROTON_JSON> (required) Path to the Proton Pass JSON export.
--bitwarden <BITWARDEN_JSON> (none) Existing Bitwarden export to patch. Omit for direct conversion.
--out <OUTPUT_JSON> bitwarden_with_passkeys.json Where to write the resulting Bitwarden vault.
--report <REPORT> report.txt Report path. A .json extension emits JSON; anything else emits plain text.
--dry-run off Compute and report all actions but do not write the output vault.
--strict off Exit non-zero if any passkey could not be migrated cleanly.
--no-create-duplicates off Don't create duplicate items for extra passkeys on a single login.
--replace-existing-passkeys off (Graft mode) overwrite a passkey already present on a matched item.

Exit codes: 0 success · 2 invalid/malformed input · 3 strict-mode failures · 1 other errors.

The migration report

Every run writes a report describing what happened to each passkey. Actions include ATTACH, CREATE_DUPLICATE, REPLACE_EXISTING_PASSKEY, their DRY_RUN_* variants, and review states such as UNMATCHED, AMBIGUOUS, UNSUPPORTED, CONFLICTED and DECODE_FAILURE.

Plain-text example:

Passkey migration report

Total passkeys: 3
Strict failures: 1
Actions:
  ATTACH: 2
  UNMATCHED: 1

ATTACH  "GitHub" -> github.com (octocat) -> Bitwarden item "GitHub"
ATTACH  "Google" -> google.com (you@example.com): attached passkey to login item
UNMATCHED  "Old Service" -> oldservice.io (legacy): no Bitwarden login matched this relying party

Use a .json report path to get the same data as structured JSON for further processing.

What gets converted

In direct mode, Proton item types are mapped to their Bitwarden equivalents:

Proton type Bitwarden type
login Login (with URIs, TOTP and passkeys)
note Secure note
creditCard Card (cardholder, number, expiry, CVV, PIN)
identity Identity (name, address, contact, work details)
custom Secure note with custom fields preserved
anything else Secure note containing the original Proton JSON (nothing is lost)

Proton vaults become Bitwarden folders, custom/extra fields are preserved (hidden fields stay hidden), and pinned items are marked as favorites. When a single login carries more than one passkey, additional ones are split into clearly-named duplicate items (disable with --no-create-duplicates).

Safety guarantees

  • Verify-before-write: generated FIDO2 credentials are re-parsed and the P-256 private key is round-tripped; in graft mode the patched vault is diffed against the original to confirm only the intended items changed.
  • Non-destructive: your input files are never modified. Output is always written to a separate file.
  • No silent failures: anything that can't be migrated cleanly is surfaced in the report (and, under --strict, in the exit code) rather than being dropped.
  • Encrypted exports rejected: the tool refuses encrypted Bitwarden input so it never produces a half-decoded vault.

Testing

cargo test

The suite covers item-type mapping, passkey decoding/encoding and the verification logic (21 tests).

Development

cargo build          # debug build
cargo test           # run the test suite
cargo clippy         # lint
cargo fmt            # format

The project is a small, dependency-light Rust crate. Source layout:

File Responsibility
src/main.rs CLI parsing and orchestration of the two modes.
src/direct.rs Build a full Bitwarden vault from a Proton export.
src/proton.rs Parse Proton items and decode passkeys.
src/bitwarden.rs Match and graft passkeys onto an existing Bitwarden export.
src/crypto.rs Base64 / P-256 key-material helpers.
src/verify.rs Independent verification of generated and patched vaults.
src/report.rs Report and error types.

License

Released under the BSD 2-Clause License. © 2026 Atri Hegde.