Gelukkig ondersteunen steeds meer websites het gebruik van tweestapsverificatie. En vaak is dat (ook) met behulp van een TOTP-authenticator. Maar ieder voordeel heeft zijn nadeel en in dit geval betekent dit dat het aantal secrets dat ik heb steeds groter wordt en er steeds meer komt kijken bij het wisselen van app.

⚠️ LET OP: Onderstaand artikel gaat over het exporteren en importeren van vertrouwelijke gegevens. Ga met je TOTP-tokens om als met je wachtwoorden. Sla ze, ook al is het maar tijdelijk, altijd op een veilige locatie op. Je bent zelf verantwoordelijk voor je eigen beveiliging.

De laatste tijd ben ik een beetje aan het experimenteren geweest met verschillende authenticator-apps1 voor het beheer van mijn TOTP-secrets. Maar het lukte me niet om de codes simpel over te zetten van Proton Authenticator naar Bitwarden Authenticator. Proton maakt een keurig json-bestand, maar Bitwarden ondersteunt dat (nog) niet. Gelukkig ondersteunt Bitwarden onder andere het eenvoudige json-bestand van Raivo OTP.

proton-export.json:

{
    "version": 1,
    "entries": [
        {
            "id": "REDACTED_ID",
            "content": {
                "uri": "otpauth://totp/REDACTED_NAME?secret=REDACTED_SECRET&issuer=REDACTED_ISSUER&algorithm=SHA1&digits=6&period=30",
                "entry_type": "Totp",
                "name": "REDACTED_NAME"
            },
            "note": null
        }
   ]
}

raivo-export.json:

[
    {
        "tags": "",
        "account": "REDACTED_NAME",
        "secret": "REDACTED_SECRET",
        "category": "General",
        "timer": "30",
        "algorithm": "SHA1",
        "iconType": "raivo_repository",
        "iconValue": "REDACTED_ICON",
        "pinned": "false",
        "kind": "TOTP",
        "counter": "0",
        "digits": "6",
        "issuer": "REDACTED_ISSUER"
    }
]

Gemakzuchtig als ik ben, heb ik ChatGPT gevraagd om een Python script te maken die de vertaling maakt van het ene naar het andere formaat. En eigenlijk werkte dat direct. Hieronder het script dat ik heb gebruikt. Het is natuurlijk niet bomproef, maar voor een eenmalig script is dat natuurlijk ook niet nodig.

proton-to-raivo-totp.py:

import json
import sys
from urllib.parse import urlparse, parse_qs, unquote

def parse_otpauth(uri):
    parsed = urlparse(uri)
    label = unquote(parsed.path[1:])
    params = parse_qs(parsed.query)

    issuer = params.get("issuer", [""])[0]
    secret = params.get("secret", [""])[0]
    algorithm = params.get("algorithm", ["SHA1"])[0]
    digits = params.get("digits", ["6"])[0]
    period = params.get("period", ["30"])[0]

    if ":" in label:
        issuer_from_label, account = label.split(":", 1)
        if not issuer:
            issuer = issuer_from_label
    else:
        account = label

    return {
        "tags": "",
        "account": account,
        "secret": secret,
        "category": "General",
        "timer": str(period),
        "algorithm": algorithm,
        "iconType": "raivo_repository",
        "iconValue": issuer,
        "pinned": "false",
        "kind": "TOTP",
        "counter": "0",
        "digits": str(digits),
        "issuer": issuer
    }


def convert(proton_data):
    raivo_entries = []

    for entry in proton_data.get("entries", []):
        content = entry.get("content", {})
        uri = content.get("uri")

        if uri and uri.startswith("otpauth://"):
            parsed = parse_otpauth(uri)
            if parsed["secret"]:
                raivo_entries.append(parsed)

    return raivo_entries


def main(input_file, output_file):
    with open(input_file, "r", encoding="utf-8") as f:
        proton_data = json.load(f)

    raivo_data = convert(proton_data)

    with open(output_file, "w", encoding="utf-8") as f:
        json.dump(raivo_data, f, indent=4)

    print(f"Converted successfully: {output_file}")
    print(f"Total accounts: {len(raivo_data)}")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python proton_to_raivo.py proton.json raivo.json")
        sys.exit(1)

    main(sys.argv[1], sys.argv[2])

Plaats het script naast de proton export en roep het aan met:

python3 proton-to-raivo-totp.py proton-export.json raivo-import.json

Vervolgens is het nog een kleine moeite om het bestand raivo-import.json te importeren in je app.

⚠️ Nogmaals: als je bestanden met TOTP-secrets bewaart, doe het dan veilig!


  1. Uiteraard ga ik omwille van mijn operationele beveiliging niet in op mijn huidige opzet. ↩︎