Files
otp-import/README.md
Amal Graafstra a8a405600c Add --import-qr to run the QR->CSV step before importing
Wrap the QR decode/append step behind --import-qr on the importer so a
single command runs the whole pipeline: decode QR images, append only
new credentials to the CSV (deduped on otpauth://, existing entries
silently skipped), then validate and import. QR folder defaults to the
CSV's folder; override with --qr-images. Refactor qr_to_sheet into
reusable collect_candidates / apply_new_candidates / run_qr_import.
2026-07-08 15:31:03 -07:00

106 lines
4.4 KiB
Markdown

# otp-import
Turn saved OTP QR-code screenshots into credentials on a VivoKey Apex OTP
(ykneo-oath) applet, via a canonical CSV.
## Pipeline
1. **QR -> CSV** (`qr_to_sheet.py`): decode QR screenshots and append new
credentials to `otp-keys.csv`, deduplicated on the `otpauth://` data.
2. **Edit labels (optional)**: adjust the `issuer` / `account` columns to taste.
Never edit the `otpauth_uri` column - it is the immutable source of truth.
3. **CSV -> applet** (`apex_otp_import.py`): write to the Apex, validating for
anything you may have broken (duplicate names, invalid characters, overlength).
The CSV has four columns: `issuer`, `account` (editable labels), `otpauth_uri`
(do not edit), and `source_file`. The secret and other crypto parameters live
inside `otpauth_uri` and are always taken from there.
```
# Populate/refresh the CSV from a folder of QR screenshots
python qr_to_sheet.py --images /path/to/qr-screenshots --csv otp-keys.csv
python qr_to_sheet.py --images ... --csv ... --dry-run # preview, no write
```
Undecodable images (stylized/colored QRs, or text-only screenshots) are listed
by filename so you can add them by hand.
## Importing
Import TOTP credentials from the CSV (or a legacy `.xlsx`) into the applet.
The importer reads `otp-keys.csv` (or a legacy `.xlsx`), takes the credential
name from the `issuer`/`account` columns and the crypto from `otpauth_uri`, and
writes each credential using the standard YKOATH `PUT` command. It authenticates
first if the applet is password-protected, asks before overwriting credentials
that already exist, and can optionally profile per-credential on-chip memory
consumption via the `javacard-memory` applet.
The protocol layer is verified against the applet sources
(`com.vivokey.otp.YkneoOath` / `OathObj`) and `doc/Protocol.txt`. See
[docs/design.md](docs/design.md) for the full design and protocol notes.
## Security
This tool handles live TOTP secrets. **Keep your secrets out of this
repository.** The spreadsheet, any CSV, and QR screenshots are excluded by
`.gitignore`; keep the actual data in a private location (e.g. a synced
personal folder) and point the importer at it with `--input`.
## Requirements
- Python 3.9+
- `pyscard`, `cryptography`, `openpyxl`
- A PC/SC reader with the Apex (or a JavaCard carrying the applet) presented
## Usage
```
# 1. Validate the data and preview the APDUs (no card needed)
python apex_otp_import.py --input /path/to/otp-keys.csv --dry-run
# 2. Read-only: list what is already on the applet
python apex_otp_import.py --input /path/to/otp-keys.csv --list
# 3. Import (asks before overwriting existing credentials)
python apex_otp_import.py --input /path/to/otp-keys.csv
# Non-interactive conflict handling
python apex_otp_import.py --input ... --overwrite-all
python apex_otp_import.py --input ... --skip-existing
# Target a specific reader, supply a password, and profile memory
python apex_otp_import.py --input ... --reader VivoKey --password '...' --profile-memory
# One-shot pipeline: decode new QRs into the CSV (only new, deduped on
# otpauth://), then validate and import. QR folder defaults to the CSV's folder.
python apex_otp_import.py --input otp-keys.csv --import-qr --reader VivoKey
python apex_otp_import.py --input otp-keys.csv --import-qr --qr-images /qr/folder --overwrite-all
# Import, then set an access password (only if the applet has none yet)
python apex_otp_import.py --input ... --set-password 'yourpassword'
# Reset: wipe all credentials and any password (standalone; no input needed)
python apex_otp_import.py --reset
```
Authenticating to a password-protected applet uses the standard YKOATH scheme
(PBKDF2-HMAC-SHA1 of the password with the device id as salt, then mutual
challenge-response). `--set-password` only sets a password on an applet that has
none; it does not change or remove an existing one. `--reset` clears everything.
The input is validated before any reader is touched. The run halts with a
per-row report if it finds: duplicate credential names; invalid Base32;
over-long names or keys; an unsupported algorithm; control characters or a
`:`/`/` inside an issuer or account (they break the OATH name convention); or a
digit count outside 6-8 / a non-positive period. Fix the source data and re-run.
## Tests
Hardware-free unit tests cover the protocol encoding, password derivation,
input validation, and response reassembly:
```
python test_oath.py
```